Compare commits

..
105 Commits
Author SHA1 Message Date
renovate[bot]andWolfgang Walther fa40c8ebb0 chore(deps): update cachix/install-nix-action digest to 13d8dd5 2026-08-13 21:06:08 +00:00
renovate[bot]andWolfgang Walther 1c722e4f3c chore(deps): update actions/checkout action to v7.0.1 2026-07-23 20:11:36 +00:00
renovate[bot]andWolfgang Walther ebb4ad0a52 chore(deps): update cachix/install-nix-action digest to 630ae54 2026-07-15 19:53:33 +00:00
renovate[bot]andWolfgang Walther 5168140151 chore(deps): update cachix/install-nix-action digest to a49548c 2026-07-10 20:26:38 +00:00
renovate[bot]andWolfgang Walther 8fcee34842 chore(deps): update all dependencies to v7 2026-06-19 19:08:03 +00:00
renovate[bot]andWolfgang Walther 98a1cbe5bb chore(deps): update all dependencies to v6.0.3 2026-06-03 18:34:24 +00:00
renovate[bot]andWolfgang Walther e11fa82b25 chore(deps): update cachix/install-nix-action digest to 8aa0397 2026-05-05 15:53:57 +00:00
renovate[bot]andWolfgang Walther 25c707a0e4 chore(deps): update cachix/install-nix-action digest to ab73962 2026-04-28 11:32:13 +00:00
Wolfgang Walther 64e8abeace docs: fix broken link 2026-04-15 09:30:42 +02:00
renovate[bot]andWolfgang Walther c2d76a37ab chore(deps): update cachix/install-nix-action digest to 6165592 2026-04-09 08:57:55 +00:00
renovate[bot]andWolfgang Walther 64704e5689 chore(deps): update cachix/install-nix-action digest to 96951a3 2026-03-30 09:58:08 +00:00
renovate[bot]andWolfgang Walther f2e1f6e43f chore(deps): update cachix/install-nix-action digest to 51f3067 2026-03-24 12:37:51 +00:00
renovate[bot]andWolfgang Walther 431e243fa8 chore(deps): update all dependencies 2026-03-13 14:03:01 +00:00
renovate[bot]andWolfgang Walther d27f6538b2 chore(deps): update cachix/install-nix-action digest to 4e002c8 2025-12-11 20:00:35 +00:00
renovate[bot]andWolfgang Walther 499ad37b20 chore(deps): update actions/checkout action to v6.0.1 2025-12-08 07:59:54 +00:00
renovate[bot]andWolfgang Walther 4a27bf59f1 chore(deps): update actions/checkout action to v6 2025-11-21 17:29:01 +00:00
renovate[bot]andWolfgang Walther 948ff53f2b chore(deps): update cachix/install-nix-action digest to 0b0e072 2025-11-18 17:49:01 +00:00
renovate[bot]andWolfgang Walther 6cd742ab45 chore(deps): update actions/checkout action to v5.0.1 2025-11-18 09:38:37 +00:00
renovate[bot]andWolfgang Walther f4acfe05a2 chore(deps): update cachix/install-nix-action digest to fd24c48 2025-10-15 11:04:15 +00:00
renovate[bot]andWolfgang Walther c6b985c961 chore(deps): update cachix/install-nix-action digest to 7ab6e7f 2025-10-14 09:42:49 +00:00
renovate[bot]andWolfgang Walther 8d146d6ef6 chore(deps): update cachix/install-nix-action digest to 9280e7a 2025-10-02 18:58:42 +00:00
renovate[bot]andWolfgang Walther 3b464bcfd1 chore(deps): update cachix/install-nix-action digest to a809471 2025-09-21 14:01:35 +00:00
renovate[bot]andWolfgang Walther 54843a903a chore(deps): update cachix/install-nix-action digest to 7be5dee 2025-09-13 09:46:11 +00:00
renovate[bot]andWolfgang Walther a114a9a275 chore(deps): update cachix/install-nix-action digest to 56a7bb7 2025-09-06 14:27:39 +00:00
renovate[bot]andWolfgang Walther 5023b5e66b chore(deps): update actions/checkout action to v5 2025-08-11 15:32:08 +00:00
renovate[bot]andWolfgang Walther 566782b153 chore(deps): update cachix/install-nix-action digest to c134e4c 2025-07-23 21:05:06 +00:00
renovate[bot]andWolfgang Walther 5fb3ef514f chore(deps): update cachix/install-nix-action digest to f0fe604 2025-07-12 14:36:27 +00:00
renovate[bot]andWolfgang Walther 48ae38cbf4 chore(deps): update cachix/install-nix-action digest to cebd211 2025-07-12 08:07:20 +00:00
renovate[bot]andWolfgang Walther 3c3c2f6d17 chore(deps): update cachix/install-nix-action digest to f0fe604 2025-07-09 07:08:45 +00:00
steve-chavezandWolfgang Walther 86f1d60952 docs: remove Greenplum integration
They're not really a sponsor, so it's not fair to include them.
2025-05-30 14:36:53 +02:00
Wolfgang Walther eb205e2157 docs: remove outdated "in production" links
Moat has been bought by Oracle. The advertising subpage redirects to
oracle.com, but pointing at that would be a bit misleading.

No need to keep failing links either.
2025-05-28 09:18:24 +02:00
renovate[bot]andWolfgang Walther 86f613499e chore(deps): update cachix/install-nix-action digest to d1ca217 2025-04-01 19:57:04 +00:00
renovate[bot]andWolfgang Walther 512633588a chore(deps): update cachix/install-nix-action action to v31 2025-03-27 09:04:59 +00:00
Wolfgang Walther 8dfdae993f docs: Remove broken link 2025-03-26 17:01:21 +01:00
Wolfgang Walther 20b8025b45 docs: Fix outdated links 2025-02-22 16:11:37 +01:00
Taimoor ZaeemandWolfgang Walther 716c5ea3f3 docs: add missing jwt claims and clock skew (#3908) 2025-02-22 13:19:32 +01:00
Laurence IslaandWolfgang Walther 1bd530df3a docs: fix broken links older GUC settings
No longer links but embeds the old settings in a details html element.
2024-12-25 11:42:50 +01:00
renovate[bot]andWolfgang Walther 2fc7ae77ed chore(deps): update all dependencies 2024-10-24 05:13:44 +02:00
renovate[bot]andWolfgang Walther b8efa956af chore(deps): update actions/checkout action to v4.2.2 2024-10-24 05:08:39 +02:00
Wolfgang Walther b2ffd73377 docs: Remove broken link
https://github.com/PostgREST/postgrest/actions/runs/11136795565/job/30949162312
2024-10-04 16:03:06 +02:00
renovate[bot]andWolfgang Walther bae2e817aa chore(deps): update dependency urllib3 to v2.2.3 2024-09-12 21:01:02 +02:00
steve-chavezandWolfgang Walther e99d7ea310 chore: remove links to gitter
We'll now use github discussions for support.
2024-07-13 17:13:36 +02:00
steve-chavezandWolfgang Walther 0d147c87d6 chore: remove paypal links
It was tied to a personal account and donations there have been too rare.
2024-07-10 21:13:44 +02:00
Sandro BauerandWolfgang Walther ea70b0bf82 docs: fix rendering for inline code block in operator list 2024-07-02 12:28:35 +02:00
Wolfgang Walther 5c4d7d374b docs: Fix linkcheck
Some URLs are still forbidden for our linkcheck tool, so disabling them
again.

Others are permanently redirected, so adjusting them.
2024-06-19 08:41:50 +02:00
renovate[bot]andWolfgang Walther 3d513d379f chore(deps): update dependency urllib3 to v2.2.2 2024-06-17 21:03:55 +02:00
renovate[bot]andWolfgang Walther a2c3d09e5f chore(deps): update actions/checkout action to v4.1.7 2024-06-13 08:46:04 +02:00
renovate[bot]andWolfgang Walther 2044f7393f chore(deps): update cachix/install-nix-action action to v27 2024-05-17 08:27:50 +02:00
renovate[bot]andWolfgang Walther 488699d126 chore(deps): update actions/checkout action to v4.1.6 2024-05-17 08:09:41 +02:00
renovate[bot]andWolfgang Walther 89ff91b2e1 chore(deps): update actions/checkout action to v4.1.5 2024-05-09 13:37:22 +02:00
Wolfgang WaltherandWolfgang Walther e789d2e815 ci: Remove all CI except docs on EOL branches 2024-05-04 13:34:52 +02:00
renovate[bot]andWolfgang Walther 54390e80c3 chore(deps): update all dependencies 2024-05-04 12:20:59 +02:00
renovate[bot]andWolfgang Walther e433c7d478 chore(deps): update all dependencies 2024-04-26 08:31:53 +02:00
renovate[bot]andWolfgang Walther 006c109f91 chore(deps): update actions/download-artifact action to v4.1.7 2024-04-24 20:53:34 +02:00
renovate[bot]andWolfgang Walther 09677ec535 chore(deps): update all dependencies 2024-04-22 21:29:43 +02:00
Wolfgang Walther 2c5f61a9d8 ci: Use explicit runner image versions 2024-04-22 21:26:23 +02:00
renovate[bot]andWolfgang Walther d41ad6d44a chore(deps): update all dependencies 2024-04-20 12:45:46 +02:00
renovate[bot]andWolfgang Walther 1a0a7b1917 chore(deps): update codecov/codecov-action action to v4.2.0 2024-04-04 07:17:41 +02:00
renovate[bot]andWolfgang Walther 39705ea700 chore(deps): update codecov/codecov-action action to v4.1.1 2024-03-26 19:56:32 +01:00
renovate[bot]andWolfgang Walther 6a4232a0b9 chore(deps): update actions/cache action to v4.0.2 2024-03-19 21:59:15 +01:00
renovate[bot]andWolfgang Walther 8d046e7004 chore(deps): update cachix/install-nix-action action to v26 2024-03-08 16:29:41 +01:00
renovate[bot]andWolfgang Walther 865766639f chore(deps): update ubuntu:jammy docker digest to 77906da 2024-03-07 16:28:11 +01:00
renovate[bot]andWolfgang Walther 56c9dbbae2 chore(deps): update actions/download-artifact action to v4.1.4 2024-03-02 12:10:23 +01:00
Wolfgang Walther 3c12dda474 chore: Add full semver comment to used actions 2024-03-01 08:58:56 +01:00
renovate[bot]andWolfgang Walther 6a2d50e2ab chore(deps): update actions/cache digest to ab5e6d0 2024-03-01 08:12:10 +01:00
Wolfgang Walther 593b1dc478 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:23:03 +01:00
renovate[bot]andWolfgang Walther 26c21d8cb8 chore(deps): update all dependencies 2024-02-26 22:15:51 +01:00
Wolfgang Walther c805ed45a5 ci: Use actions/download-artifact instead of dawidd6/action-download-artifact 2024-02-24 21:58:41 +01:00
renovate[bot]andWolfgang Walther 6bc5de3acb chore(deps): pin dependencies 2024-02-24 21:54:59 +01:00
renovate[bot]andWolfgang Walther 397503b560 chore(deps): update all dependencies 2024-02-24 19:46:01 +01:00
Wolfgang Walther da013f5877 chore(deps): update codecov/codecov-action action to v4 2024-02-24 18:43:28 +01:00
Wolfgang Walther 7a88adeaba docs: Display version number more prominently
Resolves https://github.com/PostgREST/postgrest-docs/issues/734
2024-02-19 21:52:09 +01:00
Wolfgang Walther 7e3bd50fe6 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:51:36 +01:00
Wolfgang Walther 64641fed46 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:47:27 +01:00
Wolfgang Walther 42a72f0603 ci: Refactor conditions to check for branch events 2024-02-17 17:46:46 +01:00
Wolfgang Walther b3600a26fb 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:10 +01:00
Wolfgang Walther 2a7a6b038a ci: Fix FreeBSD build on cirrus 2024-02-17 15:24:10 +01:00
Wolfgang Walther 2a95acd163 Merge branch 'docs/v11.2' into rel-11.2 2024-02-17 13:46:23 +01:00
Wolfgang Walther 68d83f7463 chore: Run postgrest-style on docs files
This prevents CI from failing after the merge.
2024-02-17 13:43:21 +01:00
Wolfgang Walther c1c45060ba 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:21 +01:00
Laurence Islaandsteve-chavez 6af706facd Remove former sponsors (#694) 2023-11-07 17:26:12 -05:00
Laurence Islaandsteve-chavez f975830d31 add Neon as new sponsor (#693) 2023-11-06 23:17:55 -05:00
Wolfgang Walther f884da79fe bump version to 11.2.2 2023-10-25 16:35:20 +02:00
Laurence IslaandWolfgang Walther 10e72ba0c1 fix: regression by reverting fix that returned 206 when first-pos=length in Range header 2023-10-25 16:35:20 +02:00
Kam Ting HoiandWolfgang Walther e1e0cea494 fix: range request with 0 rows and 0 offset return status 416 (#2991) 2023-10-25 16:16:21 +02:00
Laurence IslaandLaurence Isla 23790b7e8d Remove former sponsor 2023-10-05 13:08:06 -05:00
steve-chavez 005f9e4d59 releases: add v11.2.1 2023-10-04 12:24:29 -03:00
Laurence IslaandSteve Chavez d07b5acf15 Bump to v11.2.1 2023-10-04 00:07:09 -03:00
Laurence IslaandSteve Chavez 64d02d7375 fix: regression that rejects URI connection strings with certain unescaped characters in the password 2023-10-04 00:07:09 -03:00
Laurence IslaandSteve Chavez 5c822b7ec4 fix: RPCs not embedding correctly when using overloaded functions for computed relationships 2023-10-04 00:07:09 -03:00
steve-chavez de97f646a4 ci: fix MacOS CI 2023-10-04 00:07:09 -03:00
steve-chavez a45c639a97 fix: arrow filter on RPC returning TABLE+composite 2023-10-04 00:07:09 -03:00
steve-chavez 1e711051da fix: unnecessary set default_transaction_isolation 2023-10-04 00:07:09 -03:00
steve-chavez 79077c873f test: adjust memory test 2023-10-04 00:07:09 -03:00
Laurence IslaandSteve Chavez 2ed163945b Add test option for PostgreSQL 16 2023-10-04 00:07:09 -03:00
Laurence IslaandSteve Chavez cf3cd4b8d0 Add missing changelog entries 2023-10-04 00:07:09 -03:00
Laurence IslaandSteve Chavez 38ea4378da fix: error when requesting "Prefer: count=<type>" with null filters on embedded resources 2023-10-04 00:07:09 -03:00
Laurence IslaandSteve Chavez 226f1caa24 fix: bug when Null Filtering on embedded resources
When doing Null Filtering, the to-one embed resources were not included if they had a NULL value in any of the selected fields.
2023-10-04 00:07:09 -03:00
steve-chavez 6475f254f7 fix: inconsistent Preference-Applied
* Don't apply `tx=commit` if the transaction doesn't commit
* Apply `count=exact`
* Also simplifies the Preference-Applied logic, removing the need for
  some functions.
2023-10-04 00:07:09 -03:00
dependabot[bot]andSteve Chavez d6cd5d0fb4 build(deps): bump cachix/install-nix-action (#2935) 2023-10-04 00:07:09 -03:00
dependabot[bot]andSteve Chavez c73282c3f0 build(deps): bump actions/checkout from 3 to 4 (#2936) 2023-10-04 00:07:09 -03:00
Taimoor ZaeemandSteve Chavez d7dfdaa03f fix: range request with first position same as length return status 206 2023-10-04 00:07:09 -03:00
Taimoor ZaeemandSteve Chavez 6c8ce3929c fix: duplicate headers in response 2023-10-04 00:07:09 -03:00
Diogo BiazusandSteve Chavez f84bc6a0ff fix: schema cache and configuration reloading with NOTIFY not working on Windows 2023-10-04 00:07:09 -03:00
Taimoor ZaeemandSteve Chavez 82ecf836c4 fix: application/vnd.pgrst.array not accepted as a valid mediatype 2023-10-04 00:07:09 -03:00
328 changed files with 10936 additions and 20416 deletions
-42
View File
@@ -1,42 +0,0 @@
freebsd_instance:
image_family: freebsd-14-2
build_task:
# Don't change this name without adjusting .github/workflows/build.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'
)
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
bin_artifacts:
path: postgrest
-9
View File
@@ -1,9 +0,0 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
+17
View File
@@ -0,0 +1,17 @@
<!--
Before reporting a bug:
If your database schema has changed while the PostgREST server is running,
send the server a SIGUSR1 signal or restart it(http://postgrest.org/en/stable/admin.html#schema-reloading)
to ensure the schema cache is not stale. This sometimes fixes apparent bugs.
-->
### Environment
* PostgreSQL version: (if using docker, specify the image)
* PostgREST version: (if using docker, specify the image)
* Operating system:
### Description of issue
(Expected behavior vs actual behavior)
(Steps to reproduce: Include a minimal SQL definition plus how you make the request to PostgREST and the response body)
-28
View File
@@ -1,28 +0,0 @@
---
name: Bug report
about: Create a bug report to help us improve
type: Bug
title: ''
labels: ''
assignees: ''
---
<!--
Before reporting a bug:
If your database schema has changed while the PostgREST server is running,
send the server a SIGUSR1 signal or restart it (http://postgrest.org/en/stable/admin.html#schema-reloading) to ensure the schema cache is not stale. This sometimes fixes apparent bugs.
-->
### Environment
* PostgreSQL version: (if using docker, specify the image)
* PostgREST version: (if using docker, specify the image)
* Operating system:
### Description of issue
Describe the behavior you expected vs the actual behavior. Include:
- A minimal SQL definition.
- How you make the request to PostgREST (curl command preferred).
- The PostgREST response.
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
-17
View File
@@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an enhancement for this project
type: Feature
title: ''
labels: ''
assignees: ''
---
## Problem
A clear and concise description of what the problem is.
## Solution
A clear and concise description of what you want to happen.
-14
View File
@@ -3,18 +3,4 @@ When submitting a new feature or fix:
- Add a new entry to the CHANGELOG - https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md#unreleased - Add a new entry to the CHANGELOG - https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md#unreleased
- If relevant, update the docs - If relevant, update the docs
- Use a prefix for the PR title or commits, e.g. "fix: description of the 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.)
+ Other prefixes may be used if necessary
- If there's a breaking change, add `BREAKING CHANGE` and an explanation to your commit message
--> -->
-5
View File
@@ -1,5 +0,0 @@
# TODO: Remove this once a new actionlint release has been cut
# and made its way to us through nixpkgs.
self-hosted-runner:
labels:
- ubuntu-24.04-arm
@@ -1,119 +0,0 @@
name: Artifact from Cirrus
description: Waits for a specific Cirrus CI run to complete, then downloads the artifact and uploads it to the current workflow. This will silently succeed if Cirrus CI did not schedule a task within 2 minutes.
inputs:
download:
description: Name of Artifact to download from Cirrus CI
required: true
task:
description: Name of Cirrus Task
required: true
token:
description: GitHub Token
required: true
upload:
description: Name of Artifact to upload on GitHub Actions
required: true
runs:
using: composite
steps:
- shell: bash
run: echo "GH_TOKEN=${{ inputs.token }}" >> "$GITHUB_ENV"
- name: Wait for Check Suite to be created
id: check-suite
env:
# GITHUB_SHA does weird things for pull request, so we roll our own:
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
shell: bash
run: |
get_check_runs_url() {
gh api "repos/{owner}/{repo}/commits/${COMMIT}/check-suites" \
| jq -r '.check_suites[] | select(.app.slug == "cirrus-ci") | .check_runs_url'
}
for _ in $(seq 1 12); do
check_runs_url="$(get_check_runs_url)"
if [ -z "$check_runs_url" ]; then
echo "Cirrus CI task has not started, yet. Waiting..."
sleep 10
else
echo "check_runs_url=$check_runs_url" >> "$GITHUB_OUTPUT"
exit 0
fi
done
>&2 echo "Cirrus CI check suite not found. Is Cirrus CI enabled for this repo?"
- name: Find task by name
id: find-task
if: steps.check-suite.outputs.check_runs_url
shell: bash
run: |
get_number_of_tasks() {
gh api "${{ steps.check-suite.outputs.check_runs_url }}" \
| jq -r '.check_runs | map(select(.name == "${{ inputs.task }}")) | length'
}
tasks="$(get_number_of_tasks)"
case "$tasks" in
0)
echo "Task not found, assuming it's skipped intentionally..."
exit 0
;;
1)
echo "task_found=1" >> "$GITHUB_OUTPUT"
exit 0
;;
*)
>&2 echo "More than 1 task with the same name found. Don't know what to do..."
exit 1
;;
esac
- name: Wait for Cirrus CI to complete task
if: steps.find-task.outputs.task_found
shell: bash
run: |
get_conclusion() {
gh api "${{ steps.check-suite.outputs.check_runs_url }}" \
| jq -r '.check_runs[] | select(.name == "${{ inputs.task }}" and .status == "completed") | .conclusion'
}
while true; do
conclusion="$(get_conclusion)"
if [ -z "$conclusion" ]; then
echo "Cirrus CI task has not completed, yet. Waiting..."
sleep 30
else
if [ "$conclusion" == "success" ]; then
break
else
exit 1
fi
fi
done
- name: Download artifact from Cirrus CI
if: steps.find-task.outputs.task_found
id: download
shell: bash
run: |
get_external_id() {
gh api "${{ steps.check-suite.outputs.check_runs_url }}" \
| jq -er '.check_runs[] | select(.name == "${{ inputs.task }}") | .external_id'
}
archive="$(mktemp)"
artifacts="$(mktemp -d)"
until curl --no-progress-meter --fail -o "${archive}" \
"https://api.cirrus-ci.com/v1/artifact/task/$(get_external_id)/${{ inputs.download }}.zip"
do
# This happens when a tag is pushed on the same commit. In this case the
# job is immediately marked as "completed" for us, so we end up here after a few
# seconds - but the actual Cirrus CI task is still running and didn't produce its artifact, yet.
echo "Artifact not found on Cirrus CI, yet. Waiting..."
sleep 30
done
unzip "${archive}" -d "${artifacts}"
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
- name: Save artifact to GitHub Actions
if: steps.find-task.outputs.task_found
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ inputs.upload }}
path: ${{ steps.download.outputs.artifacts }}
if-no-files-found: error
-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@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
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@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
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 }}-
-26
View File
@@ -1,26 +0,0 @@
name: Setup Nix
description: Installs nix, sets up cachix and installs a subset of tooling.
inputs:
authToken:
description: Token to pass to cachix
tools:
description: Tools to install with nix-env -iA <tools>
runs:
using: composite
steps:
- uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30
with:
nix_conf: |-
always-allow-substitutes = true
max-jobs = auto
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: postgrest
authToken: ${{ inputs.authToken }}
skipPush: ${{ inputs.authToken == '' }}
- if: ${{ inputs.tools }}
run: nix-env -f default.nix -iA ${{ inputs.tools }}
shell: bash
-18
View File
@@ -1,18 +0,0 @@
codecov:
branch: main
require_ci_to_pass: false
comment: false
coverage:
status:
project:
default:
target: auto
threshold: 1%
only_pulls: false
patch:
default:
target: auto
threshold: 1%
only_pulls: true
-40
View File
@@ -1,40 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices"
],
"baseBranches": [
"main",
"/^v[0-9]+/"
],
"rebaseWhen": "conflicted",
"pip_requirements": {
"enabled": false
},
"packageRules": [
{
"matchBaseBranches": [ "/^v[0-9]+/" ],
"matchManagers": ["haskell-cabal"],
"enabled": false
},
{
"matchBaseBranches": [ "/^v[0-9]+/" ],
"groupName": "all dependencies"
},
{
"matchManagers": ["haskell-cabal"],
"matchPackageNames": ["base", "bytestring", "containers", "directory", "mtl", "parsec", "process", "text"],
"groupName": "GHC dependencies"
},
{
"matchManagers": ["haskell-cabal"],
"matchPackageNames": ["hasql", "hasql-dynamic-statements", "hasql-notifications", "hasql-transaction", "hasql-pool"],
"groupName": "hasql"
},
{
"matchManagers": ["haskell-cabal"],
"matchPackageNames": ["fuzzyset"],
"allowedVersions": "<0.3"
}
]
}
-210
View File
@@ -1,210 +0,0 @@
name: Build
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/build.yaml
- .github/actions/**
- .github/scripts/**
- .github/*
- '*.nix'
- nix/**
- .cirrus.yml
- cabal.project*
- postgrest.cabal
- stack.yaml*
- '**.hs'
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: build-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
static:
name: Nix - Linux x86-64 static
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build static executable
run: nix-build -A postgrestStatic
- name: Save built executable as artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: postgrest-linux-static-x86-64
path: result/bin/postgrest
if-no-files-found: error
- name: Build Docker image
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
- name: Save built Docker image as artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: postgrest-docker-x86-64
path: postgrest-docker.tar.gz
if-no-files-found: error
macos:
name: Nix - MacOS
runs-on: macos-14
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Install gnu sed
run: brew install gnu-sed
- name: Build everything
run: |
# The --dry-run will give us a list of derivations to download from cachix and
# derivations to build. We only take those that would have to be built and then build
# those explicitly. This has the advantage that pure verification will not include
# a download anymore, making it much faster. If something needs to be built, only
# the dependencies required to do so will be downloaded, but not everything.
nix-build --dry-run 2>&1 \
| gsed -e '1,/derivations will be built:$/d' -e '/paths will be fetched/Q' \
| xargs nix-build
stack:
strategy:
fail-fast: false
matrix:
include:
- name: Linux aarch64
runs-on: ubuntu-24.04-arm
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-ubuntu-aarch64
deps: sudo apt-get update && sudo apt-get install libpq-dev
- name: MacOS aarch64
runs-on: macos-14
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-macos-aarch64
deps: brew link --force libpq
- name: MacOS x86-64
runs-on: macos-13
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-macos-x86-64
deps: brew link --force libpq
- name: Windows
runs-on: windows-2022
cache: |
C:\sr\pantry
C:\sr\snapshots
C:\sr\stack.sqlite3
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
artifact: postgrest-windows-x86-64
name: Stack - ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: haskell-actions/setup@64445b6b5dd545faf5f8e2acee8253eb5c2b29aa # v2.7.11
with:
# This must match the version in stack.yaml's resolver
ghc-version: 9.6.6
enable-stack: true
stack-no-global: true
stack-setup-ghc: true
- name: Cache ~/.stack
uses: ./.github/actions/cache-on-main
with:
path: ${{ matrix.cache }}
prefix: stack
suffix: ${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
- name: Cache .stack-work
uses: ./.github/actions/cache-on-main
with:
path: .stack-work
save-prs: true
prefix: stack-work-${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
suffix: ${{ hashFiles('main/**/*.hs', 'src/**/*.hs') }}
- name: Install dependencies
if: matrix.deps
run: ${{ matrix.deps }}
- name: Build with Stack
run: stack build --lock-file error-on-write --local-bin-path result --copy-bins
- name: Strip Executable
run: strip result/postgrest*
- name: Save built executable as artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ matrix.artifact }}
path: |
result/postgrest
result/postgrest.exe
if-no-files-found: error
freebsd:
name: Stack - FreeBSD from CirrusCI
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/artifact-from-cirrus
with:
token: ${{ github.token }}
task: Build FreeBSD (Stack)
download: bin
upload: postgrest-freebsd-x86-64
cabal:
strategy:
matrix:
ghc: ['9.6.6', '9.8.2']
fail-fast: false
name: Cabal - Linux x86-64 - GHC ${{ matrix.ghc }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: haskell-actions/setup@64445b6b5dd545faf5f8e2acee8253eb5c2b29aa # v2.7.11
with:
ghc-version: ${{ matrix.ghc }}
- name: Cache .cabal
uses: ./.github/actions/cache-on-main
with:
path: |
~/.cabal/packages
~/.cabal/store
prefix: cabal-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
suffix: ${{ hashFiles('postgrest.cabal', 'cabal.project') }}
- name: Cache dist-newstyle
uses: ./.github/actions/cache-on-main
with:
path: dist-newstyle
save-prs: true
prefix: cabal-${{ matrix.ghc }}-dist-newstyle-${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
suffix: ${{ hashFiles('**/*.hs') }}
- name: Install dependencies
run: cabal build --only-dependencies --enable-tests --enable-benchmarks
- name: Build
run: cabal build --enable-tests --enable-benchmarks all
-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-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: style.lint.bin style.styleCheck.bin
- name: Run linter (check locally with `nix-shell --run postgrest-lint`)
run: postgrest-lint
- name: Run style check (auto-format with `nix-shell --run postgrest-style`)
run: postgrest-style-check
-70
View File
@@ -1,70 +0,0 @@
name: CI
on:
push:
branches:
- main
- v[0-9]+
jobs:
check:
name: Check
uses: ./.github/workflows/check.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
docs:
name: Docs
uses: ./.github/workflows/docs.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
test:
name: Test
uses: ./.github/workflows/test.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build:
name: Build
uses: ./.github/workflows/build.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
tag:
name: Tag
concurrency:
# Never tag outdated commits on the main branch by skipping superseded commits
group: ci-tag-${{ (github.ref == 'refs/heads/main' && github.ref) || github.run_id }}
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
cancel-in-progress: false
if: vars.RELEASE_ENABLED
runs-on: ubuntu-24.04
needs:
- docs
- test
- build
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
- name: Tag latest commit
run: |
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
if [[ "$cabal_version" == *.*.* ]]; then
git fetch --tags
if [ -z "$(git tag --list "v$cabal_version")" ]; then
git tag "v$cabal_version"
git push origin "v$cabal_version"
fi
else
git tag -f "devel"
git push -f origin "devel"
fi
+35 -38
View File
@@ -1,53 +1,50 @@
name: Docs name: Docs
on: on:
workflow_call: push:
secrets: branches:
CACHIX_AUTH_TOKEN: - main
required: false - v[0-9]+
pull_request: pull_request:
branches: branches:
- main - main
- v[0-9]+ - 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
jobs: jobs:
build: build:
name: Build name: Build docs
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Nix Environment - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
uses: ./.github/actions/setup-nix - run: nix-env -f docs/default.nix -iA build
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: docs.build.bin
- run: postgrest-docs-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: spellcheck:
name: Spellcheck name: Run spellcheck
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Nix Environment - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
uses: ./.github/actions/setup-nix - run: nix-env -f docs/default.nix -iA spellcheck
with: - run: postgrest-docs-spellcheck
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: docs.spellcheck.bin docs.dictcheck.bin dictcheck:
- name: Run spellcheck name: Run dictcheck
run: postgrest-docs-spellcheck runs-on: ubuntu-24.04
- name: Run dictcheck steps:
run: postgrest-docs-dictcheck - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
- run: nix-env -f docs/default.nix -iA dictcheck
- run: postgrest-docs-dictcheck
linkcheck:
name: Run linkcheck
if: github.base_ref == 'main'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
- run: nix-env -f docs/default.nix -iA linkcheck
- run: postgrest-docs-linkcheck
-17
View File
@@ -1,17 +0,0 @@
name: Linkcheck
on:
schedule:
- cron: '1 2 * * 3'
jobs:
linkcheck:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: docs.linkcheck.bin
- run: postgrest-docs-linkcheck
-205
View File
@@ -1,205 +0,0 @@
name: Release
on:
push:
tags:
- devel
- v*
concurrency:
# Terminate all previous runs of the same workflow for the same tag.
group: release-${{ github.ref }}
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
cancel-in-progress: false
jobs:
build:
name: Build
uses: ./.github/workflows/build.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
prepare:
name: Prepare
runs-on: ubuntu-24.04
needs:
- build
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check the version to be released
run: |
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
if [ "${GITHUB_REF_NAME}" != "devel" ] && [ "${GITHUB_REF_NAME}" != "v$cabal_version" ]; then
echo "Tagged version ($GITHUB_REF_NAME) does not match the one in postgrest.cabal (v$cabal_version). Aborting release..."
exit 1
fi
- name: Identify changes from CHANGELOG.md
run: |
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
echo "Getting unreleased changes..."
sed -n "1,/## Unreleased/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
else
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
echo "Propper release, getting changes for version $version ..."
sed -n "1,/## \[$version\]/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
fi
echo "Relevant extract from CHANGELOG.md:"
cat CHANGES.md
- name: Save CHANGES.md as artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-changes
path: CHANGES.md
if-no-files-found: error
github:
name: GitHub
permissions:
contents: write
runs-on: ubuntu-24.04
needs:
- prepare
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download all artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts
- name: Create release bundle with archives for all builds
run: |
find artifacts -type f -iname postgrest -exec chmod +x {} \;
mkdir -p release-bundle
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x86-64.tar.xz" \
-C artifacts/postgrest-linux-static-x86-64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
-C artifacts/postgrest-macos-aarch64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-x86-64.tar.xz" \
-C artifacts/postgrest-macos-x86-64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
-C artifacts/postgrest-freebsd-x86-64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
-C artifacts/postgrest-ubuntu-aarch64 postgrest
zip --junk-paths "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x86-64.zip" \
artifacts/postgrest-windows-x86-64/postgrest.exe
- name: Save release bundle
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-bundle
path: release-bundle
if-no-files-found: error
- name: Publish release on GitHub
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Releasing version ${GITHUB_REF_NAME} on GitHub..."
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
# To replace the existing release, we must first delete the old assets,
# then modify the release, then add the new assets.
gh release view devel --json assets \
| jq -r '.assets[] | .name' \
| xargs -rn1 \
gh release delete-asset -y devel
gh release edit devel \
-t devel \
--verify-tag \
-F artifacts/release-changes/CHANGES.md \
--prerelease
gh release upload --clobber devel release-bundle/*
else
gh release create "${GITHUB_REF_NAME}" \
-t "${GITHUB_REF_NAME}" \
--verify-tag \
-F artifacts/release-changes/CHANGES.md \
release-bundle/*
fi
docker:
name: Docker Hub
runs-on: ubuntu-24.04-arm
needs:
- prepare
if: |
vars.DOCKER_REPO && vars.DOCKER_USER
env:
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download x86-64 Docker image
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: postgrest-docker-x86-64
- name: Download aarch64 binary
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: postgrest-ubuntu-aarch64
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build aarch64 Docker image
run: |
# This only pushes the image via digest, not a tag. This will not appear
# in the image list on Docker Hub, yet. It will be later added to the main
# tag's manifest.
docker buildx build \
-t "$DOCKER_REPO/postgrest" \
--platform linux/arm64 \
--output push-by-digest=true,type=image,push=true \
--metadata-file metadata.json \
.
echo "SHA256_ARM=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_ENV"
- name: Publish images on Docker Hub
run: |
docker load -i postgrest-docker.tar.gz
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
docker buildx imagetools create --append \
-t "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
"$DOCKER_REPO/postgrest@$SHA256_ARM"
# Only tag 'latest' for full releases
if [ "${GITHUB_REF_NAME}" != "devel" ]; then
echo "Pushing to 'latest' tag for full release of ${GITHUB_REF_NAME} ..."
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
docker push "$DOCKER_REPO"/postgrest:latest
docker buildx imagetools create --append \
-t "$DOCKER_REPO/postgrest:latest" \
"$DOCKER_REPO/postgrest@$SHA256_ARM"
else
echo "Skipping push to 'latest' tag for pre-release..."
fi
docker-description:
name: Docker Hub Description
runs-on: ubuntu-24.04
if: |
vars.DOCKER_REPO && vars.DOCKER_USER &&
github.ref == 'refs/tags/devel'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
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
-158
View File
@@ -1,158 +0,0 @@
name: Test
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
CODECOV_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/test.yaml
- .github/workflows/report.yaml
- .github/actions/setup-nix/**
- default.nix
- nix/**
- .stylish-haskell.yaml
- cabal.project
- postgrest.cabal
- '**.hs'
- test/**
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: test-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
coverage:
name: Coverage
runs-on: ubuntu-24.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.coverage.bin tests.testDoctests.bin tests.testSpecIdempotence.bin
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
run: postgrest-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
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: [12, 13, 14, 15, 16, 17]
name: PG ${{ matrix.pgVersion }}
runs-on: ubuntu-24.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.testSpec.bin tests.testIO.bin tests.testBigSchema.bin withTools.postgresql-${{ matrix.pgVersion }}.bin
- name: Run spec tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
- name: Run IO tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io -vv
- name: Run IO tests on a big schema
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
memory:
name: Memory
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.testMemory.bin
- name: Run memory tests
run: postgrest-test-memory
loadtest:
strategy:
matrix:
kind: ['mixed', 'jwt']
name: Loadtest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: loadtest.loadtestAgainst.bin loadtest.report.bin
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
id: get-latest-tag
with:
prefix: v
- name: Run loadtest
run: |
postgrest-loadtest-against -k ${{ matrix.kind }} main ${{ steps.get-latest-tag.outputs.tag }}
postgrest-loadtest-report >> "$GITHUB_STEP_SUMMARY"
flake:
strategy:
fail-fast: false
matrix:
runs-on:
- macos-13 # x86_64-darwin
- macos-14 # aarch64-darwin
- ubuntu-24.04 # x86_64-linux
- ubuntu-24.04-arm # aarch64-linux
name: Flake Check
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Run flake check
run: |
nix flake check
+1 -2
View File
@@ -14,7 +14,7 @@ site
.#* .#*
*.swp *.swp
result* result*
dist-* dist-newstyle
postgrest.hp postgrest.hp
postgrest.prof postgrest.prof
__pycache__ __pycache__
@@ -24,4 +24,3 @@ coverage
loadtest loadtest
.history .history
.docs-build .docs-build
gen_targets.http
+1 -1
View File
@@ -5,6 +5,6 @@ python:
install: install:
- requirements: docs/requirements.txt - requirements: docs/requirements.txt
build: build:
os: ubuntu-24.04 os: ubuntu-22.04
tools: tools:
python: "3.11" python: "3.11"
+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.
+13 -28
View File
@@ -4,30 +4,30 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
## Sponsors ## Sponsors
<table align="center"> <table>
<tbody> <tbody>
<tr> <tr>
<td align="center" valign="middle"> <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"> <a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/cybertec.svg"> <img width="222px" src="static/cybertec-new.png">
</a> </a>
</td> </td>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank"> <a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
<img width="296px" src="static/neon.jpg"> <img width="296px" src="static/2ndquadrant.png">
</a> </a>
</td> </td>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a href="https://code.build/?utm_source=sponsor&utm_campaign=postgrest" target="_blank"> <a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/code-build.png"> <img width="296px" src="static/retool.png">
</a> </a>
</td> </td>
</tr> </tr>
<tr></tr> <tr></tr>
<tr> <tr>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank"> <a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/tembo.png"> <img width="296px" src="static/gnuhost.png">
</a> </a>
</td> </td>
<td align="center" valign="middle"> <td align="center" valign="middle">
@@ -35,6 +35,11 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
<img width="296px" src="static/supabase.png"> <img width="296px" src="static/supabase.png">
</a> </a>
</td> </td>
<td align="center" valign="middle">
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/oblivious.jpg">
</a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -73,26 +78,6 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
<img width="222px" src="static/timescaledb.png"> <img width="222px" src="static/timescaledb.png">
</a> </a>
</td> </td>
<td align="center" valign="middle">
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img max-width="222px" height="88" src="static/retool.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
<img width="222px" src="static/2ndquadrant.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="222px" src="static/oblivious.jpg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="222px" src="static/gnuhost.png">
</a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
-284
View File
@@ -5,285 +5,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
## [13.0.2] - 2025-06-02
### Fixed
- Fix regression that makes `ORDER BY` with nulls-order not work alongside limits by @laurenceisla in #4109
## [13.0.1] - 2025-06-01
### Fixed
- Fix jwt error returning HTTP status `400` for invalid role by @taimoorzaeem in #3601
- Fix `db-extra-search-path` cannot be set to nothing by @taimoorzaeem in #4074
+ It can now be disabled by setting it to empty string.
+ Schema Cache load error is now logged including `db-schemas` and `db-extra-search-path` config values.
## [13.0.0] - 2025-05-08
### Added
- #3558, Add the `admin-server-host` config to set the host for the admin server - @develop7
- #3607, Log to stderr when the JWT secret is less than 32 characters long - @laurenceisla
- #2858, Performance improvements when calling RPCs via GET using indexes in more cases - @wolfgangwalther
- #3560, Log resolved host in "Listening on ..." messages - @develop7
- #3727, Log maximum pool size - @steve-chavez
- #1536, Add string comparison feature for jwt-role-claim-key - @taimoorzaeem
- #3747, Allow `not_null` value for the `is` operator - @taimoorzaeem
- #2255, Apply `to_tsvector()` explicitly to the full-text search filtered column (excluding `tsvector` types) - @laurenceisla
- #1578, Log the main SQL query to stderr at the current `log-level` when `log-query=main-query` - @laurenceisla
- #3903, Log connection pool borrows on `log-level=debug` - @taimoorzaeem
- #3041, Allow spreading one-to-many and many-to-many embedded resources - @laurenceisla
+ The selected columns in the embedded resources are aggregated into arrays
+ Aggregates are not supported
- #2967, Add `Proxy-Status` header for better error response - @taimoorzaeem
- #4016, Add `Content-Length` response header - @laurenceisla
### Fixed
- #3693, Prevent spread embedding to allow aggregates when they are disabled - @laurenceisla
- #3693, A nested spread embedding now correctly groups by the fields of its top parent relationship - @laurenceisla
- #3693, Fix spread embedding errors when using the `count()` aggregate without a field - @laurenceisla
+ Fixed `"column reference <col> is ambiguous"` error when selecting `?select=...table(col,count())`
+ Fixed `"column <json_aggregate>.<alias> does not exist"` error when selecting `?select=...table(aias:count())`
- #3727, Clarify "listening" logs - @steve-chavez
- #3795, Clarify `Accept: vnd.pgrst.object` error message - @steve-chavez
- #3697, #3602, Handle queries on non-existing table gracefully - @taimoorzaeem
- #3600, #3926, Improve JWT errors - @taimoorzaeem
- #3013, Fix `order=` with POST, PATCH, PUT and DELETE requests - @taimoorzaeem
- #3965, Fix filter on unselected columns in a table-valued function - @taimoorzaeem
- #4052, Fix schema cache load duplicate objects with different object type but same oid - @taimoorzaeem
### Changed
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
- #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther
- #2052, Dropped support for PostgreSQL 11 - @wolfgangwalther
- #3508, PostgREST now fails to start when `server-port` and `admin-server-port` config options are the same - @develop7
- #3607, PostgREST now fails to start when the JWT secret is less than 32 characters long - @laurenceisla
- #3644, Fail schema cache lookup with invalid `db-schemas` or `db-extra-search-path` config - @wolfgangwalther
- Previously, this would silently return 200 - OK on the root endpoint, but don't provide any usable endpoints.
- Note: This also applies when deleting the `public` schema - both config options default to that.
- #3757, Remove support for `Prefer: params=single-object` - @joelonsql
+ This preference was deprecated in favor of Functions with an array of JSON objects
- #3013, Drop support for Limited updates/deletes
+ The feature was complicated and largely unused.
- #3956, Drop `/config` endpoint of admin server - @steve-chavez
+ The endpoint was at risk of being left unprotected when exposing it.
+ The accompanying `admin-server-config-enabled` config was also dropped.
- #3697, #3602, Querying non-existent table now returns `PGRST205` error instead of empty json - @taimoorzaeem
- #3600, #3926, Improve JWT errors - @taimoorzaeem
+ Return `PGRST301` error when `Bearer` in auth header is sent empty
+ Diagnostic error messages instead of exposed internals
+ Return new `PGRST303` error when jwt claims decoding fails
- #3906, Return `PGRST125` and `PGRST126` errors instead of empty json - @taimoorzaeem
## [12.2.12] - 2025-05-01
### Fixed
- #3956, Fix exposing admin server `/config` by default - @steve-chavez
+ The above endpoint is now disabled unless the `admin-server-config-enabled` config is set to `true`
## [12.2.11] - 2025-04-22
### Fixed
- #4030, Fix regression with parameter `charset=utf-8` in mediatype - @taimoorzaeem
## [12.2.10] - 2025-04-18
### Fixed
- #3889, Fix: JWT cache purging on every request decreases performance - @mkleczek
## [12.2.9] - 2025-04-16
### Fixed
- #3498, Fix incorrect parsing of the `for` parameter of the `application/vnd.pgrst.plan` media type - @taimoorzaeem
- #4014, Fix JWT cache allows old tokens after the jwt-secret is changed in a config reload - @taimoorzaeem
## [12.2.8] - 2025-02-10
### Fixed
- #3841, Log `503` client error to stderr - @taimoorzaeem
## [12.2.7] - 2025-02-03
### Fixed
- #2524, Fix schema reloading notice on windows - @diogob
## [12.2.6] - 2025-01-29
### Fixed
- #3788, Fix jwt cache does not remove expired entries - @taimoorzaeem
## [12.2.5] - 2025-01-20
### Fixed
- #3867, Fix startup for arm64 docker image - @wolfgangwalther
## [12.2.4] - 2025-01-18
### Fixed
- #3779, Always log the schema cache load time - @steve-chavez
- #3706, Fix insert with `missing=default` uses default value of domain instead of column - @taimoorzaeem
## [12.2.3] - 2024-08-01
### Fixed
- #3091, Broken link in OpenAPI description `externalDocs` - @salim-b
- #3659, Embed One-to-One relationship with different column order properly - @wolfgangwalther
- #3504, Remove `format` from `rowFilter` parameters in OpenAPI - @dantheman2865
- #3660, Fix regression that loaded the schema cache before the in-database configuration - @steve-chavez, @laurenceisla
## [12.2.2] - 2024-07-10
### Fixed
- #3093, Nested empty embeds no longer show empty values and are correctly omitted - @laurenceisla
- #3644, Make --dump-schema work with in-database pgrst.db_schemas setting - @wolfgangwalther
- #3644, Show number of timezones in schema cache load report - @wolfgangwalther
- #3644, List correct enum options in OpenApi output when multiple types with same name are present - @wolfgangwalther
- #3523, Fix schema cache loading retry without backoff - @steve-chavez
## [12.2.1] - 2024-06-27
### Fixed
- #3147, Don't reload schema cache on every listener failure - @steve-chavez
### Documentation
- #3592, Architecture diagram now supports dark mode and has links - @laurenceisla
- #3616, The schema isolation diagram now supports dark mode and uses well-known schemas - @laurenceisla
## [12.2.0] - 2024-06-11
### Added
- #2887, Add Preference `max-affected` to limit affected resources - @taimoorzaeem
- #3171, Add an ability to dump config via admin API - @skywriter
- #3171, #3046, Log schema cache stats to stderr - @steve-chavez
- #3210, Dump schema cache through admin API - @taimoorzaeem
- #2676, Performance improvement on bulk json inserts, around 10% increase on requests per second by removing `json_typeof` from write queries - @steve-chavez
- #3435, Add log-level=debug, for development purposes - @steve-chavez
- #1526, Add `/metrics` endpoint on admin server - @steve-chavez
- Exposes connection pool metrics, schema cache metrics
- #3404, Show the failed MESSAGE or DETAIL in the `details` field of the `PGRST121` (could not parse RAISE 'PGRST') error - @laurenceisla
- #3404, Show extra information in the `PGRST121` (could not parse RAISE 'PGRST') error - @laurenceisla
+ Shows the failed MESSAGE or DETAIL in the `details` field
+ Shows the correct JSON format in the `hints` field
- #3340, Log when the LISTEN channel gets a notification - @steve-chavez
- #3184, Log full pg version to stderr on connection - @steve-chavez
- #3242. Add config `db-hoisted-tx-settings` to apply only hoisted function settings - @taimoorzaeem
- #3214, #3229 Log connection pool events on log-level=debug - @steve-chavez, @laurenceisla
### Fixed
- #3237, Dump media handlers and timezones with --dump-schema - @wolfgangwalther
- #3323, #3324, Don't hide error on LISTEN channel failure - @steve-chavez
- #3330, Incorrect admin server `/ready` response on slow schema cache loads - @steve-chavez
- #3345, Fix in-database configuration values not loading for `pgrst.server_trace_header` and `pgrst.server_cors_allowed_origins` - @laurenceisla
- #3404, Clarify the `PGRST121` (could not parse RAISE 'PGRST') error message - @laurenceisla
- #3267, Fix wrong `503 Service Unavailable` on pg error `53400` - @taimoorzaeem
- #2985, Fix not adding `application_name` on all connection strings - @steve-chavez
- #3424, Admin `/live` and `/ready` now differentiates a failure as 500 status - @steve-chavez
+ 503 status is still given when postgREST is in a recovering state
- #3478, Media Types are parsed case insensitively - @develop7
- #3533, #3536, Fix listener silently failing on read replica - @steve-chavez
+ If the LISTEN connection fails, it's retried with exponential backoff
- #3414, Force listener to connect to read-write instances using `target_session_attrs` - @steve-chavez
- #3255, Fix incorrect `413 Request Entity Too Large` on pg errors `54*` - @taimoorzaeem
- #3549, Remove verbosity from error logs starting with "An error occurred..." and replacing it with "Failed to..." - @laurenceisla
### Deprecated
- Support for PostgreSQL versions 9.6, 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions. See https://www.postgresql.org/support/versioning/.
- `Prefer: params=single-object` is deprecated. Use [a function with a single unnamed JSON parameter](https://postgrest.org/en/latest/references/api/functions.html#function-single-json) instead. - @steve-chavez
### Documentation
- #3289, Add dark mode. Can be toggled by a button in the bottom right corner. - @laurenceisla
- #3384, Add architecture diagram and documentation - @steve-chavez
## [12.0.3] - 2024-05-09
### Fixed
- #3149, Misleading "Starting PostgREST.." logs on schema cache reloading - @steve-chavez
- #3205, Fix wrong subquery error returning a status of 400 Bad Request - @steve-chavez
- #3224, Return status code 406 for non-accepted media type instead of code 415 - @wolfgangwalther
- #3160, Fix using select= query parameter for custom media type handlers - @wolfgangwalther
- #3361, Clarify PGRST204(column not found) error message - @steve-chavez
- #3373, Remove rejected mediatype `application/vnd.pgrst.object+json` from response - @taimoorzaeem
- #3418, Fix OpenAPI not tagging a FK column correctly on O2O relationships - @laurenceisla
- #3256, Fix wrong http status for pg error `42P17 infinite recursion` - @taimoorzaeem
## [12.0.2] - 2023-12-20
### Fixed
- #3124, Fix table's media type handlers not working for all schemas - @steve-chavez
- #3126, Fix empty row on media type handler function - @steve-chavez
## [12.0.1] - 2023-12-12
### Fixed
- #3054, Fix not allowing special characters in JSON keys - @laurenceisla
- #2344, Replace JSON parser error with a clearer generic message - @develop7
- #3100, Add missing in-database configuration option for `jwt-cache-max-lifetime` - @laurenceisla
- #3089, The any media type handler now sets `Content-Type: application/octet-stream` by default instead of `Content-Type: application/json` - @steve-chavez
## [12.0.0] - 2023-12-01
### Added
- #1614, Add `db-pool-automatic-recovery` configuration to disable connection retrying - @taimoorzaeem
- #2492, Allow full response control when raising exceptions - @taimoorzaeem, @laurenceisla
- #2771, #2983, #3062, #3055 Add `Server-Timing` response header - @taimoorzaeem, @develop7, @laurenceisla
- #2698, Add config `jwt-cache-max-lifetime` and implement JWT caching - @taimoorzaeem
- #2943, Add `handling=strict/lenient` for Prefer header - @taimoorzaeem
- #2441, Add config `server-cors-allowed-origins` to specify CORS origins - @taimoorzaeem
- #2825, SQL handlers for custom media types - @steve-chavez
+ Solves #1548, #2699, #2763, #2170, #1462, #1102, #1374, #2901
- #2799, Add timezone in Prefer header - @taimoorzaeem
- #3001, Add `statement_timeout` set on functions - @taimoorzaeem
- #3045, Apply superuser settings on impersonated roles if they have PostgreSQL 15 `GRANT SET ON PARAMETER` privilege - @steve-chavez
- #915, Add support for aggregate functions - @timabdulla
+ The aggregate functions SUM(), MAX(), MIN(), AVG(), and COUNT() are now supported.
+ It's disabled by default, you can enable it with `db-aggregates-enabled`.
- #3057, Log all internal database errors to stderr - @laurenceisla
### Fixed
- #3015, Fix unnecessary count() on RPC returning single - @steve-chavez
- #1070, Fix HTTP status responses for upserts - @taimoorzaeem
+ `PUT` returns `201` instead of `200` when rows are inserted
+ `POST` with `Prefer: resolution=merge-duplicates` returns `200` instead of `201` when no rows are inserted
- #3019, Transaction-Scoped Settings are now shown clearly in the Postgres logs - @laurenceisla
+ Shows `set_config('pgrst.setting_name', $1)` instead of `setconfig($1, $2)`
+ Does not apply to role settings and `app.settings.*`
- #2420, Fix bogus message when listening on port 0 - @develop7
- #3067, Fix Acquision Timeout errors logging to stderr when `log-level=crit` - @laurenceisla
### Changed
- Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
- Removed [db-use-legacy-gucs](https://postgrest.org/en/v11.2/references/configuration.html#db-use-legacy-gucs) - @laurenceisla
+ All PostgreSQL versions now use GUCs in JSON format for [Headers, Cookies and JWT claims](https://postgrest.org/en/v12/references/transactions.html#request-headers-cookies-and-jwt-claims).
## [11.2.2] - 2023-10-25 ## [11.2.2] - 2023-10-25
### Fixed ### Fixed
@@ -586,11 +307,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #2312, Using `Prefer: return=representation` no longer returns a `Location` header - @laurenceisla - #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 - #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 + 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 ## [9.0.1] - 2022-06-03
+3
View File
@@ -0,0 +1,3 @@
This repository follows the same contribution guidelines as the main PostgREST repository contribution guidelines:
https://github.com/PostgREST/postgrest/blob/main/.github/CONTRIBUTING.md
-21
View File
@@ -1,21 +0,0 @@
# PostgREST Docker Hub image for aarch64.
# The x86-64 is a single-static-binary image built via Nix, see:
# nix/tools/docker/README.md
FROM ubuntu:noble@sha256:6015f66923d7afbc53558d7ccffd325d43b4e249f41a6e93eef074c9505d2233 AS postgrest
RUN apt-get update -y \
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY postgrest /usr/bin/postgrest
RUN chmod +x /usr/bin/postgrest
EXPOSE 3000
USER 1000
# Use the array form to avoid running the command using bash, which does not handle `SIGTERM` properly.
# See https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop
CMD ["postgrest"]
+15 -10
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-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest)
[![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org) [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org)
@@ -13,30 +13,30 @@ API than you are likely to write from scratch.
## Sponsors ## Sponsors
<table align="center"> <table>
<tbody> <tbody>
<tr> <tr>
<td align="center" valign="middle"> <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"> <a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/cybertec.svg"> <img width="222px" src="static/cybertec-new.png">
</a> </a>
</td> </td>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank"> <a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
<img width="296px" src="static/neon.jpg"> <img width="296px" src="static/2ndquadrant.png">
</a> </a>
</td> </td>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a href="https://code.build/?utm_source=sponsor&utm_campaign=postgrest" target="_blank"> <a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/code-build.png"> <img width="296px" src="static/retool.png">
</a> </a>
</td> </td>
</tr> </tr>
<tr></tr> <tr></tr>
<tr> <tr>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank"> <a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/tembo.png"> <img width="296px" src="static/gnuhost.png">
</a> </a>
</td> </td>
<td align="center" valign="middle"> <td align="center" valign="middle">
@@ -44,6 +44,11 @@ API than you are likely to write from scratch.
<img width="296px" src="static/supabase.png"> <img width="296px" src="static/supabase.png">
</a> </a>
</td> </td>
<td align="center" valign="middle">
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/oblivious.jpg">
</a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -61,7 +66,7 @@ Big thanks to our sponsors! You can join them by supporting PostgREST on [Patreo
``` ```
## [Documentation](http://postgrest.org) ## [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 ## Performance
-4
View File
@@ -1,4 +0,0 @@
packages: postgrest.cabal
tests: true
package *
ghc-options: -split-sections
-1
View File
@@ -1 +0,0 @@
index-state: hackage.haskell.org 2025-02-01T14:59: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
+81 -50
View File
@@ -1,33 +1,14 @@
{ system ? builtins.currentSystem { system ? builtins.currentSystem }:
, compiler ? "ghc948"
, # Commit of the Nixpkgs repository that we want to use.
# It defaults to reading the inputs from flake.lock, which serves
# as a compatibility layer for non-flake builds / default.nix / shell.nix.
nixpkgsVersion ? let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
{
inherit (lock.nodes.nixpkgs.locked) owner repo rev;
tarballHash = lock.nodes.nixpkgs.locked.narHash;
}
, # Nix files that describe the Nixpkgs repository. We evaluate the expression
# using `import` below.
nixpkgs ? let inherit (nixpkgsVersion) owner repo rev tarballHash; in
builtins.fetchTarball {
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
sha256 = tarballHash;
}
}:
let let
name = name =
"postgrest"; "postgrest";
compiler =
"ghc924";
# PostgREST source files, filtered based on the rules in the .gitignore files # PostgREST source files, filtered based on the rules in the .gitignore files
# and file extensions. We want to include as little as possible, as the files # and file extensions. We want to include as litte as possible, as the files
# added here will increase the space used in the Nix store and trigger the # added here will increase the space used in the Nix store and trigger the
# build of new Nix derivations when changed. # build of new Nix derivations when changed.
src = src =
@@ -35,6 +16,18 @@ let
(pkgs.gitignoreSource ./.) (pkgs.gitignoreSource ./.)
[ ".cabal" ".hs" ".lhs" "LICENSE" ]; [ ".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 = allOverlays =
import nix/overlays; import nix/overlays;
@@ -43,7 +36,10 @@ let
allOverlays.build-toolbox allOverlays.build-toolbox
allOverlays.checked-shell-script allOverlays.checked-shell-script
allOverlays.gitignore allOverlays.gitignore
allOverlays.postgresql-libpq allOverlays.postgis
(allOverlays.postgresql-default { inherit patches; })
allOverlays.postgresql-legacy
allOverlays.postgresql-future
(allOverlays.haskell-packages { inherit compiler; }) (allOverlays.haskell-packages { inherit compiler; })
allOverlays.slocat allOverlays.slocat
]; ];
@@ -54,28 +50,58 @@ let
postgresqlVersions = postgresqlVersions =
[ [
{ name = "postgresql-17"; postgresql = pkgs.postgresql_17.withPackages (p: [ p.postgis p.pg_safeupdate ]); } {
{ 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-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-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-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-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 ]); }
]; ];
# Dynamic derivation for PostgREST patches =
postgrest = pkgs.lib.pipe (pkgs.haskell.packages."${compiler}".callCabal2nix name src { }) [ pkgs.callPackage nix/patches { };
# To allow ghc-datasize to be used.
lib.disableLibraryProfiling
# We are never going to use dynamic haskell libraries anyway. "Dynamic" refers to how
# non-haskell deps are linked. All haskell dependencies are always statically linked.
lib.disableSharedLibraries
];
staticHaskellPackage = import nix/static.nix { inherit compiler name pkgs src; }; # Dynamic derivation for PostgREST
postgrest =
pkgs.haskell.packages."${compiler}".callCabal2nix name 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 # Options passed to cabal in dev tools and tests
devCabalOptions = devCabalOptions =
"-f dev --test-show-detail=direct --disable-shared"; "-f dev --test-show-detail=direct";
profiledHaskellPackages =
pkgs.haskell.packages."${compiler}".extend (self: super:
{
mkDerivation =
args:
super.mkDerivation (args // { enableLibraryProfiling = true; });
}
);
inherit (pkgs.haskell) lib; inherit (pkgs.haskell) lib;
in in
@@ -89,11 +115,12 @@ rec {
lib.dontCheck postgrest; lib.dontCheck postgrest;
# Profiled dynamic executable. # Profiled dynamic executable.
postgrestProfiled = pkgs.lib.pipe postgrestPackage [ postgrestProfiled =
lib.enableExecutableProfiling lib.enableExecutableProfiling (
lib.enableLibraryProfiling lib.dontHaddock (
lib.dontHaddock lib.dontCheck (profiledHaskellPackages.callCabal2nix name src { })
]; )
);
inherit (postgrest) env; inherit (postgrest) env;
@@ -109,23 +136,27 @@ rec {
pkgs.callPackage nix/tools/cabalTools.nix { inherit devCabalOptions postgrest; }; pkgs.callPackage nix/tools/cabalTools.nix { inherit devCabalOptions postgrest; };
withTools = withTools =
pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions postgrest; }; pkgs.callPackage nix/tools/withTools.nix { inherit cabalTools devCabalOptions postgresqlVersions postgrest; };
# Development tools. # Development tools.
devTools = devTools =
pkgs.callPackage nix/tools/devTools.nix { inherit tests style devCabalOptions hsie withTools; }; pkgs.callPackage nix/tools/devTools.nix { inherit tests style devCabalOptions hsie withTools; };
# Documentation tools.
docs =
pkgs.callPackage nix/tools/docs.nix { };
# Load testing tools. # Load testing tools.
loadtest = loadtest =
pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; }; pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; };
# Script for running memory tests.
memory =
pkgs.callPackage nix/tools/memory.nix { inherit postgrestProfiled withTools; };
# Utility for updating the pinned version of Nixpkgs.
nixpkgsTools =
pkgs.callPackage nix/tools/nixpkgsTools.nix { };
# Scripts for publishing new releases. # Scripts for publishing new releases.
release = release =
pkgs.callPackage nix/tools/release.nix { }; pkgs.callPackage nix/tools/release { };
# Linting and styling tools. # Linting and styling tools.
style = style =
@@ -141,8 +172,8 @@ rec {
}; };
} // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux rec { } // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux rec {
# Static executable. # Static executable.
inherit (staticHaskellPackage) postgrestStatic; inherit postgrestStatic;
inherit (staticHaskellPackage) packagesStatic; inherit packagesStatic;
# Docker images and loading script. # Docker images and loading script.
docker = docker =
+1 -1
View File
@@ -5,4 +5,4 @@ Pipfile.lock
_diagrams/db.pdf _diagrams/db.pdf
misspellings misspellings
unuseddict 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. 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 ## Documentation structure
This documentation is structured according to tutorials-howtos-topics-references. For more details on the rationale of this 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. 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.
+26 -8
View File
@@ -5,20 +5,38 @@ The ER diagrams were created with https://github.com/BurntSushi/erd/.
You can go download erd from https://github.com/BurntSushi/erd/releases and then do: You can go download erd from https://github.com/BurntSushi/erd/releases and then do:
```bash ```bash
./erd_static-x86-64 -i ./er/film.er -o ../_static/film.png ./erd_static-x86-64 -i film.er -o ../_static/film.png
``` ```
The fonts used belong to the GNU FreeFont family. You can download them here: http://ftp.gnu.org/gnu/freefont/ The fonts used belong to the GNU FreeFont family. You can download them here: http://ftp.gnu.org/gnu/freefont/
## UML ## LaTeX
The UML diagrams are created with https://plantuml.com/. The schema structure diagram is done with LaTeX. You can use a GUI like https://www.mathcha.io/editor to create the .tex file.
PlantUML only creates one diagram per file. Then use this command to generate the png file.
That's why we need to create another one for dark mode.
For example, for the file [uml/arch.uml](uml/arch.uml) there's [uml/dark/arch-dark.uml](uml/dark/arch-dark.uml) which includes the first one:
```bash ```bash
plantuml -tsvg uml/arch.uml -o ../../_static pdflatex --shell-escape -halt-on-error db.tex
plantuml -tsvg -darkmode uml/dark/arch-dark.uml -o ../../../_static
## and move it to the static folder(it's not easy to do it in one go with the pdflatex)
mv db.png ../_static/
```
LaTeX is used because it's a tweakable plain text format.
You can install the full latex suite with `nix`:
```
nix-env -iA texlive.combined.scheme-full
```
To tweak the file with a live reload environment use:
```bash
# open the pdf(zathura used as an example)
zathura db.pdf &
# live reload with entr
echo db.tex | entr pdflatex --shell-escape -halt-on-error db.tex
``` ```
+71
View File
@@ -0,0 +1,71 @@
\documentclass[convert]{standalone}
\usepackage{amsmath}
\usepackage{tikz}
\usepackage{mathdots}
\usepackage{yhmath}
\usepackage{cancel}
\usepackage{color}
\usepackage{siunitx}
\usepackage{array}
\usepackage{multirow}
\usepackage{amssymb}
\usepackage{gensymb}
\usepackage{tabularx}
\usepackage{booktabs}
\usetikzlibrary{fadings}
\usetikzlibrary{patterns}
\usetikzlibrary{shadows.blur}
\usetikzlibrary{shapes}
\begin{document}
\newcommand\customScale{0.35}
\begin{tikzpicture}[x=0.75pt,y=0.75pt,yscale=-1,xscale=1, scale=\customScale, every node/.style={scale=\customScale}]
%Shape: Can [id:dp7234864758664346]
\draw [fill={rgb, 255:red, 47; green, 97; blue, 144 } ,fill opacity=1 ] (497.5,51.5) -- (497.5,255.5) .. controls (497.5,275.66) and (423.18,292) .. (331.5,292) .. controls (239.82,292) and (165.5,275.66) .. (165.5,255.5) -- (165.5,51.5) .. controls (165.5,31.34) and (239.82,15) .. (331.5,15) .. controls (423.18,15) and (497.5,31.34) .. (497.5,51.5) .. controls (497.5,71.66) and (423.18,88) .. (331.5,88) .. controls (239.82,88) and (165.5,71.66) .. (165.5,51.5) ;
%Shape: Rectangle [id:dp7384065579958246]
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (189,115) -- (252.5,115) -- (252.5,155) -- (189,155) -- cycle ;
%Shape: Rectangle [id:dp24763906430298177]
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (292,118) -- (362,118) -- (362,158) -- (292,158) -- cycle ;
%Shape: Rectangle [id:dp3775601612537265]
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (397,114) -- (467,114) -- (467,154) -- (397,154) -- cycle ;
%Shape: Rectangle [id:dp7071457022893852]
\draw [fill={rgb, 255:red, 248; green, 231; blue, 28 } ,fill opacity=1 ] (269,199) -- (397.5,199) -- (397.5,273) -- (269,273) -- cycle ;
%Straight Lines [id:da8846759047437789]
\draw (268,234) -- (226.44,155.77) ;
\draw [shift={(225.5,154)}, rotate = 422.02] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
%Straight Lines [id:da6908444738113828]
\draw (309.5,198) -- (307.6,161) ;
\draw [shift={(307.5,159)}, rotate = 447.06] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
%Straight Lines [id:da7168757864413169]
\draw (398.5,233) -- (431.72,154.84) ;
\draw [shift={(432.5,153)}, rotate = 473.03] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
%Up Down Arrow [id:dp14059754167108496]
\draw [fill={rgb, 255:red, 126; green, 211; blue, 33 } ,fill opacity=1 ] (312.5,288.5) -- (330,273) -- (347.5,288.5) -- (338.75,288.5) -- (338.75,319.5) -- (347.5,319.5) -- (330,335) -- (312.5,319.5) -- (321.25,319.5) -- (321.25,288.5) -- cycle ;
% Text Node
\draw (201,129) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
% Text Node
\draw (307,130) node [anchor=north west][inner sep=0.75pt] [align=left ] {tables};
% Text Node
\draw (414,127) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
% Text Node
\draw (272,203) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 0; green, 0; blue, 0 } ,opacity=1 ] [align=center] { \\ views \\ + \\ \ \ stored procedures};
% Text Node
\draw (322,178) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{api}};
% Text Node
\draw (190,97) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{internal}};
% Text Node
\draw (300,99) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{private}};
% Text Node
\draw (417,101) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{core}};
% Text Node
\draw (358,306) node [anchor=north west][inner sep=0.75pt] [align=left] {REST};
\end{tikzpicture}
\end{document}
-91
View File
@@ -1,91 +0,0 @@
@startuml
skinparam backgroundColor transparent
package "PostgREST" {
() HTTP as HTTPAPI
HTTPAPI - [Auth]
[Auth] -r.> [ApiRequest]
[ApiRequest] -r.> [Plan]
[Plan] -r.> [Query]
[Query] - () "Connection Pool" : "\t"
[Plan] -u-> [Schema Cache]:uses
[Schema Cache] <- () Listener : reloads
() HTTP as HTTPADMIN
[Admin] -r- () HTTPADMIN
[Config] -l- () CLI
[Config] <-r~ Listener
HTTPADMIN -[hidden]r- CLI
[Schema Cache] -l[hidden]- [Config]
[Schema Cache] -l[hidden]- [Admin]
[Schema Cache] -l[hidden]- CLI
}
database "PostgreSQL" {
node Authorization {
rectangle "Roles, GRANT, RLS"
}
node "API schema" as API {
rectangle "Functions, Views"
}
rectangle "Tables, extensions" as tbs
API -d- tbs
API -l[hidden]- Authorization
}
:user:
hexagon Proxy
:user: .r-> Proxy
HTTPAPI <.l- Proxy
:operator: .d-> HTTPADMIN
:operator: .d-> CLI
PostgreSQL <.developer : "\t"
Listener -r.> "PostgreSQL"
"Connection Pool" -r.> "PostgreSQL" : "\t\t"
note bottom of Auth
Authenticates the user request
end note
note bottom of ApiRequest
Parses the URL syntax
end note
note bottom of Plan
Generates internal AST
end note
note bottom of Query
Generates the SQL
end note
note top of Listener
LISTEN session
end note
url of Admin is [[../references/admin_server.html#admin-server]]
url of API is [[../explanations/schema_isolation.html]]
url of Auth is [[../references/auth.html#authn]]
url of ApiRequest is [[../explanations/architecture.html#api-request]]
url of Plan is [[../explanations/architecture.html#plan]]
url of Query is [[../explanations/architecture.html#query]]
url of Authorization is [[../explanations/db_authz.html]]
url of CLI is [[../references/cli.html#cli]]
url of "Connection Pool" is [[../references/connection_pool.html]]
url of Config is [[../references/configuration.html#configuration]]
url of HTTPADMIN is [[../explanations/architecture.html#http]]
url of HTTPAPI is [[../explanations/architecture.html#http]]
url of Listener is [[../references/listener.html#listener]]
url of Proxy is [[../explanations/nginx.html]]
url of "Schema Cache" is [[../references/schema_cache.html#schema-cache]]
@enduml
-3
View File
@@ -1,3 +0,0 @@
@startuml
!include ../arch.uml
@enduml
-3
View File
@@ -1,3 +0,0 @@
@startuml
!include ../sch-iso.uml
@enduml
-29
View File
@@ -1,29 +0,0 @@
@startuml
skinparam backgroundColor transparent
skinparam linetype ortho
skinparam node {
backgroundColor transparent
borderThickness 1
}
database "PostgreSQL" {
node public {
rectangle tables_public as "tables"
}
node extensions as "**extensions**" {
}
node API as "<size:20>api" {
rectangle vf_api as "views + functions"
}
tables_public <-- vf_api
extensions <-- vf_api
}
vf_api <-[thickness=3]-> () PostgREST
@enduml
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

-1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

-1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

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

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 B

After

Width:  |  Height:  |  Size: 468 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

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

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="391px" preserveAspectRatio="none" style="width:328px;height:391px;" version="1.1" viewBox="0 0 328 391" width="328px" zoomAndPan="magnify"><defs/><g><!--cluster PostgreSQL--><g id="cluster_PostgreSQL"><path d="M6,16 C6,6 158.5,6 158.5,6 C158.5,6 311,6 311,16 L311,293.59 C311,303.59 158.5,303.59 158.5,303.59 C158.5,303.59 6,303.59 6,293.59 L6,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><path d="M6,16 C6,26 158.5,26 158.5,26 C158.5,26 311,26 311,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="92.2305" x="112.3848" y="40.9951">PostgreSQL</text></g><!--cluster public--><g id="cluster_public"><polygon fill="none" points="30,74,40,64,153,64,153,146.29,143,156.29,30,156.29,30,74" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="143" x2="153" y1="74" y2="64"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="30" x2="143" y1="74" y2="74"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="143" x2="143" y1="74" y2="156.29"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="47.9063" x="63.5469" y="89.9951">public</text></g><!--cluster API--><g id="cluster_API"><polygon fill="none" points="70,190.29,80,180.29,246,180.29,246,269.59,236,279.59,70,279.59,70,190.29" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="236" x2="246" y1="190.29" y2="180.29"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="70" x2="236" y1="190.29" y2="190.29"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="236" x2="236" y1="190.29" y2="279.59"/><text fill="#FFFFFF" font-family="sans-serif" font-size="20" font-weight="bold" lengthAdjust="spacing" textLength="34.668" x="136.666" y="211.8545">api</text></g><!--entity tables_public--><g id="elem_tables_public"><rect fill="#313139" height="36.2969" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="62.752" x="71.62" y="104"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="42.752" x="81.62" y="126.9951">tables</text></g><!--entity extensions--><g id="elem_extensions"><polygon fill="none" points="169.14,109,179.14,99,294.8695,99,294.8695,135.2969,284.8695,145.2969,169.14,145.2969,169.14,109" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="284.8695" x2="294.8695" y1="109" y2="99"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="169.14" x2="284.8695" y1="109" y2="109"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="284.8695" x2="284.8695" y1="109" y2="145.2969"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="85.7295" x="184.14" y="131.9951">extensions</text></g><!--entity vf_api--><g id="elem_vf_api"><rect fill="#313139" height="36.2969" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="144.6465" x="85.68" y="227.29"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="124.6465" x="95.68" y="250.2851">views + functions</text></g><!--entity PostgREST--><g id="elem_PostgREST"><ellipse cx="158" cy="352.59" fill="#313139" rx="8" ry="8" style="stroke:#E7E7E7;stroke-width:0.5;"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="74.6895" x="120.6553" y="382.5851">PostgREST</text></g><!--reverse link tables_public to vf_api--><g id="link_tables_public_vf_api"><path d="M110.03,146.6 C110.03,169.85 110.03,203.55 110.03,226.86 " fill="none" id="tables_public-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="110.03,140.6,106.03,149.6,110.03,145.6,114.03,149.6,110.03,140.6" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--reverse link extensions to vf_api--><g id="link_extensions_vf_api"><path d="M199.73,151.63 C199.73,175.24 199.73,205.18 199.73,226.88 " fill="none" id="extensions-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="199.73,145.63,195.73,154.63,199.73,150.63,203.73,154.63,199.73,145.63" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--link vf_api to PostgREST--><g id="link_vf_api_PostgREST"><path d="M158,269.62 C158,292.9 158,320.34 158,337.81 " fill="none" id="vf_api-PostgREST" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="158,263.62,154,272.62,158,268.62,162,272.62,158,263.62" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="158,343.81,162,334.81,158,338.81,154,334.81,158,343.81" style="stroke:#E7E7E7;stroke-width:3.0;"/></g><!--SRC=[KypCIyufJKbLqDFJBqxEqCqipjShpSq10000]--></g></svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

-1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.1 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: 89 KiB

+6 -40
View File
@@ -28,12 +28,7 @@ import os
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = ["sphinx_tabs.tabs", "sphinx_copybutton"]
"sphinx_tabs.tabs",
"sphinx_copybutton",
"sphinxext.opengraph",
"sphinx_rtd_dark_mode",
]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ["_templates"]
@@ -50,7 +45,7 @@ source_suffix = ".rst"
master_doc = "index" master_doc = "index"
# This is overriden by readthedocs with the version tag anyway # This is overriden by readthedocs with the version tag anyway
version = "13.0" version = "11.2"
# To avoid repetition in <title> we set this to an empty string. # To avoid repetition in <title> we set this to an empty string.
release = "" release = ""
@@ -64,7 +59,7 @@ copyright = "2017, " + author
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = "en" language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
@@ -75,7 +70,7 @@ language = "en"
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "shared/*.rst"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The reST default role (used for this markup: `text`) to use for all # The reST default role (used for this markup: `text`) to use for all
# documents. # documents.
@@ -114,7 +109,7 @@ html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
html_theme_options = {} html_theme_options = {"display_version": False}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = [] # html_theme_path = []
@@ -292,36 +287,7 @@ def setup(app):
app.add_css_file("css/custom.css") app.add_css_file("css/custom.css")
user_agent = ( user_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
)
linkcheck_ignore = [
# 403 only in CI / GitHub Actions
r"https://www.patreon.com/postgrest",
r"https://blog.frankel.ch/poor-man-api",
# Odd SSL error
r"https://www.dripdepot.com",
# New GitHub UI delays comment load, so anchor fails
r"https://github.com/.*#issuecomment",
# Random 500 Internal Server Error
r"https://jwt.io",
]
# sphinx-tabs configuration # sphinx-tabs configuration
sphinx_tabs_disable_tab_closing = True sphinx_tabs_disable_tab_closing = True
# sphinx_rtd_dark_mode configuration
default_dark_mode = False
# sphinxext-opengraph configuration
ogp_image = "_images/logo.png"
ogp_use_first_image = True
ogp_enable_meta_description = True
ogp_description_length = 300
## RTD sets html_baseurl, ensures we use the correct env for canonical URLs
## Useful to generate correct meta tags for Open Graph
## Refs: https://github.com/readthedocs/readthedocs.org/issues/10226, https://github.com/urllib3/urllib3/pull/3064
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
+93
View File
@@ -0,0 +1,93 @@
let
# Commit of the Nixpkgs repository that we want to use.
nixpkgsVersion = {
date = "2021-06-02";
rev = "84aa23742f6c72501f9cc209f29c438766f5352d";
tarballHash = "0h7xl6q0yjrbl9vm3h6lkxw692nm8bg3wy65gm95a2mivhrdjpxp";
};
# Nix files that describe the Nixpkgs repository. We evaluate the expression
# using `import` below.
pkgs = import
(fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
sha256 = nixpkgsVersion.tarballHash;
})
{ };
sphinxTabsPkg = ps: ps.callPackage ./extensions/sphinx-tabs.nix { };
sphinxCopybuttonPkg = ps: ps.callPackage ./extensions/sphinx-copybutton.nix { };
python = pkgs.python3.withPackages (ps: [ ps.sphinx ps.sphinx_rtd_theme ps.livereload (sphinxTabsPkg ps) (sphinxCopybuttonPkg ps) ]);
in
{
inherit pkgs;
build =
pkgs.writeShellScriptBin "postgrest-docs-build"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
# clean previous build, otherwise some errors might be supressed
rm -rf _build
${python}/bin/sphinx-build --color -W -b html -a -n . _build
'';
serve =
pkgs.writeShellScriptBin "postgrest-docs-serve"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
# livereload_docs.py needs to find "sphinx-build"
PATH=${python}/bin:$PATH
${python}/bin/python livereload_docs.py
'';
spellcheck =
pkgs.writeShellScriptBin "postgrest-docs-spellcheck"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
FILES=$(find . -type f -iname '*.rst' | tr '\n' ' ')
cat $FILES \
| grep -v '^\(\.\.\| \)' \
| sed 's/`.*`//g' \
| ${pkgs.aspell}/bin/aspell -d ${pkgs.aspellDicts.en}/lib/aspell/en_US -p ./postgrest.dict list \
| sort -f \
| tee misspellings
test ! -s misspellings
'';
# dictcheck detects obsolete entries in postgrest.dict, that are not used anymore
dictcheck =
pkgs.writeShellScriptBin "postgrest-docs-dictcheck"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
FILES=$(find . -type f -iname '*.rst' | tr '\n' ' ')
cat postgrest.dict \
| tail -n+2 \
| tr '\n' '\0' \
| xargs -0 -n 1 -i \
sh -c "grep \"{}\" $FILES > /dev/null || echo \"{}\"" \
| 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
'';
}
+9 -10
View File
@@ -6,7 +6,10 @@ Community Tutorials
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ - * `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet. In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
* `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service. * `PostgREST + Auth0: Create REST API in minutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest-1-introduction/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
* `PostgREST + PostGIS API tutorial in 5 minutes <https://gis-ops.com/postgrest-postgis-api-tutorial-geospatial-api-in-5-minutes/>`_ -
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`s_procs` interface.
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ - * `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service). A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
@@ -19,8 +22,6 @@ Community Tutorials
* `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. * `A poor man's API <https://blog.frankel.ch/poor-man-api>`_ - Shows how to integrate PostgREST with Apache APISIX as an alternative to Nginx.
.. * `Accessing a PostgreSQL database in Godot 4 via PostgREST <https://peterkingsbury.com/2022/08/16/godot-postgresql-postgrest/>`_
.. _templates: .. _templates:
Templates Templates
@@ -34,7 +35,6 @@ Templates
Example Apps Example Apps
------------ ------------
* `archtika <https://github.com/thiloho/archtika>`_ - selfhosted CMS
* `delibrium-postgrest <https://gitlab.com/delibrium/delibrium-postgrest/>`_ - example school API and front-end in Vue.js * `delibrium-postgrest <https://gitlab.com/delibrium/delibrium-postgrest/>`_ - example school API and front-end in Vue.js
* `ETH-transactions-storage <https://github.com/Adamant-im/ETH-transactions-storage>`_ - indexer for Ethereum to get transaction list by ETH address * `ETH-transactions-storage <https://github.com/Adamant-im/ETH-transactions-storage>`_ - indexer for Ethereum to get transaction list by ETH address
* `general <https://github.com/PierreRochard/general>`_ - example auth back-end * `general <https://github.com/PierreRochard/general>`_ - example auth back-end
@@ -49,18 +49,17 @@ DevOps
* `cloudgov-demo-postgrest <https://github.com/GSA/cloudgov-demo-postgrest>`_ - demo for a federally-compliant REST API on cloud.gov * `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 * `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 * `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: .. _eco_external_notification:
External Notification External Notification
--------------------- ---------------------
These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows functions to initiate actions outside the database such as sending emails. These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows stored procedures to initiate actions outside the database such as sending emails.
* `pg-notify-stdout <https://github.com/mkleczek/pg-notify-stdout>`_ - writes notifications to standard output (use in shell scripts etc.)
* `pg-notify-webhook <https://github.com/vbalasu/pg-notify-webhook>`_ - trigger webhooks from PostgreSQL's LISTEN/NOTIFY * `pg-notify-webhook <https://github.com/vbalasu/pg-notify-webhook>`_ - trigger webhooks from PostgreSQL's LISTEN/NOTIFY
* `pgsql-listen-exchange <https://github.com/gmr/pgsql-listen-exchange>`_ - RabbitMQ * `pgsql-listen-exchange <https://github.com/gmr/pgsql-listen-exchange>`_ - RabbitMQ
* `postgres-websockets <https://github.com/diogob/postgres-websockets>`_ - expose web sockets for PostgreSQL's LISTEN/NOTIFY * `postgres-websockets <https://github.com/diogob/postgres-websockets>`_ - expose web sockets for PostgreSQL's LISTEN/NOTIFY
@@ -83,8 +82,8 @@ Client-Side Libraries
--------------------- ---------------------
* `postgrest-csharp <https://github.com/supabase-community/postgrest-csharp>`_ - C# * `postgrest-csharp <https://github.com/supabase-community/postgrest-csharp>`_ - C#
* `postgrest-dart <https://github.com/supabase/postgrest-dart>`_ - Dart * `postgrest-dart <https://github.com/supabase-community/postgrest-dart>`_ - Dart
* `postgrest-ex <https://github.com/supabase-community/postgrest-ex>`_ - Elixir * `postgrest-ex <https://github.com/J0/postgrest-ex>`_ - Elixir
* `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go * `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go
* `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript * `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript
* `postgrest-kt <https://github.com/supabase-community/postgrest-kt>`_ - Kotlin * `postgrest-kt <https://github.com/supabase-community/postgrest-kt>`_ - Kotlin
-95
View File
@@ -1,95 +0,0 @@
Architecture
############
This page describes the architecture of PostgREST.
Bird's Eye View
===============
You can click on the components to navigate to their respective documentation.
.. container:: img-dark
.. See https://github.com/sphinx-doc/sphinx/issues/2240#issuecomment-187366626
.. raw:: html
<object width="100%" data="../_static/arch-dark.svg" type="image/svg+xml"></object>
.. container:: img-light
.. raw:: html
<object width="100%" data="../_static/arch.svg" type="image/svg+xml"></object>
Code Map
========
This section talks briefly about various important modules.
Main
----
The starting point of the program is `Main.hs <https://github.com/PostgREST/postgrest/blob/main/main/Main.hs>`_.
CLI
---
Main then calls `CLI.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/CLI.hs>`_, which is in charge of :ref:`cli`.
App
---
`App.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/App.hs>`_ is then in charge of composing the different modules.
Auth
----
`Auth.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Auth.hs>`_ is in charge of :ref:`authn`.
Api Request
-----------
`ApiRequest.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/ApiRequest.hs>`_ is in charge of parsing the URL query string (following PostgREST syntax), the request headers, and the request body.
A request might be rejected at this level if it's invalid. For example when providing an unknown media type to PostgREST or using an unknown HTTP method.
Plan
----
Using the Schema Cache, `Plan.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Plan.hs>`_ generates an internal AST, filling out-of-band SQL details (like an ``ON CONFLICT (pk)`` clause) required to complete the user request.
A request might be rejected at this level if it's invalid. For example when doing resource embedding on a nonexistent resource.
Query
-----
`Query.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Query.hs>`_ generates the SQL queries (parametrized and prepared) required to satisfy the user request.
Only at this stage a connection from the pool might be used.
Schema Cache
------------
`SchemaCache.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/SchemaCache.hs>`_ is in charge of :ref:`schema_cache`.
Config
------
`Config.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Config.hs>`_ is in charge of :ref:`configuration`.
Admin
-----
`Admin.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Admin.hs>`_ is in charge of the :ref:`admin_server`.
HTTP
----
The HTTP server is provided by `Warp <https://aosabook.org/en/posa/warp.html>`_.
Listener
--------
`Listener.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Listener.hs>`_ is in charge of the :ref:`listener`.
+13 -4
View File
@@ -92,10 +92,19 @@ You can mix the group and individual role policies. For instance we could still
-- allow authenticator to switch into user000 role -- allow authenticator to switch into user000 role
-- (the role itself has nologin) -- (the role itself has nologin)
.. _schema_isolation:
Schemas Schemas
======= =======
You must explicitly allow roles to access the exposed schemas in :ref:`db-schemas`. A PostgREST instance exposes all the tables, views, and stored procedures of the schemas configured in :ref:`db-schemas`. This means private data or implementation details can go inside private schemas and be invisible to HTTP clients.
It is recommended that you don't expose tables on the schemas you expose, 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.
.. image:: ../_static/db.png
You must explicitly allow roles to access the exposed schemas:
.. code-block:: postgres .. code-block:: postgres
@@ -156,7 +165,7 @@ You can also grant execute on all functions in a schema to a higher privileged r
Security definer 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. 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. 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 +175,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 -- login as a user wich has privileges on the private schemas
-- create a sample function -- 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 begin
-- access to a private schema called 'auth' -- access to a private schema called 'auth'
select auth.user_role(email, pass) into _role; select auth.user_role(email, pass) into _role;
@@ -180,7 +189,7 @@ Note the ``SECURITY DEFINER`` keywords at the end of the function. See `PostgreS
Views 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. If you're on PostgreSQL >= 15, this behavior can be changed by specifying the ``security_invoker`` option.
-11
View File
@@ -1,11 +0,0 @@
.. _external_auth:
External Authentication
-----------------------
JWT from Auth0
~~~~~~~~~~~~~~
An external service like `Auth0 <https://auth0.com/>`_ can do the hard work transforming OAuth from Github, Twitter, Google etc into a JWT suitable for PostgREST. Auth0 can also handle email signup and password reset flows.
To use Auth0, create `an application <https://auth0.com/docs/get-started/applications>`_ for your app and `an API <https://auth0.com/docs/get-started/apis>`_ for your PostgREST server. Auth0 supports both HS256 and RS256 scheme for the issued tokens for APIs. For simplicity, you may first try HS256 scheme while creating your API on Auth0. Your application should use your PostgREST API's `API identifier <https://auth0.com/docs/get-started/apis/api-settings>`_ by setting it with the `audience parameter <https://auth0.com/docs/secure/tokens/access-tokens/get-access-tokens#control-access-token-audience>`_ during the authorization request. This will ensure that Auth0 will issue an access token for your PostgREST API. For PostgREST to verify the access token, you will need to set ``jwt-secret`` on PostgREST config file with your API's signing secret.
+63 -15
View File
@@ -3,12 +3,58 @@
Installation Installation
############ ############
The release page has `pre-compiled binaries for macOS, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_. The release page has `pre-compiled binaries for Mac OS X, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_ .
The Linux binary is a static executable that can be run on any Linux distribution. The Linux binary is a static executable that can be run on any Linux distribution.
You can also use your OS package manager. You can also use your OS package manager.
.. include:: ../shared/installation.rst .. tabs::
.. group-tab:: Mac OSX
You can install PostgREST from the `Homebrew official repo <https://formulae.brew.sh/formula/postgrest>`_.
.. code:: bash
brew install postgrest
.. group-tab:: FreeBSD
You can install PostgREST from the `official ports <https://www.freshports.org/www/hs-postgrest>`_.
.. code:: bash
pkg install hs-postgrest
.. group-tab:: Linux
.. tabs::
.. tab:: Arch Linux
You can install PostgREST from the `community repo <https://archlinux.org/packages/extra/x86_64/postgrest/>`_.
.. code:: bash
pacman -S postgrest
.. tab:: Nix
You can install PostgREST from nixpkgs.
.. code:: bash
nix-env -i haskellPackages.postgrest
.. group-tab:: Windows
You can install PostgREST using `Chocolatey <https://community.chocolatey.org/packages/postgrest>`_ or `Scoop <https://github.com/ScoopInstaller/Scoop>`_.
.. code:: bash
choco install postgrest
scoop install postgrest
.. _pg-dependency: .. _pg-dependency:
@@ -16,11 +62,10 @@ Supported PostgreSQL versions
============================= =============================
=============== ================================= =============== =================================
**Supported** PostgreSQL >= 12 **Supported** PostgreSQL >= 9.6
=============== ================================= =============== =================================
PostgREST works with all PostgreSQL versions still `officially supported <https://www.postgresql.org/support/versioning/>`_. PostgREST works with all PostgreSQL versions starting from 9.6.
Running PostgREST Running PostgREST
================= =================
@@ -164,15 +209,14 @@ If you want to have a visual overview of your API in your browser you can add sw
.. code-block:: yaml .. code-block:: yaml
# in services: swagger:
swagger: image: swaggerapi/swagger-ui
image: swaggerapi/swagger-ui ports:
ports: - "8080:8080"
- "8080:8080" expose:
expose: - "8080"
- "8080" environment:
environment: API_URL: http://localhost:3000/
API_URL: http://localhost:3000/
With this you can see the swagger-ui in your browser on port 8080. With this you can see the swagger-ui in your browser on port 8080.
@@ -183,6 +227,10 @@ Building from Source
When a pre-built binary does not exist for your system you can build the project 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. You can build PostgREST from source with `Stack <https://github.com/commercialhaskell/stack>`_. It will install any necessary Haskell dependencies on your system.
* `Install Stack <https://docs.haskellstack.org/en/stable/#how-to-install-stack>`_ for your platform * `Install Stack <https://docs.haskellstack.org/en/stable/#how-to-install-stack>`_ for your platform
@@ -194,7 +242,7 @@ You can build PostgREST from source with `Stack <https://github.com/commercialha
Ubuntu/Debian libpq-dev, libgmp-dev, zlib1g-dev Ubuntu/Debian libpq-dev, libgmp-dev, zlib1g-dev
CentOS/Fedora/Red Hat postgresql-devel, zlib-devel, gmp-devel CentOS/Fedora/Red Hat postgresql-devel, zlib-devel, gmp-devel
BSD postgresql12-client BSD postgresql12-client
macOS libpq, gmp OS X libpq, gmp
===================== ======================================= ===================== =======================================
* Build and install binary * Build and install binary
+12 -5
View File
@@ -42,7 +42,7 @@ The first step is to create an Nginx configuration file that proxies requests to
HTTPS HTTPS
----- -----
PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how <https://nginx.org/en/docs/http/configuring_https_servers.html>`_. 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 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`). 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 .. 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 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" \ .. code-tab:: http
-H "Accept: application/vnd.pgrst.object+json"
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. This allows compound primary keys and makes the intent for singular response independent of a URL convention.
-25
View File
@@ -1,25 +0,0 @@
.. _schema_isolation:
Schema Isolation
================
A PostgREST instance exposes all the tables, views, and functions of a single `PostgreSQL schema <https://www.postgresql.org/docs/current/ddl-schemas.html>`_ (a namespace of database objects). This means private data or implementation details can go inside different private schemas and be invisible to HTTP clients.
It is recommended that you don't expose tables on your API schema. Instead expose views and functions which insulate the internal details from the outside world.
This allows you to change the internals of your schema and maintain backwards compatibility. It also keeps your code easier to refactor, and provides a natural way to do API versioning.
.. container:: svg-container-md
.. container:: img-dark
.. See https://github.com/sphinx-doc/sphinx/issues/2240#issuecomment-187366626
.. raw:: html
<object width="100%" data="../_static/sch-iso-dark.svg" type="image/svg+xml"></object>
.. container:: img-light
.. raw:: html
<object width="100%" data="../_static/sch-iso.svg" type="image/svg+xml"></object>
+33
View File
@@ -0,0 +1,33 @@
{ lib
, buildPythonPackage
, fetchFromGitHub
, sphinx
}:
buildPythonPackage rec {
pname = "sphinx-copybutton";
version = "0.4.0";
src = fetchFromGitHub {
owner = "executablebooks";
repo = "sphinx-copybutton";
rev = "v${version}";
sha256 = "sha256-vrEIvQeP7AMXSme1PBp0ox5k8Q1rz+1cbHIO+o17Jqc=";
fetchSubmodules = true;
};
propagatedBuildInputs = [
sphinx
];
doCheck = false; # no tests
pythonImportsCheck = [ "sphinx_copybutton" ];
meta = with lib; {
description = "A small sphinx extension to add a \"copy\" button to code blocks";
homepage = "https://github.com/executablebooks/sphinx-copybutton";
license = licenses.mit;
maintainers = with maintainers; [ Luflosi ];
};
}
+29
View File
@@ -0,0 +1,29 @@
{ lib
, buildPythonPackage
, fetchPypi
, sphinx
}:
buildPythonPackage rec {
pname = "sphinx-tabs";
version = "3.2.0";
src = fetchPypi {
inherit pname version;
sha256 = "sha256:1970aahi6sa7c37cpz8nwgdb2xzf21rk6ykdd1m6w9wvxla7j4rk";
};
propagatedBuildInputs = [
sphinx
];
doCheck = false;
pythonImportsCheck = [ "sphinx_tabs" ];
meta = with lib; {
description = "Create tabbed content in Sphinx documentation when building HTML";
homepage = "https://sphinx-tabs.readthedocs.io";
license = licenses.mit;
};
}
+33 -12
View File
@@ -5,20 +5,22 @@ Create a SOAP endpoint
:author: `fjf2002 <https://github.com/fjf2002>`_ :author: `fjf2002 <https://github.com/fjf2002>`_
PostgREST supports :ref:`custom_media`. With a bit of work, SOAP endpoints become possible. PostgREST now has XML support. With a bit of work, SOAP endpoints become possible.
Please note that PostgREST supports just ``text/xml`` MIME type in request/response headers ``Content-Type`` and ``Accept``.
If you have to use other MIME types such as ``application/soap+xml``, you could manipulate the headers in your reverse proxy.
Minimal Example Minimal Example
--------------- ---------------
This example will simply return the request body, inside a tag ``therequestbodywas``. This example will simply return the request body, inside a tag ``therequestbodywas``.
Add the following function to your PostgreSQL database: Add the following function to your PostgreSQL database:
.. code-block:: postgres .. code-block:: postgres
create domain "text/xml" as pg_catalog.xml; CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS xml AS $$
CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS "text/xml" AS $$
DECLARE DECLARE
nsarray CONSTANT text[][] := ARRAY[ nsarray CONSTANT text[][] := ARRAY[
ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/'] ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/']
@@ -77,6 +79,25 @@ and should roughly look like:
</soapenv:Body> </soapenv:Body>
</soapenv:Envelope> </soapenv:Envelope>
Unfortunately the ``Accept: text/xml`` header is currently mandatory concerning PostgREST, otherwise it will respond
with a ``Content-Type: application/json`` header and enclose the response with quotes.
(You can check the returned headers by adding ``-v`` to the curl call.)
If your SOAP clients do not send the ``Accept: text/xml`` header, you can fix that in your nginx reverse proxy
by adding something like ...
.. code-block:: nginx
set $accept $http_accept;
if ($contentType ~ "^text/xml($|;)") {
set $accept "text/xml";
}
proxy_set_header Accept $accept;
to your ``location`` nginx configuration.
(The given example sets the ``Accept`` header for each request of Content-Type ``text/xml``.)
A more elaborate example A more elaborate example
------------------------ ------------------------
@@ -100,7 +121,7 @@ potentially disclosing internals to the client, but instead handle the errors di
xmlelement(NAME "soapenv:Body", body) xmlelement(NAME "soapenv:Body", body)
); );
$function$; $function$;
-- helper function -- helper function
CREATE OR REPLACE FUNCTION _soap_exception( CREATE OR REPLACE FUNCTION _soap_exception(
faultcode text, faultcode text,
@@ -116,9 +137,9 @@ potentially disclosing internals to the client, but instead handle the errors di
) )
); );
$function$; $function$;
CREATE OR REPLACE FUNCTION fraction_to_decimal(xml) CREATE OR REPLACE FUNCTION fraction_to_decimal(xml)
RETURNS "text/xml" RETURNS xml
LANGUAGE plpgsql LANGUAGE plpgsql
AS $function$ AS $function$
DECLARE DECLARE
@@ -186,14 +207,14 @@ The output should roughly look like:
</soapenv:Body> </soapenv:Body>
</soapenv:Envelope> </soapenv:Envelope>
References References
---------- ----------
For more information concerning PostgREST, cf. 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:`scalar_return_formats`
- :ref:`Nginx reverse proxy <nginx>` - :ref:`Nginx reverse proxy <admin>`
For SOAP reference, visit For SOAP reference, visit
@@ -1,326 +0,0 @@
.. _providing_html_htmx:
Providing HTML Content Using Htmx
=================================
:author: `Laurence Isla <https://github.com/laurenceisla>`_
This how-to shows a way to return HTML content and use the `htmx library <https://htmx.org/>`_ to handle the AJAX requests.
Htmx expects an HTML response and uses it to replace an element inside the DOM (see the `htmx introduction <https://htmx.org/docs/#introduction>`_ in the docs).
.. image:: ../_static/how-tos/htmx-demo.gif
.. warning::
This is a proof of concept showing what can be achieved using both technologies.
We are working on `plmustache <https://github.com/PostgREST/plmustache>`_ which will further improve the HTML aspect of this how-to.
Preparatory Configuration
-------------------------
We will make a to-do app based on the :ref:`tut0`, so make sure to complete it before continuing.
To simplify things, we won't be using authentication, so grant all permissions on the ``todos`` table to the ``web_anon`` user.
.. code-block:: postgres
grant all on api.todos to web_anon;
grant usage, select on sequence api.todos_id_seq to web_anon;
Next, add the ``text/html`` as a :ref:`custom_media`. With this, PostgREST can identify the request made by your web browser (with the ``Accept: text/html`` header)
and return a raw HTML document file.
.. code-block:: postgres
create domain "text/html" as text;
Creating an HTML Response
-------------------------
Let's create a function that returns a basic HTML file, using `Pico CSS <https://picocss.com>`_ for styling and
`Ionicons <https://ionic.io/ionicons>`_ to show some icons later.
.. code-block:: postgres
create or replace function api.index() returns "text/html" as $$
select $html$
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title>
<!-- Pico CSS for CSS styling -->
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet" />
</head>
<body>
<main class="container">
<article>
<h5 style="text-align: center;">
PostgREST + HTMX To-Do List
</h5>
</article>
</main>
<!-- Script for Ionicons icons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</body>
</html>
$html$;
$$ language sql;
The web browser will open the web page at ``http://localhost:3000/rpc/index``.
.. image:: ../_static/how-tos/htmx-simple.jpg
.. _html_htmx_list_create:
Listing and Creating To-Dos
---------------------------
Now, let's show a list of the to-dos already inserted in the database.
For that, we'll also need a function to help us sanitize the HTML content that may be present in the task.
.. code-block:: postgres
create or replace function api.sanitize_html(text) returns text as $$
select replace(replace(replace(replace(replace($1, '&', '&amp;'), '"', '&quot;'),'>', '&gt;'),'<', '&lt;'), '''', '&apos;')
$$ language sql;
create or replace function api.html_todo(api.todos) returns text as $$
select format($html$
<div>
<%2$s>
%3$s
</%2$s>
</div>
$html$,
$1.id,
case when $1.done then 's' else 'span' end,
api.sanitize_html($1.task)
);
$$ language sql stable;
create or replace function api.html_all_todos() returns text as $$
select coalesce(
string_agg(api.html_todo(t), '<hr/>' order by t.id),
'<p><em>There is nothing else to do.</em></p>'
)
from api.todos t;
$$ language sql;
These two functions are used to build the to-do list template. We won't use them as PostgREST endpoints.
- The ``api.html_todo`` function uses the table ``api.todos`` as a parameter and formats each item into a list element ``<li>``.
The PostgreSQL `format <https://www.postgresql.org/docs/current/functions-string.html#FUNCTIONS-STRING-FORMAT>`_ is useful to that end.
It replaces the values according to the position in the template, e.g. ``%1$s`` will be replaced with the value of ``$1.id`` (the first parameter).
- The ``api.html_all_todos`` function returns the ``<ul>`` wrapper for all the list elements.
It uses `string_arg <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ to concatenate all the to-dos in a single text value.
It also returns an alternative message, instead of a list, when the ``api.todos`` table is empty.
Next, let's add an endpoint to register a to-do in the database and modify the ``/rpc/index`` page accordingly.
.. code-block:: postgres
create or replace function api.add_todo(_task text) returns "text/html" as $$
insert into api.todos(task) values (_task);
select api.html_all_todos();
$$ language sql;
create or replace function api.index() returns "text/html" as $$
select $html$
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title>
<!-- Pico CSS for CSS styling -->
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet"/>
<!-- htmx for AJAX requests -->
<script src="https://unpkg.com/htmx.org"></script>
</head>
<body>
<main class="container"
style="max-width: 600px"
hx-headers='{"Accept": "text/html"}'>
<article>
<h5 style="text-align: center;">
PostgREST + HTMX To-Do List
</h5>
<form hx-post="/rpc/add_todo"
hx-target="#todo-list-area"
hx-trigger="submit"
hx-on="htmx:afterRequest: this.reset()">
<input type="text" name="_task" placeholder="Add a todo...">
</form>
<div id="todo-list-area">
$html$
|| api.html_all_todos() ||
$html$
<div>
</article>
</main>
<!-- Script for Ionicons icons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</body>
</html>
$html$;
$$ language sql;
- The ``/rpc/add_todo`` endpoint allows us to add a new to-do using the ``_task`` parameter and returns an ``html`` with all the to-dos in the database.
- The ``/rpc/index`` now adds the ``hx-headers='{"Accept": "text/html"}'`` tag to the ``<body>``.
This will make sure that all htmx elements inside the body send this header, otherwise PostgREST won't recognize it as HTML.
There is also a ``<form>`` element that uses the htmx library. Let's break it down:
+ ``hx-post="/rpc/add_todo"``: sends an AJAX POST request to the ``/rpc/add_todo`` endpoint, with the value of the ``_task`` from the ``<input>`` element.
+ ``hx-target="#todo-list-area"``: the HTML content returned from the request will go inside ``<div id="todo-list-area"></div>`` (which is the list of to-dos).
+ ``hx-trigger="submit"``: htmx will do this request when submitting the form (by pressing enter while inside the ``<input>``).
+ ``hx-on="htmx:afterRequest: this.reset()">``: this is a Javascript command that clears the form `after the request is done <https://htmx.org/events/#htmx:afterRequest>`_.
With this, the ``http://localhost:3000/rpc/index`` page lists all the todos and adds new ones by submitting tasks in the input element.
Don't forget to refresh the :ref:`schema cache <schema_reloading>`.
.. image:: ../_static/how-tos/htmx-insert.gif
Editing and Deleting To-Dos
---------------------------
Now, let's modify ``api.html_todo`` and make it more functional.
.. code-block:: postgres
create or replace function api.html_todo(api.todos) returns text as $$
select format($html$
<div class="grid">
<div id="todo-edit-area-%1$s">
<form id="edit-task-state-%1$s"
hx-post="/rpc/change_todo_state"
hx-vals='{"_id": %1$s, "_done": %4$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<%2$s style="cursor: pointer">
%3$s
</%2$s>
</form>
</div>
<div style="text-align: right">
<button class="outline"
hx-get="/rpc/html_editable_task"
hx-vals='{"_id": "%1$s"}'
hx-target="#todo-edit-area-%1$s"
hx-trigger="click">
<span>
<ion-icon name="create"></ion-icon>
</span>
</button>
<button class="outline contrast"
hx-post="/rpc/delete_todo"
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<span>
<ion-icon name="trash" style="color: #f87171"></ion-icon>
</span>
</button>
</div>
</div>
$html$,
$1.id,
case when $1.done then 's' else 'span' end,
api.sanitize_html($1.task),
(not $1.done)::text
);
$$ language sql stable;
Let's deconstruct the new htmx features added:
- The ``<form>`` element is configured as follows:
+ ``hx-post="/rpc/change_todo_state"``: does an AJAX POST request to that endpoint. It will toggle the ``done`` state of the to-do.
+ ``hx-vals='{"_id": %1$s, "_done": %4$s}'``: adds the parameters to the request.
This is an alternative to using hidden inputs inside the ``<form>``.
+ ``hx-trigger="click"``: htmx does the request after clicking on the element.
- For the first ``<button>``:
+ ``hx-get="/rpc/html_editable_task"``: it does an AJAX GET request to that endpoint.
It returns an HTML with an input that will allow us to edit the task.
+ ``hx-target="#todo-edit-area"``: the returned HTML will replace the element with this id.
In this case, this replaces an individual task, not the whole list.
+ ``hx-vals='{"id": "eq.%1$s"}'``: adds the query parameters to the GET request.
Note that this needs the ``eq.`` operator because it represents a table column not a function parameter.
- For the second ``<button>``:
+ ``hx-post="/rpc/delete_todo"``: this post request will delete the corresponding to-do.
Clicking on the first button will enable the task editing.
That's why we create the ``api.html_editable_task`` function as an endpoint:
.. code-block:: postgres
create or replace function api.html_editable_task(_id int) returns "text/html" as $$
select format ($html$
<form id="edit-task-%1$s"
hx-post="/rpc/change_todo_task"
hx-headers='{"Accept": "text/html"}'
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="submit,focusout">
<input id="task-%1$s" type="text" name="_task" value="%2$s" autofocus>
</form>
$html$,
id,
api.sanitize_html(task)
)
from api.todos
where id = _id;
$$ language sql;
In this example, this will return an input field that allows us to edit the corresponding to-do task.
Finally, let's add the endpoints that will modify and delete the to-dos in the database.
.. code-block:: postgres
create or replace function api.change_todo_state(_id int, _done boolean) returns "text/html" as $$
update api.todos set done = _done where id = _id;
select api.html_all_todos();
$$ language sql;
create or replace function api.change_todo_task(_id int, _task text) returns "text/html" as $$
update api.todos set task = _task where id = _id;
select api.html_all_todos();
$$ language sql;
create or replace function api.delete_todo(_id int) returns "text/html" as $$
delete from api.todos where id = _id;
select api.html_all_todos();
$$ language sql;
All of those functions return an HTML list of to-dos that will replace the outdated one:
- The ``api.change_todo_state`` function updates the ``done`` column using the ``_id`` and the ``_done`` values from the request.
- The ``api.delete_todo`` function deletes a to-do using the ``_id`` value from the request.
- The ``api.change_todo_task`` function modifies the ``task`` column using the ``_id`` and the ``_task`` value from the request.
After refreshing the :ref:`schema cache <schema_reloading>`, the page at ``http://localhost:3000/rpc/index`` will allow us to edit, delete and complete any to-do.
.. image:: ../_static/how-tos/htmx-edit-delete.gif
With that, we completed the to-do list functionality.
+12 -40
View File
@@ -26,42 +26,18 @@ First, we need a public table for storing the files.
, blob bytea , blob bytea
); );
Let's assume this table contains an image of two cute kittens with id 42. We can retrieve this image in binary format from our PostgREST API by using :ref:`custom_media`: Let's assume this table contains an image of two cute kittens with id 42.
We can retrieve this image in binary format from our PostgREST API by requesting :code:`/files?select=blob&id=eq.42` with the :code:`Accept: application/octet-stream` header.
.. code-block:: postgres Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work.
That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
create domain "application/octet-stream" as bytea;
create or replace function file(id int) returns "application/octet-stream" as $$
select blob from files where id = file.id;
$$ language sql;
Now we can request the RPC endpoint :code:`/rpc/file?id=42` with the :code:`Accept: application/octet-stream` header.
.. code-block:: bash
curl "localhost:3000/rpc/file?id=42" -H "Accept: application/octet-stream"
Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work. That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
Instead, the :code:`Accept: image/webp` header is sent by many web browsers by default.
Luckily we can change the accepted media type in the function like so:
.. code-block:: postgres
create domain "image/webp" as bytea;
create or replace function file(id int) returns "image/webp" as $$
select blob from files where id = file.id;
$$ language sql;
Luckily we can specify the accepted media types in the :ref:`raw-media-types` configuration variable.
In this case, the :code:`Accept: image/webp` header is sent by many web browsers by default, so let's add it to the configuration variable, like this: :code:`raw-media-types="image/webp"`.
Now, the image will be displayed in the HTML page: Now, the image will be displayed in the HTML page:
.. code-block:: html .. code-block:: html
<img src="http://localhost:3000/file?id=42" alt="Cute Kittens"/> <img src="http://localhost:3000/files?select=blob&id=eq.42" alt="Cute Kittens"/>
Improved Version Improved Version
---------------- ----------------
@@ -81,20 +57,16 @@ First, in addition to the minimal example, we need to store the media types and
.. code-block:: postgres .. code-block:: postgres
alter table files 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; 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 up an RPC endpoint that sets the content type and filename.
Next, we set modify the function to set the content type and filename.
We use this opportunity to configure some basic, client-side caching. 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 .. code-block:: postgres
create domain "*/*" as bytea; create function file(id int) returns bytea as
create function file(id int) returns "*/*" as
$$ $$
declare headers text; declare headers text;
declare blob bytea; declare blob bytea;
@@ -107,7 +79,7 @@ For production, you probably want to configure additional caches, e.g. on the :r
from files where files.id = file.id into headers; from files where files.id = file.id into headers;
perform set_config('response.headers', headers, true); perform set_config('response.headers', headers, true);
select files.blob from files where files.id = file.id into blob; select files.blob from files where files.id = file.id into blob;
if FOUND -- special var, see https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-DIAGNOSTICS if found
then return(blob); then return(blob);
else raise sqlstate 'PT404' using else raise sqlstate 'PT404' using
message = 'NOT FOUND', message = 'NOT FOUND',
@@ -31,7 +31,7 @@ As in :ref:`sql_user_management`, we create a :code:`basic_auth` schema:
-- We put things inside the basic_auth schema to hide -- We put things inside the basic_auth schema to hide
-- them from public view. Certain public procs/views will -- them from public view. Certain public procs/views will
-- refer to helpers and tables inside. -- 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: 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; CREATE SCHEMA ext_pgcrypto;
ALTER SCHEMA ext_pgcrypto OWNER TO postgres; 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`. 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; CREATE SCHEMA ext_pgjwt;
ALTER SCHEMA ext_pgjwt OWNER TO postgres; 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 CREATE FUNCTION basic_auth.pbkdf2(salt bytea, pw text, count integer, desired_length integer, algorithm text) RETURNS bytea
LANGUAGE plpgsql IMMUTABLE 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; 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>`_: 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 CREATE FUNCTION basic_auth.check_user_pass(username text, password text) RETURNS name
LANGUAGE sql 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. 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. -- 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'; 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 LANGUAGE plpgsql security definer
AS $$ AS $$
DECLARE DECLARE
_role name; _role name;
result basic_auth.jwt_token;
BEGIN BEGIN
-- check email and password -- check email and password
SELECT basic_auth.check_user_pass(username, password) INTO _role; 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, SELECT login.username as role,
extract(epoch FROM now())::integer + 60*60 AS exp extract(epoch FROM now())::integer + 60*60 AS exp
) r ) r
INTO token; INTO result;
RETURN result;
END; END;
$$; $$;
@@ -253,11 +259,19 @@ Test at the REST level
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
An API request to call this function would look like: An API request to call this function would look like:
.. code-block:: bash .. tabs::
curl "http://localhost:3000/rpc/login" \ .. code-tab:: http
-X POST -H "Content-Type: application/json" \
-d '{ "username": "foo", "password": "bar" }' 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!) 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: 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. 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. 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" \ .. code-tab:: http
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY"
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: 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 -- We put things inside the basic_auth schema to hide
-- them from public view. Certain public procs/views will -- them from public view. Certain public procs/views will
-- refer to helpers and tables inside. -- refer to helpers and tables inside.
create schema if not exists basic_auth;
create table create table if not exists
basic_auth.users ( basic_auth.users (
email text primary key check ( email ~* '^.+@.+\..+$' ), email text primary key check ( email ~* '^.+@.+\..+$' ),
pass text not null check (length(pass) < 512), 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. 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 $$ basic_auth.check_role_exists() returns trigger as $$
begin begin
if not exists (select 1 from pg_roles as r where r.rolname = new.role) then 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 end
$$ language plpgsql; $$ language plpgsql;
drop trigger if exists ensure_user_role_exists on basic_auth.users;
create constraint trigger ensure_user_role_exists create constraint trigger ensure_user_role_exists
after insert or update on basic_auth.users after insert or update on basic_auth.users
for each row 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. 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 $$ basic_auth.encrypt_pass() returns trigger as $$
begin begin
if tg_op = 'INSERT' or new.pass <> old.pass then 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 end
$$ language plpgsql; $$ language plpgsql;
drop trigger if exists encrypt_pass on basic_auth.users;
create trigger encrypt_pass create trigger encrypt_pass
before insert or update on basic_auth.users before insert or update on basic_auth.users
for each row 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. 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 basic_auth.user_role(email text, pass text) returns name
language plpgsql language plpgsql
as $$ 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. 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 .. 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( SELECT public.sign(
row_to_json(r), 'reallyreallyreallyreallyverysafe' row_to_json(r), 'reallyreallyreallyreallyverysafe'
) AS token ) AS token
@@ -134,7 +141,7 @@ PostgREST exposes this function to clients via a POST request to ``/rpc/jwt_test
.. note:: .. 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 .. 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 .. code-block:: postgres
-- add type
CREATE TYPE basic_auth.jwt_token AS (
token text
);
-- login should be on your exposed schema -- login should be on your exposed schema
create function create or replace function
login(email text, pass text, out token text) as $$ login(email text, pass text) returns basic_auth.jwt_token as $$
declare declare
_role name; _role name;
result basic_auth.jwt_token;
begin begin
-- check email and password -- check email and password
select basic_auth.user_role(email, pass) into _role; 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, select _role as role, login.email as email,
extract(epoch from now())::integer + 60*60 as exp extract(epoch from now())::integer + 60*60 as exp
) r ) r
into token; into result;
return result;
end; end;
$$ language plpgsql security definer; $$ 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: An API request to call this function would look like:
.. code-block:: bash .. tabs::
curl "http://localhost:3000/rpc/login" \ .. code-tab:: http
-X POST -H "Content-Type: application/json" \
-d '{ "email": "foo@bar.com", "pass": "foobar" }' 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!) 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>`_ :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:: .. contents::
:local: :local:
:depth: 1 :depth: 1
.. NOTE: Titles are ordered alphabetically. New entries should respect this order. Timestamps
----------
You can use the **time zone** to filter or send data if needed.
.. code-block:: postgres
create table reports (
id int primary key
, due_date timestamptz
);
Suppose you are located in Sydney and want create a report with the date in the local time zone. Your request should look like this:
.. tabs::
.. code-tab:: http
POST /reports HTTP/1.1
Content-Type: application/json
[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },
{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]
.. code-tab:: bash Curl
curl "http://localhost:3000/reports" \
-X POST -H "Content-Type: application/json" \
-d '[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]'
Someone located in Cairo can retrieve the data using their local time, too:
.. tabs::
.. code-tab:: http
GET /reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo"
.. code-block:: json
[
{
"id": 1,
"due_date": "2022-02-23T19:10:15-05:00"
}
]
The response has the date in the time zone configured by the server: ``UTC -05:00``.
You can use other comparative filters and also all the `PostgreSQL special date/time input values <https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE>`_ as illustrated in this example:
.. tabs::
.. code-tab:: http
GET /reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch)) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch))"
.. code-block:: json
[
{
"id": 2,
"due_date": "2022-02-27T06:00:00-05:00"
}
]
JSON
----
To work with a ``json`` type column, you can handle the value as a JSON object.
.. code-block:: postgres
create table products (
id int primary key,
name text unique,
extra_info json
);
You can insert a new product using a JSON object for the ``extra_info`` column:
.. tabs::
.. code-tab:: http
POST /products HTTP/1.1
Content-Type: application/json
{
"id": 1,
"name": "Canned fish",
"extra_info": {
"expiry_date": "2025-12-31",
"exportable": true
}
}
.. code-tab:: bash Curl
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"name": "Canned fish",
"extra_info": {
"expiry_date": "2025-12-31",
"exportable": true
}
}
EOF
To query and filter the data see :ref:`json_columns` for a complete reference.
Arrays 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. 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, "id": 1,
"title": "Paddington", "title": "Paddington",
"tags": "{family,comedy,not streamable}", "tags": "{family,comedy,not streamable}",
"performance_times": "{12:40,15:00,20:00}" "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: 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, "id": 1,
"title": "Paddington", "title": "Paddington",
"tags": ["family", "comedy", "not streamable"], "tags": ["family", "comedy", "not streamable"],
"performance_times": ["12:40", "15:00", "20:00"] "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`. 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: 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] ] ] "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: 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 .. 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 Composite Types
--------------- ---------------
@@ -172,79 +286,260 @@ With PostgREST, you have two options to handle `composite type columns <https://
On one hand you can insert values using string representation. 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\")" } { "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. Or you could insert the same data in JSON format.
.. code-block:: bash .. tabs::
curl "http://localhost:3000/products" \ .. code-tab:: http
-X POST -H "Content-Type: application/json" \
-d @- << EOF POST /products HTTP/1.1
{ Content-Type: application/json
"id": 2,
"size": { {
"length": 0.7, "id": 2,
"width": 0.5, "size": {
"height": 1.8, "length": 0.7,
"unit": "m" "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`. 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 .. 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 ( To insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type:
id int primary key generated always as identity,
name text,
size letter_size
);
To insert or update the value use a string: .. tabs::
.. code-block:: bash .. code-tab:: http
curl -X POST "http://localhost:3000/products" \ POST /events HTTP/1.1
-H "Content-Type: application/json" \ Content-Type: application/json
-d @- << EOF
{ "name": "t-shirt", "size": "l" }
EOF
You can then query and filter the enum using the compatible :ref:`operators <operators>`. {
For example, to get all the products larger than `m` and ordering them by their size: "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 .. code-block:: json
[ [
{ {
"name": "t-shirt", "id": 1,
"size": "l" "name": "New Year's Party",
}, "duration": "[\"2022-12-31 11:00:00\",\"2023-01-01 06:00:00\"]"
{
"name": "hoodie",
"size": "xl"
} }
] ]
.. _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, set the ``Accept: application/octet-stream`` header and select only the
``bytea`` type column.
.. tabs::
.. code-tab:: http
GET /files?select=file&id=eq.1 HTTP/1.1
Accept: application/octet-stream
.. code-tab:: bash Curl
curl "http://localhost:3000/files?select=file&id=eq.1" \
-H "Accept: application/octet-stream"
Use more accurate headers according to the type of the files by using the :ref:`raw-media-types` configuration. For example, adding the ``raw-media-types="image/png"`` setting to the configuration file will allow you to use the ``Accept: image/png`` header:
.. tabs::
.. code-tab:: http
GET /files?select=file&id=eq.1 HTTP/1.1
Accept: image/png
.. code-tab:: bash Curl
curl "http://localhost:3000/files?select=file&id=eq.1" \
-H "Accept: image/png"
See :ref:`providing_img` for a step-by-step example on how to handle images in HTML.
.. warning::
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
hstore hstore
------ ------
@@ -263,67 +558,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: 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": 1, "name": "common => Egypt, official => \"Arab Republic of Egypt\", native => مصر" },
{ "id": 2, "name": "common => Germany, official => \"Federal Republic of Germany\", native => Deutschland" } { "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 ``\``. 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: 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 .. code-block:: json
[{ "native": "مصر" }] [{ "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: .. _ww_postgis:
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 .. code-block:: postgres
@@ -338,23 +619,42 @@ You can use the string representation for `PostGIS <https://postgis.net/>`_ data
To add areas in polygon format, you can use string representation: 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": 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))" } { "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>`_: 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" \ .. code-tab:: http
-H "Accept: application/geo+json"
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 .. code-block:: json
@@ -418,9 +718,15 @@ In the case that you are using older PostGIS versions, then creating a function
Now this query will return the same results: 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 .. code-block:: json
@@ -455,157 +761,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"
}
]
+22 -50
View File
@@ -5,7 +5,7 @@ PostgREST Documentation
.. container:: image-container .. container:: image-container
.. figure:: ../static/postgrest.png .. figure:: _static/logo.png
.. image:: https://img.shields.io/github/stars/postgrest/postgrest.svg?style=social .. image:: https://img.shields.io/github/stars/postgrest/postgrest.svg?style=social
:target: https://github.com/PostgREST/postgrest :target: https://github.com/PostgREST/postgrest
@@ -28,59 +28,30 @@ Sponsors
.. container:: image-container .. 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 .. image:: _static/gnuhost.png
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest :target: https://euronodes.com/?utm_source=sponsor&utm_campaign=postgrest
:width: 13em
.. container:: img-light
.. image:: ../static/cybertec.svg
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
.. container:: img-dark
.. image:: ../static/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
.. container:: img-dark
.. image:: ../static/code-build-dark.png
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
.. container:: img-light
.. image:: ../static/code-build.png
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
| |
.. image:: ../static/tembo.png .. image:: _static/supabase.png
:target: https://www.tembo.io/?utm_source=sponsor&utm_campaign=postgrest :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-dark .. image:: _static/neon.jpg
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
.. image:: ../static/supabase-dark.png :width: 13em
: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/empty.png
:target: #sponsors
.. The static/empty.png(created with `convert -size 320x95 xc:#fcfcfc empty.png`) is an ugly workaround .. 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. to create space and center the logos. It's not easy to layout with restructuredText.
.. .. image:: _static/empty.png .. .. image:: _static/empty.png
:target: #sponsors :target: #sponsors
:width: 13em
| |
@@ -109,10 +80,13 @@ Getting Support
The project has a friendly and growing community. For discussions, use the Github `discussions page <https://github.com/PostgREST/postgrest/discussions>`_. You can also report or search for bugs/features on the Github `issues <https://github.com/PostgREST/postgrest/issues>`_ page. The project has a friendly and growing community. For discussions, use the Github `discussions page <https://github.com/PostgREST/postgrest/discussions>`_. You can also report or search for bugs/features on the Github `issues <https://github.com/PostgREST/postgrest/issues>`_ page.
Release Notes .. toctree::
------------- :glob:
:caption: Release Notes
:reversed:
:maxdepth: 1
The release notes are published on `PostgREST's GitHub release page <https://github.com/PostgREST/postgrest/releases>`_. releases/*
Tutorials Tutorials
--------- ---------
@@ -141,13 +115,11 @@ Technical references for PostgREST's functionality.
references/auth.rst references/auth.rst
references/api.rst references/api.rst
references/cli.rst
references/transactions.rst references/transactions.rst
references/connection_pool.rst references/connection_pool.rst
references/schema_cache.rst references/schema_cache.rst
references/errors.rst references/errors.rst
references/configuration.rst references/configuration.rst
references/observability.rst
references/* references/*
Explanations Explanations
@@ -213,9 +185,9 @@ Here are some companies that use PostgREST in production.
* `Drip Depot <https://www.dripdepot.com>`_ * `Drip Depot <https://www.dripdepot.com>`_
* `Image-charts <https://www.image-charts.com>`_ * `Image-charts <https://www.image-charts.com>`_
* `Netwo <https://www.netwo.io>`_ * `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>`_. - See how Nimbus uses PostgREST in `Paul Copplestone's blog post <https://paul.copplest.one/blog/nimbus-tech-2019-04.html>`_.
* `OpenBooking <https://openbooking.ch>`_ * `OpenBooking <https://www.openbooking.ch>`_
* `Supabase <https://supabase.com>`_ * `Supabase <https://supabase.com>`_
Testimonials Testimonials
+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"}'
+31
View File
@@ -0,0 +1,31 @@
.. _external_jwt:
External JWT Generation
-----------------------
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: 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! 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: 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 -9
View File
@@ -1,7 +1,7 @@
systemd 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`` First, create postgrest configuration in ``/etc/postgrest/config``
@@ -12,12 +12,6 @@ First, create postgrest configuration in ``/etc/postgrest/config``
db-anon-role = "<your_anon_role>" db-anon-role = "<your_anon_role>"
jwt-secret = "<your_secret>" jwt-secret = "<your_secret>"
Create a dedicated ``postgrest`` user with:
.. code-block:: ini
sudo useradd -M -U -d /nonexistent -s /usr/sbin/nologin postgrest
Then create the systemd service file in ``/etc/systemd/system/postgrest.service`` Then create the systemd service file in ``/etc/systemd/system/postgrest.service``
.. code-block:: ini .. code-block:: ini
@@ -27,8 +21,6 @@ Then create the systemd service file in ``/etc/systemd/system/postgrest.service`
After=postgresql.service After=postgresql.service
[Service] [Service]
User=postgrest
Group=postgrest
ExecStart=/bin/postgrest /etc/postgrest/config ExecStart=/bin/postgrest /etc/postgrest/config
ExecReload=/bin/kill -SIGUSR1 $MAINPID ExecReload=/bin/kill -SIGUSR1 $MAINPID
+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/")
+32 -23
View File
@@ -1,62 +1,63 @@
personal_ws-1.1 en 0 utf-8 personal_ws-1.1 en 0 utf-8
api api
API's API's
APIs
APISIX APISIX
AST Archlinux
aud aud
Auth Auth
auth auth
authenticator authenticator
backoff backoff
balancer
booleans booleans
BOM Bouscal
buildpack
Bytea Bytea
Cardano Cardano
cd cd
centric centric
CLI changelog
CMS
coercible coercible
conf conf
Cloudflare Cloudflare
config config
cors
CORS CORS
CPUs
cryptographically cryptographically
CSV CSV
durations
DDL DDL
DOM
DSL
DevOps DevOps
DiBiase
dockerize dockerize
enum
Enums
Entra
eq eq
ETH ETH
Ethereum Ethereum
EveryLayout EveryLayout
Fenko
Fernandes
filename filename
FreeBSD FreeBSD
fts fts
GC
GeoJSON GeoJSON
GHC
Github Github
Google Google
grantor grantor
GraphQL GraphQL
gte gte
GUC GUC
GUCs
gucs
Haskell Haskell
Heroku
HMAC HMAC
htmx
Htmx
Homebrew Homebrew
hstore hstore
HTTP HTTP
HTTPS HTTPS
HV HV
Ibarluzea
Inlining Inlining
inlined inlined
Integrations Integrations
@@ -70,11 +71,9 @@ isdistinct
JS JS
js js
JSON JSON
JSPath
JWK JWK
JWT JWT
jwt jwt
Keycloak
Kubernetes Kubernetes
localhost localhost
login login
@@ -85,7 +84,6 @@ logins
lon lon
lt lt
lte lte
macOS
misprediction misprediction
multi multi
namespace namespace
@@ -98,15 +96,14 @@ npm
nxl nxl
nxr nxr
OAuth OAuth
ORM
Observability
Okta
OpenAPI OpenAPI
openapi openapi
ORM
ov ov
parametrized
passphrase passphrase
Pawel
PBKDF PBKDF
Pelletier
PgBouncer PgBouncer
pgcrypto pgcrypto
pgjwt pgjwt
@@ -120,6 +117,7 @@ phraseto
plainto plainto
plfts plfts
poolers poolers
POSIX
PostGIS PostGIS
PostgreSQL PostgreSQL
PostgreSQL's PostgreSQL's
@@ -131,41 +129,52 @@ pre
preflight preflight
plpgsql plpgsql
psql psql
Qin
RabbitMQ RabbitMQ
Rafaj
RDS RDS
reallyreallyreallyreallyverysafe reallyreallyreallyreallyverysafe
Rechkemmer
Redux Redux
refactor refactor
reloadable reloadable
Reloadable Reloadable
Remo
requester's requester's
RESTful RESTful
RLS RLS
RPC RPC
RSA RSA
Saleeba
safeupdate safeupdate
savepoint savepoint
schemas schemas
schema's schema's
Severin
SHA SHA
Sommer
signup signup
SIGUSR SIGUSR
sl sl
SQL SQL
sql sql
SQLSTATE
sr sr
SSL SSL
stateful stateful
stdout stdout
Stolarz
supervisees supervisees
SvelteKit SvelteKit
SwaggerUI
syslog
systemd systemd
todo todo
todos todos
tos tos
Tsingson
tsquery tsquery
tx tx
Tyll
TypeScript TypeScript
UI UI
ui ui
@@ -173,7 +182,6 @@ unicode
unikernel unikernel
unix unix
updatable updatable
unfulfillable
Untyped Untyped
UPSERT UPSERT
Upsert Upsert
@@ -193,3 +201,4 @@ Websockets
webuser webuser
wfts wfts
www www
Zac
+280
View File
@@ -0,0 +1,280 @@
.. _admin:
Admin
#####
.. _pgrst_logging:
Logging
-------
PostgREST logs basic request information to ``stdout``, including the authenticated user if available, the requesting IP address and user agent, the URL requested, and HTTP response status.
.. code::
127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 - "" "curl/7.64.0"
127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 - "" "curl/7.64.0"
For diagnostic information about the server itself, PostgREST logs to ``stderr``.
.. code::
12/Jun/2021:17:47:39 -0500: 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
.. 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.
.. code:: sql
# send logs where the collector can access them
log_destination = "stderr"
# collect stderr output to log files
logging_collector = on
# save logs in pg_log/ under the pg data directory
log_directory = "pg_log"
# (optional) new log file per day
log_filename = "postgresql-%Y-%m-%d.log"
# log every kind of SQL statement
log_statement = "all"
Restart the database and watch the log file in real-time to understand how HTTP requests are being translated into SQL commands.
.. note::
On Docker you can enable the logs by using a custom ``init.sh``:
.. code:: bash
#!/bin/sh
echo "log_statement = 'all'" >> /var/lib/postgresql/data/postgresql.conf
After that you can start the container and check the logs with ``docker logs``.
.. code:: bash
docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres
docker logs -f <container-id>
Server Version
--------------
When debugging a problem it's important to verify the running PostgREST version. There are three ways to do this:
- Look for the :code:`Server` HTTP response header that is returned on every request.
.. code::
HEAD /users HTTP/1.1
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
.. note::
The server sets the `fallback_application_name <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-FALLBACK-APPLICATION-NAME>`_ for this query to work. To override the value set ``application_name`` on the connection string.
- The ``stderr`` logs also contain the version, as noted on :ref:`pgrst_logging`.
.. _trace_header:
Trace Header
------------
You can enable tracing HTTP requests by setting :ref:`server-trace-header`. Specify the set header in the request, and the server will include it in the response.
.. code:: bash
server-trace-header = "X-Request-Id"
.. tabs::
.. 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::
HTTP/1.1 200 OK
X-Request-Id: 123
.. _explain_plan:
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).
.. tabs::
.. code-tab:: http
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.
.. tabs::
.. 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
[
{
"Plan": {
"Node Type": "Aggregate",
"Strategy": "Plain",
"Partial Mode": "Simple",
"Parallel Aware": false,
"Async Capable": false,
"Startup Cost": 73.65,
"Total Cost": 73.68,
"Plan Rows": 1,
"Plan Width": 112,
"Plans": [
{
"Node Type": "Index Scan",
"Parent Relationship": "Outer",
"Parallel Aware": false,
"Async Capable": false,
"Scan Direction": "Forward",
"Index Name": "users_pkey",
"Relation Name": "users",
"Alias": "users",
"Startup Cost": 0.15,
"Total Cost": 60.90,
"Plan Rows": 850,
"Plan Width": 36
}
]
}
}
]
By default the plan is assumed to generate the JSON representation of a resource(``application/json``), but you can obtain the plan for the :ref:`different representations that PostgREST supports <res_format>` by adding them to the ``for`` parameter. For instance, to obtain the plan for a ``text/xml``, you would use ``Accept: application/vnd.pgrst.plan; for="text/xml``.
The other available parameters are ``analyze``, ``verbose``, ``settings``, ``buffers`` and ``wal``, which correspond to the `EXPLAIN command options <https://www.postgresql.org/docs/current/sql-explain.html>`_. To use the ``analyze`` and ``wal`` parameters for example, you would add them like ``Accept: application/vnd.pgrst.plan; options=analyze|wal``.
Note that akin to the EXPLAIN command, the changes will be committed when using the ``analyze`` option. To avoid this, you can use the :ref:`db-tx-end` and the ``Prefer: tx=rollback`` header.
Securing the Execution Plan
~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's recommended to only activate :ref:`db-plan-enabled` on testing environments since it reveals internal database details.
However, if you choose to use it in production you can add a :ref:`db-pre-request` to filter the requests that can use this feature.
For example, to only allow requests from an IP address to get the execution plans:
.. code-block:: postgresql
-- Assuming a proxy(Nginx, Cloudflare, etc) passes an "X-Forwarded-For" header(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
create or replace function filter_plan_requests()
returns void as $$
declare
headers json := current_setting('request.headers', true)::json;
client_ip text := coalesce(headers->>'x-forwarded-for', '');
accept text := coalesce(headers->>'accept', '');
begin
if accept like 'application/vnd.pgrst.plan%' and client_ip != '144.96.121.73' then
raise insufficient_privilege using
message = 'Not allowed to use application/vnd.pgrst.plan';
end if;
end; $$ language plpgsql;
-- set this function on your postgrest.conf
-- db-pre-request = filter_plan_requests
.. _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.
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.
-76
View File
@@ -1,76 +0,0 @@
.. _admin_server:
Admin Server
############
PostgREST provides an admin server that can be enabled by setting :ref:`admin-server-port`.
.. _health_check:
Health Check
============
You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
Two endpoints ``live`` and ``ready`` will then be available.
.. important::
If you have a machine with multiple network interfaces and multiple PostgREST instances in the same port, you need to specify a unique :ref:`hostname <server-host>`
in the configuration of each PostgREST instance for the health check to work correctly. Don't use the special values(``!4``, ``*``, etc) in this case because the health check
could report a false positive.
Live
----
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``500`` otherwise.
For instance, to verify if PostgREST is running while the ``admin-server-port`` is set to ``3001``:
.. code-block:: bash
curl -I "http://localhost:3001/live"
.. code-block:: http
HTTP/1.1 200 OK
Ready
-----
Additionally to the ``live`` check, the ``ready`` endpoint checks the state of the :ref:`connection_pool` and the :ref:`schema_cache`. A request will return ``200 OK`` if both are good or ``503`` if not.
.. code-block:: bash
curl -I "http://localhost:3001/ready"
.. code-block:: http
HTTP/1.1 200 OK
PostgREST will try to recover from the ``503`` state with :ref:`automatic_recovery`.
Metrics
=======
Provides :ref:`metrics`.
Runtime Schema Cache
====================
Provides the ``schema_cache`` endpoint that prints the runtime :ref:`schema_cache`.
.. code-block:: bash
curl "http://localhost:3001/schema_cache"
.. code-block:: json
{
"dbMediaHandlers": ["..."],
"dbRelationships": ["..."],
"dbRepresentations": ["..."],
"dbRoutines": ["..."],
"dbTables": ["..."],
"dbTimezones": ["..."]
}
+30 -34
View File
@@ -3,24 +3,20 @@
API 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:: .. toctree::
:glob: :glob:
:maxdepth: 1 :maxdepth: 1
api/tables_views.rst api/tables_views.rst
api/functions.rst api/stored_procedures.rst
api/schemas.rst api/schemas.rst
api/computed_fields.rst api/computed_fields.rst
api/domain_representations.rst api/domain_representations.rst
api/pagination_count.rst
api/resource_embedding.rst api/resource_embedding.rst
api/resource_representation.rst api/resource_representation.rst
api/media_type_handlers.rst
api/aggregate_functions.rst
api/openapi.rst api/openapi.rst
api/preferences.rst
api/* api/*
.. raw:: html .. raw:: html
@@ -30,22 +26,22 @@ PostgREST exposes three database objects of a schema as resources: tables, views
const redirects = { const redirects = {
// Tables and Views // 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', '#operators': 'api/tables_views.html#operators',
'#logical-operators': 'api/tables_views.html#logical-operators', '#logical-operators': 'api/tables_views.html#logical-operators',
'#pattern-matching': 'api/tables_views.html#pattern-matching', '#pattern-matching': 'api/tables_views.html#pattern-matching',
'#full-text-search': 'api/tables_views.html#full-text-search', '#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', '#renaming-columns': 'api/tables_views.html#renaming-columns',
'#casting-columns': 'api/tables_views.html#casting-columns', '#casting-columns': 'api/tables_views.html#casting-columns',
'#json-columns': 'api/tables_views.html#json-columns', '#json-columns': 'api/tables_views.html#json-columns',
'#composite-array-columns': 'api/tables_views.html#composite-array-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', '#ordering': 'api/tables_views.html#ordering',
'#limits-and-pagination': 'api/pagination_count.html', '#limits-and-pagination': 'api/tables_views.html#limits-and-pagination',
'#exact-count': 'api/pagination_count.html#exact-count', '#exact-count': 'api/tables_views.html#exact-count',
'#planned-count': 'api/pagination_count.html#planned-count', '#planned-count': 'api/tables_views.html#planned-count',
'#estimated-count': 'api/pagination_count.html#estimated-count', '#estimated-count': 'api/tables_views.html#estimated-count',
'#updates': 'api/tables_views.html#update', '#updates': 'api/tables_views.html#update',
'#insertions': 'api/tables_views.html#insert', '#insertions': 'api/tables_views.html#insert',
'#bulk-insert': 'api/tables_views.html#bulk-insert', '#bulk-insert': 'api/tables_views.html#bulk-insert',
@@ -55,15 +51,15 @@ PostgREST exposes three database objects of a schema as resources: tables, views
'#put': 'api/tables_views.html#put', '#put': 'api/tables_views.html#put',
'#deletions': 'api/tables_views.html#delete', '#deletions': 'api/tables_views.html#delete',
'#limited-updates-deletions': 'api/tables_views.html#limited-update-delete', '#limited-updates-deletions': 'api/tables_views.html#limited-update-delete',
// Functions // Stored procedures
'#stored-procedures': 'api/functions.html', '#stored-procedures': 'api/stored_procedures.html#stored-procedures',
'#calling-functions-with-a-single-json-parameter': 'api/functions.html#functions-with-a-single-json-parameter', '#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/functions.html#functions-with-a-single-unnamed-parameter', '#calling-functions-with-a-single-unnamed-parameter': 'api/stored_procedures.html#functions-with-a-single-unnamed-parameter',
'#calling-functions-with-array-parameters': 'api/functions.html#functions-with-array-parameters', '#calling-functions-with-array-parameters': 'api/stored_procedures.html#functions-with-array-parameters',
'#calling-variadic-functions': 'api/functions.html#variadic-functions', '#calling-variadic-functions': 'api/stored_procedures.html#variadic-functions',
'#scalar-functions': 'api/functions.html#scalar-functions', '#scalar-functions': 'api/stored_procedures.html#scalar-functions',
'#function-filters': 'api/functions.html#table-valued-functions', '#function-filters': 'api/stored_procedures.html#table-valued-functions',
'#overloaded-functions': 'api/functions.html#overloaded-functions', '#overloaded-functions': 'api/stored_procedures.html#overloaded-functions',
// Schemas // Schemas
'#switching-schemas': 'api/schemas.html', '#switching-schemas': 'api/schemas.html',
// Resource Embedding // Resource Embedding
@@ -76,21 +72,21 @@ PostgREST exposes three database objects of a schema as resources: tables, views
'#nested-embedding': 'api/resource_embedding.html#nested-embedding', '#nested-embedding': 'api/resource_embedding.html#nested-embedding',
'#embedded-filters': 'api/resource_embedding.html#embedded-filters', '#embedded-filters': 'api/resource_embedding.html#embedded-filters',
'#embedding-with-top-level-filtering': 'api/resource_embedding.html#top-level-filtering', '#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-partitioned-tables': 'api/resource_embedding.html#embedding-partitioned-tables',
'#embedding-views': 'api/resource_embedding.html#foreign-key-joins-on-views', '#embedding-views': 'api/resource_embedding.html#embedding-views',
'#embedding-chains-of-views': 'api/resource_embedding.html#foreign-key-joins-on-chains-of-views', '#embedding-chains-of-views': 'api/resource_embedding.html#embedding-chains-of-views',
'#embedding-on-stored-procedures': 'api/resource_embedding.html#foreign-key-joins-on-table-valued-functions', '#embedding-on-stored-procedures': 'api/resource_embedding.html#embedding-on-stored-procedures',
'#embedding-after-insertions-updates-deletions': 'api/resource_embedding.html#foreign-key-joins-on-writes', '#embedding-after-insertions-updates-deletions': 'api/resource_embedding.html#embedding-after-insertions-updates-deletions',
'#embedding-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships', '#embedding-disambiguation': 'api/resource_embedding.html#embedding-disambiguation',
'#target-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships', '#target-disambiguation': 'api/resource_embedding.html#target-disambiguation',
'#hint-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships', '#hint-disambiguation': 'api/resource_embedding.html#hint-disambiguation',
"#embedding-through-join-tables": "api/resource_embedding.html#many-to-many-relationships", "#embedding-through-join-tables": "api/resource_embedding.html#many-to-many-relationships",
// OpenAPI // OpenAPI
'#openapi-support': 'api/openapi.html', '#openapi-support': 'api/openapi.html',
// Resource Representation // Resource Representation
'#response-format': 'api/resource_representation.html#response-format', '#response-format': 'api/resource_representation.html#response-format',
'#singular-or-plural': 'api/resource_representation.html#singular-or-plural', '#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
'#cors': 'api/cors.html', '#cors': 'api/cors.html',
// OPTIONS // OPTIONS
@@ -104,14 +100,14 @@ PostgREST exposes three database objects of a schema as resources: tables, views
'#immutable-and-stable-functions': 'transactions.html#access-mode', '#immutable-and-stable-functions': 'transactions.html#access-mode',
'#http-context': 'transactions.html#transaction-scoped-settings', '#http-context': 'transactions.html#transaction-scoped-settings',
'#accessing-request-headers-cookies-and-jwt-claims': 'transactions.html#request-headers-cookies-and-jwt-claims', '#accessing-request-headers-cookies-and-jwt-claims': 'transactions.html#request-headers-cookies-and-jwt-claims',
'#legacy-guc-variable-names': 'transactions.html#transaction-scoped-settings', '#legacy-guc-variable-names': 'transactions.html#legacy-settings',
'#accessing-request-path-and-method': 'transactions.html#request-path-and-method', '#accessing-request-path-and-method': 'transactions.html#request-path-and-method',
'#setting-response-headers': 'transactions.html#response-headers', '#setting-response-headers': 'transactions.html#response-headers',
'#setting-headers-via-pre-request': 'transactions.html#setting-headers-via-pre-request', '#setting-headers-via-pre-request': 'transactions.html#setting-headers-via-pre-request',
'#setting-response-status-code': 'transactions.html#response-status-code', '#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 // Admin
'#execution-plan': 'observability.html#execution-plan', '#execution-plan': 'admin.html#execution-plan',
// Deprecated // Deprecated
'#bulk-call': '../releases/v11.0.1.html#breaking-changes', '#bulk-call': '../releases/v11.0.1.html#breaking-changes',
}; };
-257
View File
@@ -1,257 +0,0 @@
.. _aggregate_functions:
Aggregate Functions
###################
PostgREST supports the following aggregate functions: ``avg()``, ``count()``, ``max()``, ``min()``, and ``sum()``.
Please refer to the `section on aggregate functions in the PostgreSQL documentation <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ for a detailed explanation of these functions.
.. note::
Aggregate functions are *disabled* by default in PostgREST, because they can create performance problems without appropriate safeguards.
See :ref:`db-aggregates-enabled` for further details.
To use an aggregate function, append it to a column in the ``select`` parameter, like so:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum()"
This will return a ``sum`` of all the values of the ``amount`` column in a single row:
.. code-block:: json
[
{
"sum": 1234.56
}
]
You can ``select`` multiple aggregate functions at the same time (you may need to :ref:`rename them <renaming_columns>` to disambiguate).
.. code-block:: bash
curl "http://localhost:3000/orders?select=total_amount:amount.sum(),avg_amount:amount.avg(),total_quantity:quantity.sum()"
.. note::
Aggregate functions work alongside other PostgREST features, like :ref:`h_filter`, :ref:`json_columns`, and :ref:`ordering`.
However they are not compatible with :ref:`domain_reps` for the moment.
Additionally, PostgreSQL's ``HAVING`` clause and ordering by aggregated columns are not yet supported.
Automatic ``GROUP BY``
======================
In SQL, a ``GROUP BY`` clause is required to aggregate the selected columns.
However, PostgREST handles grouping automatically if the columns are already present in the ``select`` parameter.
For instance:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
This will get the sum and average of the amounts grouped by each unique value in the ``order_date`` column:
.. code-block:: json
[
{
"sum": 1234.56,
"avg": 123.45,
"order_date": "2023-01-01"
},
{
"sum": 2345.67,
"avg": 234.56,
"order_date": "2023-01-02"
}
]
The ``count()`` Aggregate
=========================
.. note::
Before the addition of aggregate functions, it was possible to count by adding ``count`` (without parentheses) to the ``select`` parameter.
While this is still supported, it may be deprecated in the future, and thus use of this legacy feature is **not recommended**.
Please use ``count()`` (with parentheses) instead.
``count()`` is a special case because it can be used with or without an aggregated column. For example:
.. code-block:: bash
curl "http://localhost:3000/orders?select=count(),observation_count:observation.count(),order_date"
.. code-block:: json
[
{
"count": 4,
"observation_count": 2,
"order_date": "2023-01-01"
},
{
"count": 2,
"observation_count": 1,
"order_date": "2023-01-02"
}
]
Note that there is a difference between the result of ``count()`` and ``observation.count()``.
The former counts the whole row, while the latter counts the non ``NULL`` values of the ``observation`` column (both grouped by ``order_date``).
This is due to how PostgreSQL itself implements the ``count()`` function.
Casting Aggregates
==================
It is :ref:`possible to cast <casting_columns>` the aggregated column or the aggregate itself, or both at the same time.
Casting the Aggregated Column
-----------------------------
For example, let's say that ``orders`` has an ``order_details`` :ref:`JSON column <json_columns>` with a ``tax_amount`` key.
We cannot sum ``tax_amount`` directly because using ``->`` or ``->>`` will return the data in ``json`` or ``text`` format.
So we need to cast it to a compatible type (e.g. ``numeric``) right before the aggregate function:
.. code-block:: bash
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
.. code-block:: json
[
{
"sum": 1234.56
}
]
Casting the Aggregate
---------------------
For instance, if we wanted to round the average of the ``amount`` column, we could do so by casting ``avg()`` to an ``int``:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.avg()::int"
.. code-block:: json
[
{
"avg": 201
}
]
Aggregates and Resource Embedding
=================================
You can group an aggregate function by an :ref:`embedded resource <resource_embedding>` and also use the aggregates inside them.
Grouping by an Embedded Resource
--------------------------------
Similar to grouping by columns, aggregate functions can also be grouped by embedded resources.
For example, let's say that the ``orders`` table is related to a ``customers`` table.
To get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, we would do the following:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
.. code-block:: json
[
{
"sum": 100,
"customers": {
"name": "Customer A"
}
},
{
"sum": 200,
"customers": {
"name": "Customer B"
}
}
]
The previous example uses a "to-one" relationship, but this can be done on "to-many" relationships as well (although there are few obvious use cases).
This also works in a similar way for :ref:`spread embedded resources <spread_embed>`.
For example, ``select=amount.sum(),...customers(name)`` would sum the ``amount`` grouped by the ``name`` column.
Using Aggregates Inside Embedded Resources
------------------------------------------
Using the relationship from the previous example, let's take all the ``customers`` and embed their ``orders``.
If we also want to get the total ``amount`` grouped by the ``order_date`` of the ``orders``, we would do the following:
.. code-block:: bash
curl "http://localhost:3000/customers?select=name,city,state,orders(amount.sum(),order_date)"
.. code-block:: json
[
{
"name": "Customer A",
"city": "New York",
"state": "NY",
"orders": [
{
"sum": 215.22,
"order_date": "2023-09-01"
},
{
"sum": 905.73,
"order_date": "2023-09-02"
}
]
},
{
"name": "Customer B",
"city": "Los Angeles",
"state": "CA",
"orders": [
{
"sum": 329.71,
"order_date": "2023-09-01"
},
{
"sum": 425.87,
"order_date": "2023-09-03"
}
]
}
]
Note that the aggregate is done within the embedded resource ``orders``.
It is not affected by any of the columns from the top-level relationship ``customers``.
Aggregates in To-One Spreads
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All the aggregates inside a :ref:`one-to-one or many-to-one spread embedded resource <spread_to_one_embed>` will be hoisted to the top-level relationship.
In other words, it will behave as if the aggregate was done in the top-level relationship itself. For example:
.. code-block:: bash
curl "http://localhost:3000/orders?select=order_date,...customers(subscription_date.max(),subscription_date.min())
This will take the ``max`` and ``min`` subscription date of every customer and group it by the ``order_date`` column:
.. code-block:: json
[
{
"order_date": "2023-11-01",
"max": "2023-10-15",
"min": "2013-10-01"
},
{
"order_date": "2023-11-02",
"max": "2023-10-30",
"min": "2016-02-11"
}
]
.. note::
Aggregates inside to-many spreads are not supported
+25 -7
View File
@@ -30,9 +30,15 @@ Horizontal Filtering on Computed Fields
CREATE INDEX people_full_name_idx ON people CREATE INDEX people_full_name_idx ON people
USING GIN (to_tsvector('english', full_name(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 .. 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: 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 .. code-block:: json
@@ -60,13 +72,19 @@ Ordering on Computed Fields
:ref:`ordering` on computed fields is also possible: :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:: .. 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:: .. note::
+17 -23
View File
@@ -1,22 +1,28 @@
.. _cors:
CORS CORS
#### ====
By default, PostgREST sets highly permissive cross origin resource sharing, that is why it accepts Ajax requests from any domain. This behavior can be configured by using :ref:`server_cors_allowed_origins`.
PostgREST sets highly permissive cross origin resource sharing, that is why it accepts Ajax requests from any domain.
It also handles `preflight requests <https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request>`_ done by the browser, which are cached using the returned ``Access-Control-Max-Age: 86400`` header (86400 seconds = 24 hours). This is useful to reduce the latency of the subsequent requests. It also handles `preflight requests <https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request>`_ done by the browser, which are cached using the returned ``Access-Control-Max-Age: 86400`` header (86400 seconds = 24 hours). This is useful to reduce the latency of the subsequent requests.
A ``POST`` preflight request would look like this: A ``POST`` preflight request would look like this:
.. code-block:: bash .. tabs::
curl -i "http://localhost:3000/items" \ .. code-tab:: http
-X OPTIONS \
-H "Origin: http://example.com" \ OPTIONS /items HTTP/1.1
-H "Access-Control-Request-Method: POST" \ Origin: http://example.com
-H "Access-Control-Request-Headers: Content-Type" 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 .. code-block:: http
@@ -26,15 +32,3 @@ A ``POST`` preflight request would look like this:
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD
Access-Control-Allow-Headers: Authorization, Content-Type, Accept, Accept-Language, Content-Language Access-Control-Allow-Headers: Authorization, Content-Type, Accept, Accept-Language, Content-Language
Access-Control-Max-Age: 86400 Access-Control-Max-Age: 86400
.. _allowed_origins:
Allowed Origins
===============
With the following config setting, PostgREST will accept CORS requests from domains :code:`http://example.com` and :code:`http://example2.com`.
.. code-block::
server-cors-allowed-origins="http://example.com, http://example2.com"
+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. With this you can obtain the data in the shortened format.
.. code-block:: bash .. tabs::
curl "http://localhost:3000/profiles" \ .. code-tab:: http
-H "Accept: application/json"
GET /profiles HTTP/1.1
Accept: application/json
.. code-tab:: bash Curl
curl "http://localhost:3000/profiles" \
-H "Accept: application/json"
.. code-block:: 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. Now you can filter as usual.
.. code-block:: bash .. tabs::
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \ .. code-tab:: http
-H "Accept: application/json"
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 .. 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. Now we can :ref:`insert` (or :ref:`update`) as usual.
.. code-block:: bash .. tabs::
curl "http://localhost:3000/profiles" \ .. code-tab:: http
-H "Prefer: return=representation" \
-H "Content-Type: application/json" \
-d @- <<JSON
{"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: The response:
-374
View File
@@ -1,374 +0,0 @@
.. _functions:
Functions as RPC
================
*"A single resource can be the equivalent of a database function, with the power to abstract state changes over any number of storage items"* -- `Roy T. Fielding <https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743>`_
Functions can perform any operation allowed by PostgreSQL (read data, modify data, :ref:`raise errors <raise_error>`, and even DDL operations). Every function in the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` is executable under the :code:`/rpc` prefix.
If they return table types, functions can:
- Use all the same :ref:`read filters as Tables and Views <read>` (horizontal/vertical filtering, counts, limits, etc.).
- Use :ref:`Resource Embedding <function_embed>`, if the returned table type has relationships to other tables.
.. note::
Why the ``/rpc`` prefix? PostgreSQL allows a table or view to have the same name as a function. The prefix allows us to avoid routes collisions.
.. warning::
`Stored Procedures <https://www.postgresql.org/docs/current/xproc.html>`_ are not supported.
Calling with POST
-----------------
To supply arguments in an API call, include a JSON object in the request payload. Each key/value of the object will become an argument.
For instance, assume we have created this function in the database.
.. code-block:: postgres
CREATE FUNCTION add_them(a integer, b integer)
RETURNS integer AS $$
SELECT a + b;
$$ LANGUAGE SQL IMMUTABLE;
.. important::
Whenever you create or change a function you must refresh PostgREST's schema cache. See the section :ref:`schema_reloading`.
The client can call it by posting an object like
.. code-block:: bash
curl "http://localhost:3000/rpc/add_them" \
-X POST -H "Content-Type: application/json" \
-d '{ "a": 1, "b": 2 }'
.. code-block:: json
3
.. note::
PostgreSQL converts identifier names to lowercase unless you quote them like:
.. code-block:: postgres
CREATE FUNCTION "someFunc"("someParam" text) ...
Calling with GET
----------------
If the function doesn't modify the database, it will also run under the GET method (see :ref:`access_mode`).
.. code-block:: bash
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
The function parameter names match the JSON object keys in the POST case, for the GET case they match the query parameters ``?a=1&b=2``.
.. _function_single_json:
Functions with an array of JSON objects
----------------------------------------------
If you want to pass multiple JSON objects to a Postgres function (an array of objects), you can create a function with a parameter of type ``json`` or ``jsonb``.
Within the curl request, this JSON must be embedded in an object where they key matches the same name as the function's ``json`` or ``jsonb`` parameter.
This will allow you to loop over the array of JSON objects within the Postgres function.
This practice may allow you to reduce the number of ``curl`` requests required to accomplish a task.
For instance, assume we have created this function in the database.
.. code-block:: postgres
CREATE FUNCTION update_data(p_json jsonb)
RETURNS void AS $$
DECLARE
json_item json;
BEGIN
FOR json_item IN SELECT jsonb_array_elements(p_json) LOOP
UPDATE data_table SET data_text_column = (json_item->>'data_text')::text
WHERE data_int_column = (json_item->>'data_int')::integer;
END LOOP;
END;
$$ LANGUAGE SQL IMMUTABLE;
A ``curl`` request using the POST method would look like the following:
.. code-block:: bash
curl "http://localhost:3000/rpc/update_data" \
-X POST -H "Content-Type: application/json" \
-d '{ "p_json": [ { "data_text": "one", "data_int": "1" }, { "data_text": "two", "data_int": "2" } ] }'
Functions with a single unnamed JSON parameter
----------------------------------------------
If you want the JSON request body to be sent as a single argument, you can create a function with a single unnamed ``json`` or ``jsonb`` parameter.
For this the ``Content-Type: application/json`` header must be included in the request.
.. code-block:: postgres
CREATE FUNCTION mult_them(json) RETURNS int AS $$
SELECT ($1->>'x')::int * ($1->>'y')::int
$$ LANGUAGE SQL;
.. code-block:: bash
curl "http://localhost:3000/rpc/mult_them" \
-X POST -H "Content-Type: application/json" \
-d '{ "x": 4, "y": 2 }'
.. code-block:: json
8
.. note::
If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
.. _function_single_unnamed:
Functions with a single unnamed parameter
-----------------------------------------
You can make a POST request to a function with a single unnamed parameter to send raw ``bytea``, ``text`` or ``xml`` data.
To send raw XML, the parameter type must be ``xml`` and the header ``Content-Type: text/xml`` must be included in the request.
To send raw binary, the parameter type must be ``bytea`` and the header ``Content-Type: application/octet-stream`` must be included in the request.
.. code-block:: postgres
CREATE TABLE files(blob bytea);
CREATE FUNCTION upload_binary(bytea) RETURNS void AS $$
INSERT INTO files(blob) VALUES ($1);
$$ LANGUAGE SQL;
.. code-block:: bash
curl "http://localhost:3000/rpc/upload_binary" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary "@file_name.ext"
.. code-block:: http
HTTP/1.1 200 OK
[ ... ]
To send raw text, the parameter type must be ``text`` and the header ``Content-Type: text/plain`` must be included in the request.
.. _functions_array:
Functions with array parameters
-------------------------------
You can call a function that takes an array parameter:
.. code-block:: postgres
create function plus_one(arr int[]) returns int[] as $$
SELECT array_agg(n + 1) FROM unnest($1) AS n;
$$ language sql;
.. code-block:: bash
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{"arr": [1,2,3,4]}'
.. code-block:: json
[2,3,4,5]
For calling the function with GET, you can pass the array as an `array literal <https://www.postgresql.org/docs/current/arrays.html#ARRAYS-INPUT>`_,
as in ``{1,2,3,4}``. Note that the curly brackets have to be urlencoded(``{`` is ``%7B`` and ``}`` is ``%7D``).
.. code-block:: bash
curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D'"
.. note::
For versions prior to PostgreSQL 10, to pass a PostgreSQL native array on a POST payload, you need to quote it and use an array literal:
.. code-block:: bash
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{ "arr": "{1,2,3,4}" }'
In these versions we recommend using function parameters of type JSON to accept arrays from the client.
.. _functions_variadic:
Variadic functions
------------------
You can call a variadic function by passing a JSON array in a POST request:
.. code-block:: postgres
create function plus_one(variadic v int[]) returns int[] as $$
SELECT array_agg(n + 1) FROM unnest($1) AS n;
$$ language sql;
.. code-block:: bash
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{"v": [1,2,3,4]}'
.. code-block:: json
[2,3,4,5]
In a GET request, you can repeat the same parameter name:
.. code-block:: bash
curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4"
Repeating also works in POST requests with ``Content-Type: application/x-www-form-urlencoded``:
.. code-block:: bash
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
-d 'v=1&v=2&v=3&v=4'
.. _table_functions:
Table-Valued Functions
----------------------
A function that returns a table type can be filtered using the same filters as :ref:`tables and views <tables_views>`. They can also use :ref:`Resource Embedding <function_embed>`.
.. code-block:: postgres
CREATE FUNCTION best_films_2017() RETURNS SETOF films ..
.. code-block:: bash
curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)"
.. code-block:: bash
curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc"
.. _function_inlining:
Function Inlining
~~~~~~~~~~~~~~~~~
A function that follows the `rules for inlining <https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions#Inlining_conditions_for_table_functions>`_ will also inline :ref:`filters <h_filter>`, :ref:`order <ordering>` and :ref:`limits <limits>`.
For example, for the following function:
.. code-block:: postgres
create function getallprojects() returns setof projects
language sql stable
as $$
select * from projects;
$$;
Let's get its :ref:`explain_plan` when calling it with filters applied:
.. code-block:: bash
curl "http://localhost:3000/rpc/getallprojects?id=eq.1" \
-H "Accept: application/vnd.pgrst.plan"
.. code-block:: postgres
Aggregate (cost=8.18..8.20 rows=1 width=112)
-> Index Scan using projects_pkey on projects (cost=0.15..8.17 rows=1 width=40)
Index Cond: (id = 1)
Notice there's no "Function Scan" node in the plan, which tells us it has been inlined.
.. _scalar_functions:
Scalar functions
----------------
PostgREST will detect if the function is scalar or table-valued and will shape the response format accordingly:
.. code-block:: bash
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
.. code-block:: json
3
.. code-block:: bash
curl "http://localhost:3000/rpc/best_films_2017"
.. code-block:: json
[
{ "title": "Okja", "rating": 7.4},
{ "title": "Call me by your name", "rating": 8},
{ "title": "Blade Runner 2049", "rating": 8.1}
]
To manually choose a return format such as binary, see :ref:`custom_media`.
.. _untyped_functions:
Untyped functions
-----------------
Functions that return ``record`` or ``SETOF record`` are supported:
.. code-block:: postgres
create function projects_setof_record() returns setof record as $$
select * from projects;
$$ language sql;
.. code-block:: bash
curl "http://localhost:3000/rpc/projects_setof_record"
.. code-block:: json
[{"id":1,"name":"Windows 7","client_id":1},
{"id":2,"name":"Windows 10","client_id":1},
{"id":3,"name":"IOS","client_id":2}]
However note that they will fail when trying to use :ref:`v_filter` and :ref:`h_filter` on them.
So while they can be used for quick tests, it's recommended to always choose a strict return type for the function.
Overloaded functions
--------------------
You can call overloaded functions with different number of arguments.
.. code-block:: postgres
CREATE FUNCTION rental_duration(customer_id integer) ..
CREATE FUNCTION rental_duration(customer_id integer, from_date date) ..
.. code-block:: bash
curl "http://localhost:3000/rpc/rental_duration?customer_id=232"
.. code-block:: bash
curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01"
.. important::
Overloaded functions with the same argument names but different types are not supported.

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