Compare commits

...
63 Commits
Author SHA1 Message Date
Laurence IslaandGitHub 39e3246623 Upgrade protolude to 0.3.1 (#2306) 2022-06-08 16:15:56 -05:00
steve-chavez 120ed06134 bump version to 9.0.1
* Format CHANGELOG
2022-06-03 22:19:16 -05:00
steve-chavez a8452e7a2e fix: using CPU while idle 2022-06-03 22:19:16 -05:00
steve-chavez f7a2220159 fix: disable parallel GC for perf on high-core CPU 2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez d8085d41fb ci: run on branches starting with "rel-" 2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez 13d343bdbf ci: Run ARM builds on releases 2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez 6fc47aa525 ci: Fix Windows build using Stack
Added PostgreSQL binaries to the PATH and removed the use of msys2-keyring
2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 02eb3f0612 build(deps): bump codecov/codecov-action from 3.0.0 to 3.1.0
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez 3119c52cb7 ci: remove armv7 architectures for ARM builds
Now only builds aarch64 on the remote server without Docker
2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez 74678391c6 Add CI for ARM architectures (#2127) 2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez d16972e8da Fix misleading disambiguation error where relationship looks like valid syntax
* Add columns for the m2m relationship
2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 0f5caa50c6 build(deps): bump codecov/codecov-action from 2.1.0 to 3.0.0
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.1.0 to 3.0.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v2.1.0...v3.0.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 176db06168 build(deps): bump actions/cache from 2.1.7 to 3
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.7 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.7...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez 3ee3bcd7ae Bypass FreeBSD builds due to timeouts 2022-06-03 22:19:16 -05:00
Ezequiel AlvarezandSteve Chavez 113c487db2 fix: json/jsonb columns should not have type "string" in OpenAPI spec (#2203)
* Switch to no type for json/jsonb
2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 6876fbb0aa build(deps): bump actions/checkout from 2.4.0 to 3 (#2186) 2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez b8b486740c build(deps): bump actions/upload-artifact from 2.3.1 to 3 (#2187) 2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 83496fbcb8 build(deps): bump actions/download-artifact from 2.1.0 to 3 (#2185) 2022-06-03 22:19:16 -05:00
Laurence IslaandSteve Chavez ebc26d0c09 fix: Using GET with certain Content-Type headers now correctly calls the no parameter function if it exists
Using GET with text/plain or application/octet-stream as Content-Type headers no longer returns 404 Not Found when a function with no parameters exists
2022-06-03 22:19:16 -05:00
Steve Chavez dd8a2c8979 fix: Keep working when EMFILE is reached (#2158)
Done by upgrading to warp 3.3.19
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 7cc8502a64 fix: Remove aggregates, procedures and window functions from schema cache and OpenAPI output
Aggregates and Window functions can't be called as RPCs in a useful way.

Procedures are not supported right now, but might be added later.

Resolves #2101

Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 5a88161ff7 fix: Take PG version into account in --dump-schema
The PG version is only read by the Connection Worker, which is not used in the case dump-schema. Now, the pg version is read in the schema cache queries directly, avoiding this problem in all cases.

Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 973c22a102 ci: Fix windows build failing because of outdated keyring
Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 2c001010df fix: Remove trigger functions from schema cache and OpenAPI output
Trigger functions can't be called directly from SQL and can't be called
via the /rpc prefix either - it makes no sense to expose them in the
OpenAPI output. And we don't need to cache them in the schema cache
either.

Best practice would be to keep the trigger functions in a non-exposed
schema anyway.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez a36c5ce52d ci: Add -with-rtsopts=-KxK option to prevent introducing space leaks
Resolves #387
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 8bfbf9eb89 Align columns in postgrest.cabal 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 2e334db31b fix: Read database configuration properly when = is present in value
Resolves #2120
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez d1851a6966 refactor: Reformat db-config query 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 76fdf29f5c refactor: Move each Wai Middleware to a separate file 2022-06-03 22:19:16 -05:00
Steve Chavez 490f631011 cov: empty coverage overlay (#2119)
The overlay depended on the version number, which caused errors when upgrading.

Also update cabal description.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 685acccf6d test: Reorganize test/ folder into one subdirectory for each test type 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 9378de558f Remove unused dependencies from postgrest.cabal 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 747b0234ae cov: Add coverage overlay for impossible case in corsPolicy 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez ce578ec1b8 refactor: simplify logger middleware 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 7272d8894c cov: Remove unreachable static icon 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez de8253facc cov: Add tests for request logging 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez a15118576d test: Remove unused unix-socket.config file
Unix sockets are the default in io tests and we are testing ip connections explicitly.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 0a44125d92 test: Improve io test perfomance
Local test improves performance by 15%.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 9c9e25b2af test: Move io-tests SQL fixtures to a separate file 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez ce6ea74ee1 test: Improve log output for failing io-tests 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez e8e3349a71 test: Refactor invalid_role_claim_key_notify_reload test to properly read all available input 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 06a9794448 test: Use .encode() end .decode default values in io-tests
Those default to "utf-8" anyway.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 30a6ae6241 nix: Fix nested postgrest-with-postgresql-x calls when -f is used 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez c228c37619 Remove outdated heroku manifest 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 9f09a6547d cov: Improve jwt claims test-cases 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 130dcc4f66 fix: Fix wrong CORS header Authentication -> Authorization
Also refactors defaultCorsPolicy and corsPolicy and cleans up CORS tests
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 7db4a75146 fix: Make recursive view parsing work with XMLTABLE + DEFAULT 2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez eedfe957b5 build(deps): bump actions/upload-artifact from 2.3.0 to 2.3.1 (#2094) 2022-06-03 22:19:16 -05:00
Steve Chavez ef240ef05a fix: is not working with upper/mixed case values (#2081) 2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 89e93ef6f5 build(deps): bump actions/upload-artifact from 2.2.4 to 2.3.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.2.4 to 2.3.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2.2.4...v2.3.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 22:19:16 -05:00
dependabot[bot]andSteve Chavez 1307d00df2 build(deps): bump actions/download-artifact from 2.0.10 to 2.1.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2.0.10 to 2.1.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v2.0.10...v2.1.0)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 9c7e6070f0 fix: Dump db-config option without quotes as it's a boolean 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 3c8e309a32 nix(feat): Add postgrest-git-hooks to enable pre-commit and pre-push hooks 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 7940e73a12 nix: Add weeder to postgrest-coverage
Removes a bit of dead code from SpecHelper.hs
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 9b397878dc refactor: Make asJsonSingleF "safe", by accessing only the first element of a json_agg result 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez eda59c241e fix: Execute deferred constraint triggers when using Prefer: tx=rollback
Resolves #2020
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 4d64aae870 fix: Link to latest docs in pre-release openapi output
Resolves #2018
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez e337891c89 ci: Use matrix strategy to test against postgres versions 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez d8f03e37ae ci: Run IO tests against all postgres versions
Resolves #1820.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 0d99789938 ci: Disable update of dockerhub description on release
This is not allowed via personal access token right now and could be re-enabled in the future, once solved by docker hub.
2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 88674c5af6 nix(feat): Add bash completion for git references 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez 1aebe62a03 nix(feat): Add postgrest-loadtest 2022-06-03 22:19:16 -05:00
Wolfgang WaltherandSteve Chavez f7b3608d4e nix(feat): Add postgrest-with-git and postgrest-with-pgrst tools 2022-06-03 22:19:16 -05:00
147 changed files with 1812 additions and 745 deletions
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# This script builds PostgREST in a remote ARM server
[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; }
[ -z "$2" ] && { echo "Missing 2nd argument: Build environment directory name"; exit 1; }
PGRST_GITHUB_COMMIT="$1"
SCRIPT_DIR="$2"
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
install_packages() {
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y git build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 llvm libnuma-dev zlib1g-dev libpq-dev jq gcc
sudo apt-get clean
}
install_ghcup() {
export BOOTSTRAP_HASKELL_NONINTERACTIVE=1
export BOOTSTRAP_HASKELL_MINIMAL=1
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
source ~/.ghcup/env
}
install_cabal() {
ghcup upgrade
ghcup install cabal 3.6.0.0
ghcup set cabal 3.6.0.0
}
install_ghc() {
ghcup install ghc 8.10.7
ghcup set ghc 8.10.7
}
install_packages
# Add ghcup to the PATH for this session
[ -f ~/.ghcup/env ] && source ~/.ghcup/env
ghcup --version || install_ghcup
cabal --version || install_cabal
ghc --version || install_ghc
cd ~/$SCRIPT_DIR
# Clone the repository and build the project
git clone https://github.com/PostgREST/postgrest.git
cd postgrest
git checkout $PGRST_GITHUB_COMMIT
cabal v2-update && cabal v2-build
# Copy the built binary to the Dockerfile directory
PGRST_BIN=$(cabal exec which postgrest | tail -1)
cp $PGRST_BIN ~/$DOCKER_BUILD_DIR
# Move and compress the built binary
mkdir -p ~/$SCRIPT_DIR/result
mv $PGRST_BIN ~/$SCRIPT_DIR/result
cd ~/$SCRIPT_DIR
tar -cJf result.tar.xz result
+16
View File
@@ -0,0 +1,16 @@
# PostgREST docker hub image
FROM ubuntu:focal AS postgrest
RUN apt-get update -y \
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY postgrest /usr/bin/postgrest
EXPOSE 3000
USER 1000
CMD postgrest
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# This script publishes the Docker ARM images to Docker Hub.
[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; }
[ -z "$2" ] && { echo "Missing 2nd argument: Docker repo"; exit 1; }
[ -z "$3" ] && { echo "Missing 3rd argument: Docker username"; exit 1; }
[ -z "$4" ] && { echo "Missing 4th argument: Docker password"; exit 1; }
[ -z "$5" ] && { echo "Missing 5th argument: Build environment directory name"; exit 1; }
[ -z "$6" ] && { echo "Missing 6th argument: PostgREST version"; exit 1; }
PGRST_GITHUB_COMMIT="$1"
DOCKER_REPO="$2"
DOCKER_USER="$3"
DOCKER_PASS="$4"
SCRIPT_DIR="$5"
PGRST_VERSION="v$6"
IS_PRERELEASE="$7"
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
clean_env()
{
sudo docker logout
}
# Login to Docker
sudo docker logout
{ echo $DOCKER_PASS | sudo docker login -u $DOCKER_USER --password-stdin; } || { echo "Couldn't login to docker"; exit 1; }
trap clean_env sigint sigterm exit
# Move to the docker build environment
cd ~/$DOCKER_BUILD_DIR
# Push final images to Docker hub
# NOTE: This command publishes a separate ARM image because the builds cannot
# be added to the manifest if they are not in the registry beforehand.
# This image must be manually deleted from Docker Hub at the end of the process.
sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \
-t $DOCKER_REPO/postgrest:$PGRST_VERSION-arm \
--push .
# Add the arm images to the manifest
# NOTE: This assumes that there already is a `postgrest:<version>` image
# for the amd64 architecture pushed to Docker Hub
sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:$PGRST_VERSION $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
[ -z $IS_PRERELEASE ] && sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:latest $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
sudo docker logout
+171 -59
View File
@@ -4,18 +4,20 @@ on:
push: push:
branches: branches:
- main - main
- rel-*
tags: tags:
- v* - v*
pull_request: pull_request:
branches: branches:
- main - main
- rel-*
jobs: jobs:
Lint-Style: Lint-Style:
name: Lint & check code style name: Lint & check code style
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Setup Nix Environment - name: Setup Nix Environment
uses: ./.github/actions/setup-nix uses: ./.github/actions/setup-nix
with: with:
@@ -35,39 +37,19 @@ jobs:
# https://github.com/actions/runner/issues/241#issuecomment-842566950 # https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}" shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Setup Nix Environment - name: Setup Nix Environment
uses: ./.github/actions/setup-nix uses: ./.github/actions/setup-nix
with: with:
tools: tests withTools tools: tests
- name: Run coverage (IO tests and Spec tests against PostgreSQL 14) - name: Run coverage (IO tests and Spec tests against PostgreSQL 14)
run: postgrest-coverage run: postgrest-coverage
- name: Upload coverage to codecov - name: Upload coverage to codecov
uses: codecov/codecov-action@v2.1.0 uses: codecov/codecov-action@v3.1.0
with: with:
files: ./coverage/codecov.json files: ./coverage/codecov.json
- name: Run the spec tests against PostgreSQL 13
if: always()
run: postgrest-with-postgresql-13 postgrest-test-spec
- name: Run the spec tests against PostgreSQL 12
if: always()
run: postgrest-with-postgresql-12 postgrest-test-spec
- name: Run the spec tests against PostgreSQL 11
if: always()
run: postgrest-with-postgresql-11 postgrest-test-spec
- name: Run the spec tests against PostgreSQL 10
if: always()
run: postgrest-with-postgresql-10 postgrest-test-spec
- name: Run the spec tests against PostgreSQL 9.6
if: always()
run: postgrest-with-postgresql-9.6 postgrest-test-spec
- name: Run query cost tests against all PostgreSQL versions
if: always()
run: postgrest-with-all postgrest-test-querycost
- name: Run doctests - name: Run doctests
if: always() if: always()
run: nix-shell --run postgrest-test-doctests run: nix-shell --run postgrest-test-doctests
@@ -77,11 +59,43 @@ jobs:
run: postgrest-test-spec-idempotence run: postgrest-test-spec-idempotence
Test-Pg-Nix:
strategy:
fail-fast: false
matrix:
pgVersion: [9.6, 10, 11, 12, 13, 14]
name: Test PG ${{ matrix.pgVersion }} (Nix)
runs-on: ubuntu-latest
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@v3
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: tests withTools
- name: Run spec tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
- name: Run IO tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} -f test/io/fixtures.sql postgrest-test-io
- name: Run query cost tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-querycost
Test-Memory-Nix: Test-Memory-Nix:
name: Test memory (Nix) name: Test memory (Nix)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Setup Nix Environment - name: Setup Nix Environment
uses: ./.github/actions/setup-nix uses: ./.github/actions/setup-nix
with: with:
@@ -90,11 +104,11 @@ jobs:
run: postgrest-test-memory run: postgrest-test-memory
Build-Nix: Build-Static-Nix:
name: Build Linux static (Nix) name: Build Linux static (Nix)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Setup Nix Environment - name: Setup Nix Environment
uses: ./.github/actions/setup-nix uses: ./.github/actions/setup-nix
with: with:
@@ -103,7 +117,7 @@ jobs:
- name: Build static executable - name: Build static executable
run: nix-build -A postgrestStatic run: nix-build -A postgrestStatic
- name: Save built executable as artifact - name: Save built executable as artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v3
with: with:
name: postgrest-linux-static-x64 name: postgrest-linux-static-x64
path: result/bin/postgrest path: result/bin/postgrest
@@ -112,7 +126,7 @@ jobs:
- name: Build Docker image - name: Build Docker image
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
- name: Save built Docker image as artifact - name: Save built Docker image as artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v3
with: with:
name: postgrest-docker-x64 name: postgrest-docker-x64
path: postgrest-docker.tar.gz path: postgrest-docker.tar.gz
@@ -155,8 +169,7 @@ jobs:
~\AppData\Roaming\stack ~\AppData\Roaming\stack
~\AppData\Local\Programs\stack ~\AppData\Local\Programs\stack
.stack-work .stack-work
deps: | deps: Add-Content $env:GITHUB_PATH $env:PGBIN
stack exec -- pacman -S mingw64/mingw-w64-x86_64-postgresql --noconfirm
# We'd need to make test/with_tmp_db run on Windows first # We'd need to make test/with_tmp_db run on Windows first
# test: true # test: true
artifact: postgrest-windows-x64 artifact: postgrest-windows-x64
@@ -164,9 +177,9 @@ jobs:
name: Build ${{ matrix.name }} (Stack) name: Build ${{ matrix.name }} (Stack)
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Stack working files cache - name: Stack working files cache
uses: actions/cache@v2.1.7 uses: actions/cache@v3
with: with:
path: ${{ matrix.cache }} path: ${{ matrix.cache }}
key: ${{ runner.os }}-${{ hashFiles('stack.yaml.lock') }} key: ${{ runner.os }}-${{ hashFiles('stack.yaml.lock') }}
@@ -182,7 +195,7 @@ jobs:
echo "Using PostgreSQL binaries at $postgresql_bin ..." echo "Using PostgreSQL binaries at $postgresql_bin ..."
PATH="$postgresql_bin:$PATH" test/with_tmp_db stack test PATH="$postgresql_bin:$PATH" test/with_tmp_db stack test
- name: Save built executable as artifact - name: Save built executable as artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.artifact }} name: ${{ matrix.artifact }}
path: | path: |
@@ -190,24 +203,78 @@ jobs:
result/postgrest.exe result/postgrest.exe
if-no-files-found: error if-no-files-found: error
Get-FreeBSD-CirrusCI: Get-FreeBSD-CirrusCI:
name: Get FreeBSD build from CirrusCI name: Get FreeBSD build from CirrusCI
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Get FreeBSD executable from CirrusCI - name: Get FreeBSD executable from CirrusCI
env: env:
# GITHUB_SHA does weird things for pull request, so we roll our own: # GITHUB_SHA does weird things for pull request, so we roll our own:
GITHUB_COMMIT: ${{github.event.pull_request.head.sha || github.sha}} GITHUB_COMMIT: ${{github.event.pull_request.head.sha || github.sha}}
run: .github/get_cirrusci_freebsd run: .github/get_cirrusci_freebsd
- name: Save executable as artifact - name: Save executable as artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v3
with: with:
name: postgrest-freebsd-x64 name: postgrest-freebsd-x64
path: postgrest path: postgrest
if-no-files-found: error if-no-files-found: error
Build-Cabal-Arm:
name: Build aarch64 (Cabal)
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/rel-') }}
runs-on: ubuntu-latest
outputs:
remotepath: ${{ steps.Remote-Dir.outputs.remotepath }}
env:
GITHUB_COMMIT: ${{ github.sha }}
steps:
- uses: actions/checkout@v2.4.0
- id: Remote-Dir
name: Unique directory name for the remote build
run: echo "::set-output name=remotepath::postgrest-build-$(uuidgen)"
- name: Copy script files to the remote server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SSH_ARM_HOST }}
username: ubuntu
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
source: ".github/scripts/arm/*"
target: ${{ steps.Remote-Dir.outputs.remotepath }}
strip_components: 3
- name: Build ARM
uses: appleboy/ssh-action@master
env:
REMOTE_DIR: ${{ steps.Remote-Dir.outputs.remotepath }}
with:
host: ${{ secrets.SSH_ARM_HOST }}
username: ubuntu
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
command_timeout: 120m
script_stop: true
envs: GITHUB_COMMIT,REMOTE_DIR
script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$REMOTE_DIR"
- name: Download binaries from remote server
uses: nicklasfrahm/scp-action@main
with:
direction: download
host: ${{ secrets.SSH_ARM_HOST }}
username: ubuntu
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
source: "${{ steps.Remote-Dir.outputs.remotepath }}/result.tar.xz"
target: "result.tar.xz"
- name: Extract downloaded binaries
run: tar -xvf result.tar.xz && rm result.tar.xz
- name: Save aarch64 executable as artifact
uses: actions/upload-artifact@v2.3.1
with:
name: postgrest-ubuntu-aarch64
path: result/postgrest
if-no-files-found: error
Prepare-Release: Prepare-Release:
name: Prepare release name: Prepare release
@@ -216,15 +283,17 @@ jobs:
needs: needs:
- Lint-Style - Lint-Style
- Test-Nix - Test-Nix
- Test-Pg-Nix
- Test-Memory-Nix - Test-Memory-Nix
- Build-Nix - Build-Static-Nix
- Build-Stack - Build-Stack
- Get-FreeBSD-CirrusCI #- Get-FreeBSD-CirrusCI
- Build-Cabal-Arm
outputs: outputs:
version: ${{ steps.Identify-Version.outputs.version }} version: ${{ steps.Identify-Version.outputs.version }}
isprerelease: ${{ steps.Identify-Version.outputs.isprerelease }} isprerelease: ${{ steps.Identify-Version.outputs.isprerelease }}
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- id: Identify-Version - id: Identify-Version
name: Identify the version to be released name: Identify the version to be released
run: | run: |
@@ -261,7 +330,7 @@ jobs:
echo "Relevant extract from CHANGELOG.md:" echo "Relevant extract from CHANGELOG.md:"
cat CHANGES.md cat CHANGES.md
- name: Save CHANGES.md as artifact - name: Save CHANGES.md as artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v3
with: with:
name: release-changes name: release-changes
path: CHANGES.md path: CHANGES.md
@@ -277,9 +346,9 @@ jobs:
env: env:
VERSION: ${{ needs.Prepare-Release.outputs.version }} VERSION: ${{ needs.Prepare-Release.outputs.version }}
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Download all artifacts - name: Download all artifacts
uses: actions/download-artifact@v2.0.10 uses: actions/download-artifact@v3
with: with:
path: artifacts path: artifacts
- name: Create release bundle with archives for all builds - name: Create release bundle with archives for all builds
@@ -299,14 +368,18 @@ jobs:
tar cJvf "release-bundle/postgrest-v$VERSION-macos-x64.tar.xz" \ tar cJvf "release-bundle/postgrest-v$VERSION-macos-x64.tar.xz" \
-C artifacts/postgrest-macos-x64 postgrest -C artifacts/postgrest-macos-x64 postgrest
tar cJvf "release-bundle/postgrest-v$VERSION-freebsd-x64.tar.xz" \ # TODO: Fix timeouts for FreeBSD builds in Cirrus
-C artifacts/postgrest-freebsd-x64 postgrest #tar cJvf "release-bundle/postgrest-v$VERSION-freebsd-x64.tar.xz" \
# -C artifacts/postgrest-freebsd-x64 postgrest
tar cJvf "release-bundle/postgrest-v$VERSION-ubuntu-aarch64.tar.xz" \
-C artifacts/postgrest-ubuntu-aarch64 postgrest
zip "release-bundle/postgrest-v$VERSION-windows-x64.zip" \ zip "release-bundle/postgrest-v$VERSION-windows-x64.zip" \
artifacts/postgrest-windows-x64/postgrest.exe artifacts/postgrest-windows-x64/postgrest.exe
- name: Save release bundle - name: Save release bundle
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v3
with: with:
name: release-bundle name: release-bundle
path: release-bundle path: release-bundle
@@ -329,25 +402,29 @@ jobs:
Release-Docker: Release-Docker:
name: Release on Docker Hub name: Release on Docker Hub
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: Prepare-Release needs:
- Build-Cabal-Arm
- Prepare-Release
env: env:
GITHUB_COMMIT: ${{ github.sha }}
DOCKER_REPO: postgrest DOCKER_REPO: postgrest
DOCKER_USER: stevechavez DOCKER_USER: stevechavez
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
VERSION: ${{ needs.Prepare-Release.outputs.version }} VERSION: ${{ needs.Prepare-Release.outputs.version }}
ISPRERELEASE: ${{ needs.Prepare-Release.outputs.isprerelease }} ISPRERELEASE: ${{ needs.Prepare-Release.outputs.isprerelease }}
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Setup Nix Environment - name: Setup Nix Environment
uses: ./.github/actions/setup-nix uses: ./.github/actions/setup-nix
with: with:
tools: release tools: release
- name: Download Docker image - name: Download Docker image
uses: actions/download-artifact@v2.0.10 uses: actions/download-artifact@v3
with: with:
name: postgrest-docker-x64 name: postgrest-docker-x64
- name: Publish images on Docker Hub - name: Publish images on Docker Hub
run: | run: |
docker login -u "$DOCKER_USER" -p "${{ secrets.DOCKER_PASS }}" docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
docker load -i postgrest-docker.tar.gz docker load -i postgrest-docker.tar.gz
docker tag postgrest:latest "$DOCKER_REPO/postgrest:v$VERSION" docker tag postgrest:latest "$DOCKER_REPO/postgrest:v$VERSION"
@@ -361,13 +438,48 @@ jobs:
else else
echo "Skipping pushing to 'latest' tag for v$VERSION pre-release..." echo "Skipping pushing to 'latest' tag for v$VERSION pre-release..."
fi fi
- name: Update descriptions on Docker Hub - name: Publish images for ARM builds on Docker Hub
uses: appleboy/ssh-action@master
env: env:
DOCKER_PASS: ${{ secrets.DOCKER_PASS }} REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }}
run: | with:
if [[ -z "$ISPRERELEASE" ]]; then host: ${{ secrets.SSH_ARM_HOST }}
echo "Updating description on Docker Hub..." username: ubuntu
postgrest-release-dockerhub-description key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
else fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
echo "Skipping updating description for pre-release..." script_stop: true
fi envs: GITHUB_COMMIT,DOCKER_REPO,DOCKER_USER,DOCKER_PASS,REMOTE_DIR,VERSION,ISPRERELEASE
script: bash ~/$REMOTE_DIR/docker-publish.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" "$VERSION" "$ISPRERELEASE"
# TODO: Enable dockerhub description update again, once a solution for the permission problem is found:
# https://github.com/docker/hub-feedback/issues/1927
# - name: Update descriptions on Docker Hub
# env:
# DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
# run: |
# if [[ -z "$ISPRERELEASE" ]]; then
# echo "Updating description on Docker Hub..."
# postgrest-release-dockerhub-description
# else
# echo "Skipping updating description for pre-release..."
# fi
Clean-Arm-Server:
name: Remove copied files from server
needs:
- Build-Cabal-Arm
- Release-Docker
if: ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/rel-')) }}
runs-on: ubuntu-latest
env:
REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }}
steps:
- uses: actions/checkout@v2.4.0
- name: Remove uploaded files from server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_ARM_HOST }}
username: ubuntu
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
envs: REMOTE_DIR
script: rm -rf $REMOTE_DIR
+34
View File
@@ -0,0 +1,34 @@
name: Loadtest
on:
push:
branches:
- main
tags:
- v*
pull_request:
branches:
- main
jobs:
Loadtest-Nix:
name: Loadtest (Nix)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: loadtest
- name: Run loadtest
run: |
postgrest-loadtest-against main
postgrest-loadtest-report > loadtest/loadtest.md
- name: Upload report
uses: actions/upload-artifact@v3
with:
name: loadtest.md
path: loadtest/loadtest.md
if-no-files-found: error
+1
View File
@@ -21,3 +21,4 @@ __pycache__
*.tix *.tix
coverage coverage
.hpc .hpc
loadtest
+17 -3
View File
@@ -3,12 +3,26 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## [9.0.1] - 2022-06-03
### Added
### Fixed ### Fixed
- #2165, Fix json/jsonb columns should not have type in OpenAPI spec - @clrnd
- #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther
- #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez
- #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther
- #1724, Fix wrong CORS header Authentication -> Authorization - @wolfgangwalther
- #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther
- #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther
- #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther
- #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther
- #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez
- #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. - @laurenceisla
+ Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available.
- #2239, Fix misleading disambiguation error where the content of the `relationship` key looks like valid syntax - @laurenceisla
- #2294, Disable parallel GC for better performance on higher core CPUs - @steve-chavez
- #1076, Fix using CPU while idle - @steve-chavez
## [9.0.0] - 2021-11-25 ## [9.0.0] - 2021-11-25
### Added ### Added
-3
View File
@@ -2,9 +2,6 @@
[![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)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/postgrest) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/postgrest)
<a href="https://heroku.com/deploy?template=https://github.com/PostgREST/postgrest">
<img src="https://img.shields.io/badge/%E2%86%91_Deploy_to-Heroku-7056bf.svg" alt="Deploy">
</a>
[![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/postgrest) [![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/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)
[![Docker Stars](https://img.shields.io/docker/pulls/postgrest/postgrest.svg)](https://hub.docker.com/r/postgrest/postgrest/) [![Docker Stars](https://img.shields.io/docker/pulls/postgrest/postgrest.svg)](https://hub.docker.com/r/postgrest/postgrest/)
-59
View File
@@ -1,59 +0,0 @@
{
"name": "PostgREST",
"description": "RESTful API for any PostgreSQL database.",
"logo": "https://avatars2.githubusercontent.com/u/15115011",
"repository": "https://github.com/PostgREST/postgrest",
"env": {
"BUILDPACK_URL": {
"description": "Heroku buildpack for deploying Haskell applications",
"value": "https://github.com/PostgREST/postgrest-heroku"
},
"POSTGREST_VER": {
"description": "Version of PostgREST to deploy",
"value": "8.0.0"
},
"DB_URI": {
"description": "Database connection string, e.g. postgres://user:pass@xxxxxxx.rds.amazonaws.com/mydb",
"required": true
},
"DB_SCHEMA": {
"description": "The database schema to expose to REST clients. Tables, views and stored procedures in this schema will get API endpoints",
"required": true,
"value": "public"
},
"DB_ANON_ROLE": {
"description": "The database role to use when executing commands on behalf of unauthenticated clients",
"required": true
},
"DB_POOL": {
"description": "Number of connections to keep open in PostgRESTs database pool",
"required": false,
"value": "10"
},
"SERVER_PROXY_URI": {
"description": "Overrides the base URL used within the OpenAPI self-documentation hosted at the API root path",
"required": false
},
"JWT_SECRET": {
"description": "The secret used to decode JWT tokens clients provide for authentication",
"required": false
},
"SECRET_IS_BASE64": {
"description": "When this is set to true, the value derived from jwt-secret will be treated as a base64 encoded secret",
"required": false,
"value": "false"
},
"JWT_AUD": {
"description": "The audience that should be validated if the JWT token contains an aud claim",
"required": false
},
"MAX_ROWS": {
"description": "A hard limit to the number of rows PostgREST will fetch from a view, table, or stored procedure",
"required": false
},
"PRE_REQUEST": {
"description": "A schema-qualified stored procedure name to call right after switching roles for a client request",
"required": false
}
}
}
+5 -1
View File
@@ -123,6 +123,10 @@ rec {
docker = docker =
pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; }; pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; };
# Load testing tools.
loadtest =
pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; };
# Script for running memory tests. # Script for running memory tests.
memory = memory =
pkgs.callPackage nix/tools/memory.nix { inherit postgrestProfiled withTools; }; pkgs.callPackage nix/tools/memory.nix { inherit postgrestProfiled withTools; };
@@ -148,5 +152,5 @@ rec {
}; };
withTools = withTools =
pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions; }; pkgs.callPackage nix/tools/withTools.nix { inherit devCabalOptions postgresqlVersions postgrest; };
} }
+1 -1
View File
@@ -149,7 +149,7 @@ the PostgREST repo. Paths are resolved relative to the repo root:
$ cd src $ cd src
# Even though the current directory is ./src, the config path must still start # Even though the current directory is ./src, the config path must still start
# from the repo root: # from the repo root:
$ postgrest-run test/io-tests/configs/simple.conf $ postgrest-run test/io/configs/simple.conf
``` ```
## Testing ## Testing
@@ -14,7 +14,7 @@
{ name { name
, docs , docs
, args ? [ ] , args ? [ ]
, addCommandCompletion ? false , positionalCompletion ? ""
, inRootDir ? false , inRootDir ? false
, redirectTixFiles ? true , redirectTixFiles ? true
, withEnv ? null , withEnv ? null
@@ -22,11 +22,10 @@
, withTmpDir ? false , withTmpDir ? false
}: text: }: text:
let let
# square brackets are a pain to escape - if even possible. just don't use them...
escape = builtins.replaceStrings [ "\n" ] [ " \\n" ];
argsTemplate = argsTemplate =
let
# square brackets are a pain to escape - if even possible. just don't use them...
escapedDocs = builtins.replaceStrings [ "\n" ] [ " \\n" ] docs;
in
writeTextFile { writeTextFile {
inherit name; inherit name;
destination = "/${name}.m4"; # destination is needed to have the proper basename for completion destination = "/${name}.m4"; # destination is needed to have the proper basename for completion
@@ -37,7 +36,7 @@ let
# stripping the /nix/store/... path for nicer display # stripping the /nix/store/... path for nicer display
BASH_ARGV0="$(basename "$0")" BASH_ARGV0="$(basename "$0")"
# ARG_HELP([${name}], [${escapedDocs}]) # ARG_HELP([${name}], [${escape docs}])
${lib.strings.concatMapStrings (arg: "# " + arg) args} ${lib.strings.concatMapStrings (arg: "# " + arg) args}
# ARG_POSITIONAL_DOUBLEDASH() # ARG_POSITIONAL_DOUBLEDASH()
# ARG_DEFAULTS_POS() # ARG_DEFAULTS_POS()
@@ -65,8 +64,8 @@ let
${argbash}/bin/argbash --type completion --strip all ${argsTemplate}/${name}.m4 > $out ${argbash}/bin/argbash --type completion --strip all ${argsTemplate}/${name}.m4 > $out
'' ''
+ lib.optionalString addCommandCompletion '' + lib.optionalString (positionalCompletion != "") ''
sed 's/COMPREPLY.*compgen -o bashdefault .*$/_command/' -i $out sed 's#COMPREPLY.*compgen -o bashdefault .*$#${escape positionalCompletion}#' -i $out
'' ''
); );
+48
View File
@@ -20,6 +20,54 @@ let
# To get the sha256: # To get the sha256:
# nix-prefetch-url --unpack https://hackage.haskell.org/package/protolude-0.3.0/protolude-0.3.0.tar.gz # nix-prefetch-url --unpack https://hackage.haskell.org/package/protolude-0.3.0/protolude-0.3.0.tar.gz
# To temporarily pin unreleased versions from GitHub:
# <name> =
# prev.callCabal2nixWithOptions "<name>" (super.fetchFromGitHub {
# owner = "<owner>";
# repo = "<repo>";
# rev = "<commit>";
# sha256 = "<sha256>";
# }) "--subpath=<subpath>" {};
#
# To get the sha256:
# nix-prefetch-url --unpack https://github.com/<owner>/<repo>/archive/<commit>.tar.gz
protolude =
prev.callHackageDirect
{
pkg = "protolude";
ver = "0.3.1";
sha256 = "0gf0mn1ycllr69kdq1p07qf7935s10jz0nnhynwqy3d6nmycxr5j";
}
{ };
wai-extra =
prev.callHackageDirect
{
pkg = "wai-extra";
ver = "3.1.8";
sha256 = "1ha8sxc2ii7k7xs5nm06wfwqmf4f1p2acp4ya0jnx6yn6551qps4";
}
{ };
wai-logger =
prev.callHackageDirect
{
pkg = "wai-logger";
ver = "2.3.7";
sha256 = "1d23fdbwbahr3y1vdyn57m1qhljy22pm5cpgb20dy6mlxzdb30xd";
}
{ };
warp =
lib.dontCheck (prev.callHackageDirect
{
pkg = "warp";
ver = "3.3.19";
sha256 = "0y3jj4bhviss6ff9lwxki0zbdcl1rb398bk4s80zvfpnpy7p94cx";
}
{ });
hasql-dynamic-statements = hasql-dynamic-statements =
lib.dontCheck (lib.unmarkBroken prev.hasql-dynamic-statements); lib.dontCheck (lib.unmarkBroken prev.hasql-dynamic-statements);
+149 -7
View File
@@ -4,7 +4,10 @@
, checkedShellScript , checkedShellScript
, devCabalOptions , devCabalOptions
, entr , entr
, git
, gnugrep
, graphviz , graphviz
, haskellPackages
, hsie , hsie
, nix , nix
, silver-searcher , silver-searcher
@@ -29,7 +32,7 @@ let
"ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])" "ARG_LEFTOVERS([command arguments])"
]; ];
addCommandCompletion = true; positionalCompletion = "_command";
redirectTixFiles = false; # will be done by sub-command redirectTixFiles = false; # will be done by sub-command
inRootDir = true; inRootDir = true;
} }
@@ -64,23 +67,161 @@ let
name = "postgrest-check"; name = "postgrest-check";
docs = docs =
'' ''
Run most checks that will also run on CI. Run most checks that will also run on CI, but only against the
latest PostgreSQL version.
This currently excludes the memory tests, as those are particularly This currently excludes the memory and spec-idempotence tests,
expensive. as those are particularly expensive.
''; '';
inRootDir = true; inRootDir = true;
} }
'' ''
${withTools}/bin/postgrest-with-all ${tests}/bin/postgrest-test-spec ${tests}/bin/postgrest-test-spec
${withTools}/bin/postgrest-with-all ${tests}/bin/postgrest-test-querycost ${tests}/bin/postgrest-test-querycost
${tests}/bin/postgrest-test-doctests ${tests}/bin/postgrest-test-doctests
${tests}/bin/postgrest-test-spec-idempotence
${tests}/bin/postgrest-test-io ${tests}/bin/postgrest-test-io
${style}/bin/postgrest-lint ${style}/bin/postgrest-lint
${style}/bin/postgrest-style-check ${style}/bin/postgrest-style-check
''; '';
gitHooks =
let
name = "postgrest-git-hooks";
in
checkedShellScript
{
inherit name;
docs =
''
Enable or disable git pre-commit and pre-push hooks.
Basic is faster and will only run:
- pre-commit: postgrest-style
- pre-push: postgrest-lint
Full takes a lot more time and will run:
- pre-commit: postgrest-style && postgrest-lint
- pre-push: postgrest-check
Changes made by postgrest-style will be staged automatically.
Example usage:
postgrest-git-hooks disable
postgrest-git-hooks enable basic
postgrest-git-hooks enable full
The "run" operation and "--hook" argument are only used internally.
'';
args =
[
"ARG_POSITIONAL_SINGLE([operation], [Operation])"
"ARG_TYPE_GROUP_SET([OPERATION], [OPERATION], [operation], [disable,enable,run])"
"ARG_POSITIONAL_SINGLE([mode], [Mode], [basic])"
"ARG_TYPE_GROUP_SET([MODE], [MODE], [mode], [basic,full])"
"ARG_OPTIONAL_SINGLE([hook], , [Hook], [pre-commit])"
"ARG_TYPE_GROUP_SET([HOOK], [HOOK], [hook], [pre-commit,pre-push])"
];
positionalCompletion =
''
if test "$prev" == "${name}"; then
COMPREPLY=( $(compgen -W "enable disable" -- "$cur") )
elif test "$prev" == "enable" || test "$prev" == "disable"; then
COMPREPLY=( $(compgen -W "basic full" -- "$cur") )
fi
'';
inRootDir = true;
}
''
if [ run != "$_arg_operation" ]; then
# Remove all hooks first and ignore failures because the file might be missing.
# This assumes that we're only adding lines that include "postgrest-git-hooks"
# to the hook file.
sed -i -e '/postgrest-git-hooks/d' .git/hooks/pre-{commit,push} 2> /dev/null || true
if [ disabled != "$_arg_mode" ]; then
# The nix-shell && + nix-shell || pattern makes sure we can run the hook
# in a pure nix-shell, where nix-shell itself is not available, too.
# The $(nix-shell --run "command -v ...") pattern ensures we only need to enable
# the hooks once and still run the latest of our hook scripts, even when we
# update them in the repo.
echo 'command -v nix-shell > /dev/null || postgrest-git-hooks --hook=pre-commit run' "$_arg_mode" \
>> .git/hooks/pre-commit
# shellcheck disable=SC2016
echo 'command -v nix-shell > /dev/null && $(nix-shell --quiet -Q --run "command -v postgrest-git-hooks") --hook=pre-commit run' "$_arg_mode" \
>> .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
echo 'command -v nix-shell > /dev/null || postgrest-git-hooks --hook=pre-push run' "$_arg_mode" \
>> .git/hooks/pre-push
# shellcheck disable=SC2016
echo 'command -v nix-shell > /dev/null && $(nix-shell --quiet -Q --run "command -v postgrest-git-hooks") --hook=pre-push run' "$_arg_mode" \
>> .git/hooks/pre-push
chmod +x .git/hooks/pre-push
fi
else
# When run from a git hook, the GIT_ environment variables conflict with our withGit helper.
# The following unsets all GIT_ variables.
unset "''${!GIT_@}"
case "$_arg_mode" in
basic)
case "$_arg_hook" in
pre-commit)
# To be able to automatically add only changes from postgrest-style to the staging area,
# we need to run postgrest-style twice. Otherwise we'd risk merge conflicts when popping
# the stash afterwards.
${style}/bin/postgrest-style
stash="postgrest-git-hooks-$RANDOM"
${git}/bin/git stash push --include-untracked --keep-index -m "$stash"
if [ "$(git stash list --grep $stash)" ]; then
# Only create the stash pop trap, if we actually created a stash.
# Otherwise stash pop will cause havoc.
trap '${git}/bin/git stash pop $(git stash list --format=format:%gD --grep "$stash" -n1)' EXIT
fi
${style}/bin/postgrest-style
${git}/bin/git add .
;;
pre-push)
# Create a clean working tree without any uncomitted changes.
${withTools.withGit} HEAD ${style}/bin/postgrest-lint
;;
esac
;;
full)
case "$_arg_hook" in
pre-commit)
# To be able to automatically add only changes from postgrest-style to the staging area,
# we need to run postgrest-style twice. Otherwise we'd risk merge conflicts when popping
# the stash afterwards.
${style}/bin/postgrest-style
stash="postgrest-git-hooks-$RANDOM"
${git}/bin/git stash push --include-untracked --keep-index -m "$stash"
if [ "$(git stash list --grep $stash)" ]; then
# Only create the stash pop trap, if we actually created a stash.
# Otherwise stash pop will cause havoc.
trap '${git}/bin/git stash pop $(git stash list --format=format:%gD --grep "$stash" -n1)' EXIT
fi
${style}/bin/postgrest-style
${git}/bin/git add .
${style}/bin/postgrest-lint
;;
pre-push)
# Create a clean working tree without any uncomitted changes.
${withTools.withGit} HEAD ${check}
;;
esac
;;
esac
fi
'';
dumpMinimalImports = dumpMinimalImports =
checkedShellScript checkedShellScript
{ {
@@ -146,6 +287,7 @@ buildToolbox
watch watch
pushCachix pushCachix
check check
gitHooks
dumpMinimalImports dumpMinimalImports
hsieMinimalImports hsieMinimalImports
hsieGraphModules hsieGraphModules
+174
View File
@@ -0,0 +1,174 @@
{ buildToolbox
, checkedShellScript
, jq
, python3Packages
, vegeta
, withTools
, writers
}:
let
runner =
checkedShellScript
{
name = "postgrest-loadtest-runner";
docs = "Run vegeta. Assume PostgREST to be running.";
args = [
"ARG_LEFTOVERS([additional vegeta arguments])"
"ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [Unix socket to connect to running PostgREST instance])"
];
}
''
# ARG_USE_ENV only adds defaults or docs for environment variables
# We manually implement a required check here
# See also: https://github.com/matejak/argbash/issues/80
: "''${PGRST_SERVER_UNIX_SOCKET:?PGRST_SERVER_UNIX_SOCKET is required}"
${vegeta}/bin/vegeta -cpus 1 attack \
-unix-socket "$PGRST_SERVER_UNIX_SOCKET" \
-max-workers 1 \
-workers 1 \
-rate 0 \
-duration 60s \
"''${_arg_leftovers[@]}"
'';
loadtest =
checkedShellScript
{
name = "postgrest-loadtest";
docs = "Run the vegeta loadtests with PostgREST.";
args = [
"ARG_OPTIONAL_SINGLE([output], [o], [Filename to dump json output to], [./loadtest/result.bin])"
"ARG_OPTIONAL_SINGLE([testdir], [t], [Directory to load tests and fixtures from], [./test/load])"
"ARG_LEFTOVERS([additional vegeta arguments])"
];
inRootDir = true;
}
''
export PGRST_DB_CONFIG="false"
export PGRST_DB_POOL="1"
export PGRST_DB_TX_END="rollback-allow-override"
export PGRST_LOG_LEVEL="crit"
mkdir -p "$(dirname "$_arg_output")"
# shellcheck disable=SC2145
${withTools.withPg} --fixtures "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} \
sh -c "cd \"$_arg_testdir\" && ${runner} -targets targets.http \"''${_arg_leftovers[@]}\"" \
| tee "$_arg_output" \
| ${vegeta}/bin/vegeta report -type=text
'';
loadtestAgainst =
let
name = "postgrest-loadtest-against";
in
checkedShellScript
{
inherit name;
docs =
''
Run the vegeta loadtest twice:
- once on the <target> branch
- once in the current worktree
'';
args = [
"ARG_POSITIONAL_SINGLE([target], [Commit-ish reference to compare with])"
"ARG_LEFTOVERS([additional vegeta arguments])"
];
positionalCompletion =
''
if test "$prev" == "${name}"; then
__gitcomp_nl "$(__git_refs)"
fi
'';
inRootDir = true;
}
''
cat << EOF
Running loadtest on "$_arg_target"...
EOF
# Runs the test files from the current working tree
# to make sure both tests are run with the same files.
# Save the results in the current working tree, too,
# otherwise they'd be lost in the temporary working tree
# created by withTools.withGit.
${withTools.withGit} "$_arg_target" ${loadtest} --output "$PWD/loadtest/$_arg_target.bin" --testdir "$PWD/test/load" "''${_arg_leftovers[@]}"
cat << EOF
Done running on "$_arg_target".
EOF
cat << EOF
Running loadtest on HEAD...
EOF
${loadtest} --output "$PWD/loadtest/head.bin" --testdir "$PWD/test/load" "''${_arg_leftovers[@]}"
cat << EOF
Done running on HEAD.
EOF
'';
reporter =
checkedShellScript
{
name = "postgrest-loadtest-reporter";
docs = "Create a named json report for a single result file.";
args = [
"ARG_POSITIONAL_SINGLE([file], [Filename of result to create report for])"
"ARG_LEFTOVERS([additional vegeta arguments])"
];
inRootDir = true;
}
''
${vegeta}/bin/vegeta report -type=json "$_arg_file" \
| ${jq}/bin/jq --arg branch "$(basename "$_arg_file" .bin)" '. + {branch: $branch}'
'';
toMarkdown =
writers.writePython3 "postgrest-loadtest-to-markdown"
{
libraries = [ python3Packages.pandas python3Packages.tabulate ];
}
''
import sys
import pandas as pd
pd.read_json(sys.stdin) \
.set_index('param') \
.drop(['branch', 'earliest', 'end', 'latest']) \
.convert_dtypes() \
.to_markdown(sys.stdout, floatfmt='.0f')
'';
report =
checkedShellScript
{
name = "postgrest-loadtest-report";
docs = "Create a report of all loadtest reports as markdown.";
inRootDir = true;
}
''
find loadtest -type f -iname '*.bin' -exec ${reporter} {} \; \
| ${jq}/bin/jq '[leaf_paths as $path | {param: $path | join("."), (.branch): getpath($path)}]' \
| ${jq}/bin/jq --slurp 'flatten | group_by(.param) | map(add)' \
| ${toMarkdown}
'';
in
buildToolbox {
name = "postgrest-loadtest";
tools = [ loadtest loadtestAgainst report ];
}
+1 -1
View File
@@ -17,7 +17,7 @@ let
withPath = [ postgrestProfiled curl ]; withPath = [ postgrestProfiled curl ];
} }
'' ''
${withTools.latest} test/memory-tests.sh ${withTools.withPg} test/memory/memory-tests.sh
''; '';
in in
+2
View File
@@ -44,6 +44,8 @@ let
'' ''
${style} ${style}
trap "echo postgrest-style-check failed. Run postgrest-style to fix issues automatically." ERR
${git}/bin/git diff-index --exit-code HEAD -- '*.hs' '*.lhs' '*.nix' ${git}/bin/git diff-index --exit-code HEAD -- '*.hs' '*.lhs' '*.nix'
''; '';
+14 -12
View File
@@ -5,7 +5,7 @@
, ghc , ghc
, glibcLocales , glibcLocales
, gnugrep , gnugrep
, haskell , haskellPackages
, hpc-codecov , hpc-codecov
, jq , jq
, postgrest , postgrest
@@ -24,7 +24,7 @@ let
withEnv = postgrest.env; withEnv = postgrest.env;
} }
'' ''
${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec
''; '';
testQuerycost = testQuerycost =
@@ -36,7 +36,7 @@ let
withEnv = postgrest.env; withEnv = postgrest.env;
} }
'' ''
${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost
''; '';
testDoctests = testDoctests =
@@ -66,7 +66,7 @@ let
withEnv = postgrest.env; withEnv = postgrest.env;
} }
'' ''
${withTools.latest} ${runtimeShell} -c " \ ${withTools.withPg} ${runtimeShell} -c " \
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec && \ ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec && \
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec" ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec"
''; '';
@@ -92,8 +92,8 @@ let
} }
'' ''
${cabal-install}/bin/cabal v2-build ${devCabalOptions} ${cabal-install}/bin/cabal v2-build ${devCabalOptions}
${cabal-install}/bin/cabal v2-exec ${withTools.latest} \ ${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures.sql \
${ioTestPython}/bin/pytest -- -v test/io-tests "''${_arg_leftovers[@]}" ${ioTestPython}/bin/pytest -v test/io "''${_arg_leftovers[@]}"
''; '';
dumpSchema = dumpSchema =
@@ -106,7 +106,7 @@ let
withPath = [ jq ]; withPath = [ jq ];
} }
'' ''
${withTools.latest} \ ${withTools.withPg} \
${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \ ${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \
postgrest --dump-schema \ postgrest --dump-schema \
| ${yq}/bin/yq -y . | ${yq}/bin/yq -y .
@@ -116,7 +116,7 @@ let
checkedShellScript checkedShellScript
{ {
name = "postgrest-coverage"; name = "postgrest-coverage";
docs = "Run spec and io tests while collecting hpc coverage data."; docs = "Run spec and io tests while collecting hpc coverage data. First runs weeder to detect dead code.";
args = [ "ARG_LEFTOVERS([hpc report arguments])" ]; args = [ "ARG_LEFTOVERS([hpc report arguments])" ];
inRootDir = true; inRootDir = true;
redirectTixFiles = false; redirectTixFiles = false;
@@ -133,16 +133,18 @@ let
# build once before running all the tests # build once before running all the tests
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest lib:postgrest test:spec test:querycost ${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest lib:postgrest test:spec test:querycost
${haskellPackages.weeder}/bin/weeder --config=./test/weeder.dhall || echo Found dead code: Check file list above.
# collect all tests # collect all tests
HPCTIXFILE="$tmpdir"/io.tix \ HPCTIXFILE="$tmpdir"/io.tix \
${withTools.latest} ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} \ ${withTools.withPg} -f test/io/fixtures.sql ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- \
${ioTestPython}/bin/pytest -- -v test/io-tests ${ioTestPython}/bin/pytest -v test/io
HPCTIXFILE="$tmpdir"/spec.tix \ HPCTIXFILE="$tmpdir"/spec.tix \
${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec
HPCTIXFILE="$tmpdir"/querycost.tix \ HPCTIXFILE="$tmpdir"/querycost.tix \
${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost
# Note: No coverage for doctests, as doctests leverage GHCi and GHCi does not support hpc # Note: No coverage for doctests, as doctests leverage GHCi and GHCi does not support hpc
+158 -17
View File
@@ -1,9 +1,14 @@
{ bashCompletion { bashCompletion
, buildToolbox , buildToolbox
, cabal-install
, checkedShellScript , checkedShellScript
, curl
, devCabalOptions
, git
, lib , lib
, postgresqlVersions , postgresqlVersions
, writeTextFile , postgrest
, writeText
}: }:
let let
withTmpDb = withTmpDb =
@@ -14,7 +19,7 @@ let
docs = "Run the given command in a temporary database with ${name}"; docs = "Run the given command in a temporary database with ${name}";
args = args =
[ [
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/fixtures/load.sql])" "ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/spec/fixtures/load.sql])"
"ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])" "ARG_LEFTOVERS([command arguments])"
"ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])" "ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])"
@@ -22,7 +27,7 @@ let
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])" "ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
"ARG_USE_ENV([PGRST_DB_ANON_ROLE], [postgrest_test_anonymous], [Anonymous PG role])" "ARG_USE_ENV([PGRST_DB_ANON_ROLE], [postgrest_test_anonymous], [Anonymous PG role])"
]; ];
addCommandCompletion = true; positionalCompletion = "_command";
inRootDir = true; inRootDir = true;
redirectTixFiles = false; redirectTixFiles = false;
withPath = [ postgresql ]; withPath = [ postgresql ];
@@ -31,7 +36,7 @@ let
'' ''
# avoid starting multiple layers of withTmpDb # avoid starting multiple layers of withTmpDb
if test -v PGRST_DB_URI; then if test -v PGRST_DB_URI; then
exec "$@" exec "$_arg_command" "''${_arg_leftovers[@]}"
fi fi
setuplog="$tmpdir/setup.log" setuplog="$tmpdir/setup.log"
@@ -78,27 +83,27 @@ let
''; '';
# Helper script for running a command against all PostgreSQL versions. # Helper script for running a command against all PostgreSQL versions.
withAll = withPgAll =
let let
runners = runners =
builtins.map builtins.map
(pg: (version:
'' ''
cat << EOF cat << EOF
Running against ${pg.name}... Running against ${version.name}...
EOF EOF
trap 'echo "Failed on ${pg.name}"' exit trap 'echo "Failed on ${version.name}"' exit
(${withTmpDb pg} "$_arg_command" "''${_arg_leftovers[@]}") (${withTmpDb version} "$_arg_command" "''${_arg_leftovers[@]}")
trap "" exit trap "" exit
cat << EOF cat << EOF
Done running against ${pg.name}. Done running against ${version.name}.
EOF EOF
'') '')
@@ -113,21 +118,157 @@ let
"ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])" "ARG_LEFTOVERS([command arguments])"
]; ];
addCommandCompletion = true; positionalCompletion = "_command";
inRootDir = true; inRootDir = true;
} }
(lib.concatStringsSep "\n\n" runners); (lib.concatStringsSep "\n\n" runners);
# Create a `postgrest-with-postgresql-` for each PostgreSQL version # Create a `postgrest-with-postgresql-` for each PostgreSQL version
withVersions = builtins.map withTmpDb postgresqlVersions; withPgVersions = builtins.map withTmpDb postgresqlVersions;
withPg = builtins.head withPgVersions;
withGit =
let
name = "postgrest-with-git";
in
checkedShellScript
{
inherit name;
docs =
''
Create a new worktree of the postgrest repo in a temporary directory and
check out <commit>, then run <command> with arguments inside the temporary folder.
'';
args =
[
"ARG_POSITIONAL_SINGLE([commit], [Commit-ish reference to run command with])"
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
];
positionalCompletion =
''
if test "$prev" == "${name}"; then
__gitcomp_nl "$(__git_refs)"
else
_command_offset 2
fi
'';
inRootDir = true;
}
''
# not using withTmpDir here, because we don't want to keep the directory on error
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
${git}/bin/git worktree add -f "$tmpdir" "$_arg_commit" > /dev/null
cd "$tmpdir"
("$_arg_command" "''${_arg_leftovers[@]}")
${git}/bin/git worktree remove -f "$tmpdir" > /dev/null
'';
legacyConfig =
writeText "legacy.conf"
''
# Using this config file to support older postgrest versions for `postgrest-loadtest-against`
db-uri="$(PGRST_DB_URI)"
db-schema="$(PGRST_DB_SCHEMAS)"
db-anon-role="$(PGRST_DB_ANON_ROLE)"
db-pool="$(PGRST_DB_POOL)"
server-unix-socket="$(PGRST_SERVER_UNIX_SOCKET)"
log-level="$(PGRST_LOG_LEVEL)"
'';
waitForPgrstPid =
checkedShellScript
{
name = "postgrest-wait-for-pgrst-pid";
docs = "Wait for PostgREST to be running. Needs to be a separate command for timeout to work below.";
args = [
"ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [Unix socket to check for running PostgREST instance])"
];
}
''
# ARG_USE_ENV only adds defaults or docs for environment variables
# We manually implement a required check here
# See also: https://github.com/matejak/argbash/issues/80
: "''${PGRST_SERVER_UNIX_SOCKET:?PGRST_SERVER_UNIX_SOCKET is required}"
until [ -S "$PGRST_SERVER_UNIX_SOCKET" ]
do
sleep 0.1
done
# return pid of postgrest process
lsof -t -c '/^postgrest$/' "$PGRST_SERVER_UNIX_SOCKET"
'';
waitForPgrstReady =
checkedShellScript
{
name = "postgrest-wait-for-pgrst-ready";
docs = "Wait for PostgREST to be ready to serve requests. Needs to be a separate command for timeout to work below.";
args = [
"ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [Unix socket to check for running PostgREST instance])"
];
}
''
# ARG_USE_ENV only adds defaults or docs for environment variables
# We manually implement a required check here
# See also: https://github.com/matejak/argbash/issues/80
: "''${PGRST_SERVER_UNIX_SOCKET:?PGRST_SERVER_UNIX_SOCKET is required}"
function check_status () {
${curl}/bin/curl -s -o /dev/null -w "%{http_code}" --unix-socket "$PGRST_SERVER_UNIX_SOCKET" http://localhost/
}
while [[ "$(check_status)" != "200" ]];
do sleep 0.1;
done
'';
withPgrst =
checkedShellScript
{
name = "postgrest-with-pgrst";
docs = "Build and run PostgREST and run <command> with PGRST_SERVER_UNIX_SOCKET set.";
args =
[
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
];
positionalCompletion = "_command";
inRootDir = true;
withEnv = postgrest.env;
withTmpDir = true;
}
''
export PGRST_SERVER_UNIX_SOCKET="$tmpdir"/postgrest.socket
${cabal-install}/bin/cabal v2-build ${devCabalOptions} > "$tmpdir"/build.log 2>&1
${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \
postgrest ${legacyConfig} > "$tmpdir"/run.log 2>&1 &
# to get the pid of the postgrest process, we need to jump through some hoops
# $! will return the pid of cabal - but killing this, will not propagate to postgrest
pid=$(timeout -s TERM 1 ${waitForPgrstPid})
cleanup() {
kill "$pid" || true
}
trap cleanup EXIT
timeout -s TERM 5 ${waitForPgrstReady}
("$_arg_command" "''${_arg_leftovers[@]}")
'';
in in
buildToolbox buildToolbox
{ {
name = "postgrest-with"; name = "postgrest-with";
tools = [ withAll ] ++ withVersions; tools = [ withPgAll withGit withPgrst ] ++ withPgVersions;
extra = { # make withTools available for other nix files
# make withTools.latest available for other nix files extra = { inherit withGit withPg withPgAll withPgrst; };
latest = withTmpDb (builtins.head postgresqlVersions);
};
} }
+66 -75
View File
@@ -1,8 +1,8 @@
name: postgrest name: postgrest
version: 9.0.0 version: 9.0.1
synopsis: REST API for any Postgres database synopsis: REST API for any Postgres database
description: Reads the schema of a PostgreSQL database and creates RESTful routes description: Reads the schema of a PostgreSQL database and creates RESTful routes
for the tables and views, supporting all HTTP verbs that security for tables, views, and functions, supporting all HTTP verbs that security
permits. permits.
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
@@ -44,6 +44,7 @@ library
PostgREST.Config.PgVersion PostgREST.Config.PgVersion
PostgREST.Config.Proxy PostgREST.Config.Proxy
PostgREST.ContentType PostgREST.ContentType
PostgREST.Cors
PostgREST.DbStructure PostgREST.DbStructure
PostgREST.DbStructure.Identifiers PostgREST.DbStructure.Identifiers
PostgREST.DbStructure.Proc PostgREST.DbStructure.Proc
@@ -51,6 +52,7 @@ library
PostgREST.DbStructure.Table PostgREST.DbStructure.Table
PostgREST.Error PostgREST.Error
PostgREST.GucHeader PostgREST.GucHeader
PostgREST.Logger
PostgREST.Middleware PostgREST.Middleware
PostgREST.OpenAPI PostgREST.OpenAPI
PostgREST.Query.QueryBuilder PostgREST.Query.QueryBuilder
@@ -69,7 +71,6 @@ library
, HTTP >= 4000.3.7 && < 4000.4 , HTTP >= 4000.3.7 && < 4000.4
, Ranged-sets >= 0.3 && < 0.5 , Ranged-sets >= 0.3 && < 0.5
, aeson >= 1.4.7 && < 1.6 , aeson >= 1.4.7 && < 1.6
, ansi-wl-pprint >= 0.6.7 && < 0.7
, auto-update >= 0.1.4 && < 0.2 , auto-update >= 0.1.4 && < 0.2
, base64-bytestring >= 1 && < 1.3 , base64-bytestring >= 1 && < 1.3
, bytestring >= 0.10.8 && < 0.11 , bytestring >= 0.10.8 && < 0.11
@@ -77,11 +78,9 @@ library
, cassava >= 0.4.5 && < 0.6 , cassava >= 0.4.5 && < 0.6
, configurator-pg >= 0.2 && < 0.3 , configurator-pg >= 0.2 && < 0.3
, containers >= 0.5.7 && < 0.7 , containers >= 0.5.7 && < 0.7
, contravariant >= 1.4 && < 1.6
, contravariant-extras >= 0.3.3 && < 0.4 , contravariant-extras >= 0.3.3 && < 0.4
, cookie >= 0.4.2 && < 0.5 , cookie >= 0.4.2 && < 0.5
, either >= 4.4.1 && < 5.1 , either >= 4.4.1 && < 5.1
, fast-logger >= 2.4.5
, gitrev >= 1.2 && < 1.4 , gitrev >= 1.2 && < 1.4
, hasql >= 1.4 && < 1.5 , hasql >= 1.4 && < 1.5
, hasql-dynamic-statements == 0.3.1 , hasql-dynamic-statements == 0.3.1
@@ -99,7 +98,7 @@ library
, network-uri >= 2.6.1 && < 2.8 , network-uri >= 2.6.1 && < 2.8
, optparse-applicative >= 0.13 && < 0.17 , optparse-applicative >= 0.13 && < 0.17
, parsec >= 3.1.11 && < 3.2 , parsec >= 3.1.11 && < 3.2
, protolude >= 0.3 && < 0.4 , protolude >= 0.3.1 && < 0.4
, regex-tdfa >= 1.2.2 && < 1.4 , regex-tdfa >= 1.2.2 && < 1.4
, retry >= 0.7.4 && < 0.10 , retry >= 0.7.4 && < 0.10
, scientific >= 0.3.4 && < 0.4 , scientific >= 0.3.4 && < 0.4
@@ -110,10 +109,8 @@ library
, vector >= 0.11 && < 0.13 , vector >= 0.11 && < 0.13
, wai >= 3.2.1 && < 3.3 , wai >= 3.2.1 && < 3.3
, wai-cors >= 0.2.5 && < 0.3 , wai-cors >= 0.2.5 && < 0.3
, wai-extra >= 3.0.19 && < 3.2 , wai-extra >= 3.1.8 && < 3.2
, wai-logger >= 2.3.2 , warp >= 3.3.19 && < 3.4
, wai-middleware-static >= 0.8.1 && < 0.10
, warp >= 3.2.12 && < 3.4
-- -fno-spec-constr may help keep compile time memory use in check, -- -fno-spec-constr may help keep compile time memory use in check,
-- see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304 -- see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304
-- -optP-Wno-nonportable-include-path -- -optP-Wno-nonportable-include-path
@@ -123,7 +120,7 @@ library
-fno-spec-constr -optP-Wno-nonportable-include-path -fno-spec-constr -optP-Wno-nonportable-include-path
if flag(dev) if flag(dev)
ghc-options: -O0 ghc-options: -O0 -fwrite-ide-info
if flag(hpc) if flag(hpc)
ghc-options: -fhpc -hpcdir .hpc ghc-options: -fhpc -hpcdir .hpc
else else
@@ -146,13 +143,15 @@ executable postgrest
build-depends: base >= 4.9 && < 4.16 build-depends: base >= 4.9 && < 4.16
, containers >= 0.5.7 && < 0.7 , containers >= 0.5.7 && < 0.7
, postgrest , postgrest
, protolude >= 0.3 && < 0.4 , protolude >= 0.3.1 && < 0.4
ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2" ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I0 -qg"
-O2 -Werror -Wall -fwarn-identities -O2 -Werror -Wall -fwarn-identities
-fno-spec-constr -optP-Wno-nonportable-include-path -fno-spec-constr -optP-Wno-nonportable-include-path
if flag(dev) if flag(dev)
ghc-options: -O0 ghc-options: -O0 -fwrite-ide-info
-- https://github.com/PostgREST/postgrest/issues/387
-with-rtsopts=-K1K
if flag(hpc) if flag(hpc)
ghc-options: -fhpc -hpcdir .hpc ghc-options: -fhpc -hpcdir .hpc
else else
@@ -164,7 +163,7 @@ test-suite spec
default-extensions: OverloadedStrings default-extensions: OverloadedStrings
QuasiQuotes QuasiQuotes
NoImplicitPrelude NoImplicitPrelude
hs-source-dirs: test hs-source-dirs: test/spec
main-is: Main.hs main-is: Main.hs
other-modules: Feature.AndOrParamsSpec other-modules: Feature.AndOrParamsSpec
Feature.AsymmetricJwtSpec Feature.AsymmetricJwtSpec
@@ -211,10 +210,7 @@ test-suite spec
, base64-bytestring >= 1 && < 1.3 , base64-bytestring >= 1 && < 1.3
, bytestring >= 0.10.8 && < 0.11 , bytestring >= 0.10.8 && < 0.11
, case-insensitive >= 1.2 && < 1.3 , case-insensitive >= 1.2 && < 1.3
, cassava >= 0.4.5 && < 0.6
, containers >= 0.5.7 && < 0.7 , containers >= 0.5.7 && < 0.7
, contravariant >= 1.4 && < 1.6
, hasql >= 1.4 && < 1.5
, hasql-pool >= 0.5 && < 0.6 , hasql-pool >= 0.5 && < 0.6
, hasql-transaction >= 1.0.1 && < 1.1 , hasql-transaction >= 1.0.1 && < 1.1
, heredoc >= 0.2 && < 0.3 , heredoc >= 0.2 && < 0.3
@@ -227,72 +223,67 @@ test-suite spec
, monad-control >= 1.0.1 && < 1.1 , monad-control >= 1.0.1 && < 1.1
, postgrest , postgrest
, process >= 1.4.2 && < 1.7 , process >= 1.4.2 && < 1.7
, protolude >= 0.3 && < 0.4 , protolude >= 0.3.1 && < 0.4
, regex-tdfa >= 1.2.2 && < 1.4 , regex-tdfa >= 1.2.2 && < 1.4
, text >= 1.2.2 && < 1.3 , text >= 1.2.2 && < 1.3
, time >= 1.6 && < 1.11
, transformers-base >= 0.4.4 && < 0.5 , transformers-base >= 0.4.4 && < 0.5
, wai >= 3.2.1 && < 3.3 , wai >= 3.2.1 && < 3.3
, wai-extra >= 3.0.19 && < 3.2 , wai-extra >= 3.0.19 && < 3.2
ghc-options: -O0 -Werror -Wall -fwarn-identities ghc-options: -O0 -Werror -Wall -fwarn-identities
-fno-spec-constr -optP-Wno-nonportable-include-path -fno-spec-constr -optP-Wno-nonportable-include-path
-fno-warn-missing-signatures -fno-warn-missing-signatures
-fwrite-ide-info
-- https://github.com/PostgREST/postgrest/issues/387
-with-rtsopts=-K33K
test-suite querycost test-suite querycost
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings default-extensions: OverloadedStrings
QuasiQuotes QuasiQuotes
NoImplicitPrelude NoImplicitPrelude
hs-source-dirs: test hs-source-dirs: test/spec
main-is: QueryCost.hs main-is: QueryCost.hs
other-modules: SpecHelper other-modules: SpecHelper
build-depends: base >= 4.9 && < 4.16 build-depends: base >= 4.9 && < 4.16
, aeson >= 1.4.7 && < 1.6 , aeson >= 1.4.7 && < 1.6
, aeson-qq >= 0.8.1 && < 0.9 , base64-bytestring >= 1 && < 1.3
, async >= 2.1.1 && < 2.3 , bytestring >= 0.10.8 && < 0.11
, auto-update >= 0.1.4 && < 0.2 , case-insensitive >= 1.2 && < 1.3
, base64-bytestring >= 1 && < 1.3 , containers >= 0.5.7 && < 0.7
, bytestring >= 0.10.8 && < 0.11 , contravariant >= 1.4 && < 1.6
, case-insensitive >= 1.2 && < 1.3 , hasql >= 1.4 && < 1.5
, cassava >= 0.4.5 && < 0.6 , hasql-dynamic-statements == 0.3.1
, containers >= 0.5.7 && < 0.7 , hasql-pool >= 0.5 && < 0.6
, contravariant >= 1.4 && < 1.6 , hasql-transaction >= 1.0.1 && < 1.1
, hasql >= 1.4 && < 1.5 , heredoc >= 0.2 && < 0.3
, hasql-dynamic-statements == 0.3.1 , hspec >= 2.3 && < 2.9
, hasql-pool >= 0.5 && < 0.6 , hspec-wai >= 0.10 && < 0.12
, hasql-transaction >= 1.0.1 && < 1.1 , http-types >= 0.12.3 && < 0.13
, heredoc >= 0.2 && < 0.3 , lens >= 4.14 && < 5.1
, hspec >= 2.3 && < 2.9 , lens-aeson >= 1.0.1 && < 1.2
, hspec-wai >= 0.10 && < 0.12 , postgrest
, hspec-wai-json >= 0.10 && < 0.12 , process >= 1.4.2 && < 1.7
, http-types >= 0.12.3 && < 0.13 , protolude >= 0.3.1 && < 0.4
, lens >= 4.14 && < 5.1 , regex-tdfa >= 1.2.2 && < 1.4
, lens-aeson >= 1.0.1 && < 1.2 , wai-extra >= 3.0.19 && < 3.2
, monad-control >= 1.0.1 && < 1.1 ghc-options: -O0 -Werror -Wall -fwarn-identities
, postgrest -fno-spec-constr -optP-Wno-nonportable-include-path
, process >= 1.4.2 && < 1.7 -fwrite-ide-info
, protolude >= 0.3 && < 0.4 -- https://github.com/PostgREST/postgrest/issues/387
, regex-tdfa >= 1.2.2 && < 1.4 -with-rtsopts=-K1K
, text >= 1.2.2 && < 1.3
, time >= 1.6 && < 1.11
, transformers-base >= 0.4.4 && < 0.5
, wai >= 3.2.1 && < 3.3
, wai-extra >= 3.0.19 && < 3.2
ghc-options: -O0 -Werror -Wall -fwarn-identities
-fno-spec-constr -optP-Wno-nonportable-include-path
test-suite doctests test-suite doctests
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings default-extensions: OverloadedStrings
NoImplicitPrelude NoImplicitPrelude
hs-source-dirs: test/doctests hs-source-dirs: test/doc
main-is: Main.hs main-is: Main.hs
build-depends: base >= 4.9 && < 4.16 build-depends: base >= 4.9 && < 4.16
, doctest >= 0.8 , doctest >= 0.8
, postgrest , postgrest
, pretty-simple , pretty-simple
, protolude >= 0.3 && < 0.4 , protolude >= 0.3.1 && < 0.4
ghc-options: -threaded -O0 -Werror -Wall -fwarn-identities ghc-options: -threaded -O0 -Werror -Wall -fwarn-identities
-fno-spec-constr -optP-Wno-nonportable-include-path -fno-spec-constr -optP-Wno-nonportable-include-path
+3
View File
@@ -21,6 +21,7 @@ let
[ [
postgrest.cabalTools postgrest.cabalTools
postgrest.devTools postgrest.devTools
postgrest.loadtest
postgrest.nixpkgsTools postgrest.nixpkgsTools
postgrest.style postgrest.style
postgrest.tests postgrest.tests
@@ -37,6 +38,7 @@ lib.overrideDerivation postgrest.env (
base.buildInputs ++ [ base.buildInputs ++ [
pkgs.cabal-install pkgs.cabal-install
pkgs.cabal2nix pkgs.cabal2nix
pkgs.git
pkgs.postgresql pkgs.postgresql
postgrest.hsie.bin postgrest.hsie.bin
] ]
@@ -45,6 +47,7 @@ lib.overrideDerivation postgrest.env (
shellHook = shellHook =
'' ''
source ${pkgs.bashCompletion}/etc/profile.d/bash_completion.sh source ${pkgs.bashCompletion}/etc/profile.d/bash_completion.sh
source ${pkgs.git}/share/git/contrib/completion/git-completion.bash
source ${postgrest.hsie.bashCompletion} source ${postgrest.hsie.bashCompletion}
'' ''
+6 -3
View File
@@ -42,8 +42,10 @@ import qualified Network.Wai.Handler.Warp as Warp
import qualified PostgREST.AppState as AppState import qualified PostgREST.AppState as AppState
import qualified PostgREST.Auth as Auth import qualified PostgREST.Auth as Auth
import qualified PostgREST.Cors as Cors
import qualified PostgREST.DbStructure as DbStructure import qualified PostgREST.DbStructure as DbStructure
import qualified PostgREST.Error as Error import qualified PostgREST.Error as Error
import qualified PostgREST.Logger as Logger
import qualified PostgREST.Middleware as Middleware import qualified PostgREST.Middleware as Middleware
import qualified PostgREST.OpenAPI as OpenAPI import qualified PostgREST.OpenAPI as OpenAPI
import qualified PostgREST.Query.QueryBuilder as QueryBuilder import qualified PostgREST.Query.QueryBuilder as QueryBuilder
@@ -137,8 +139,9 @@ serverSettings AppConfig{..} =
-- | PostgREST application -- | PostgREST application
postgrest :: LogLevel -> AppState.AppState -> IO () -> Wai.Application postgrest :: LogLevel -> AppState.AppState -> IO () -> Wai.Application
postgrest logLev appState connWorker = postgrest logLevel appState connWorker =
Middleware.pgrstMiddleware logLev $ Logger.middleware logLevel .
Cors.middleware $
\req respond -> do \req respond -> do
time <- AppState.getTime appState time <- AppState.getTime appState
conf <- AppState.getConfig appState conf <- AppState.getConfig appState
@@ -466,7 +469,7 @@ handleOpenApi headersOnly tSchema (RequestContext conf@AppConfig{..} dbStructure
OAFollowPriv -> OAFollowPriv ->
OpenAPI.encode conf dbStructure OpenAPI.encode conf dbStructure
<$> SQL.statement tSchema (DbStructure.accessibleTables ctxPgVersion configDbPreparedStatements) <$> SQL.statement tSchema (DbStructure.accessibleTables ctxPgVersion configDbPreparedStatements)
<*> SQL.statement tSchema (DbStructure.accessibleProcs configDbPreparedStatements) <*> SQL.statement tSchema (DbStructure.accessibleProcs ctxPgVersion configDbPreparedStatements)
<*> SQL.statement tSchema (DbStructure.schemaDescription configDbPreparedStatements) <*> SQL.statement tSchema (DbStructure.schemaDescription configDbPreparedStatements)
OAIgnorePriv -> OAIgnorePriv ->
OpenAPI.encode conf dbStructure OpenAPI.encode conf dbStructure
-2
View File
@@ -53,7 +53,6 @@ main installSignalHandlers runAppWithSocket CLI{cliCommand, cliPath} = do
dumpSchema :: AppState -> IO LBS.ByteString dumpSchema :: AppState -> IO LBS.ByteString
dumpSchema appState = do dumpSchema appState = do
AppConfig{..} <- AppState.getConfig appState AppConfig{..} <- AppState.getConfig appState
actualPgVersion <- AppState.getPgVersion appState
result <- result <-
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
SQL.use (AppState.getPool appState) $ SQL.use (AppState.getPool appState) $
@@ -61,7 +60,6 @@ dumpSchema appState = do
queryDbStructure queryDbStructure
(toList configDbSchemas) (toList configDbSchemas)
configDbExtraSearchPath configDbExtraSearchPath
actualPgVersion
configDbPreparedStatements configDbPreparedStatements
SQL.release $ AppState.getPool appState SQL.release $ AppState.getPool appState
case result of case result of
+4 -4
View File
@@ -131,7 +131,7 @@ toText conf =
,("db-prepared-statements", T.toLower . show . configDbPreparedStatements) ,("db-prepared-statements", T.toLower . show . configDbPreparedStatements)
,("db-root-spec", q . maybe mempty dumpQi . configDbRootSpec) ,("db-root-spec", q . maybe mempty dumpQi . configDbRootSpec)
,("db-schemas", q . T.intercalate "," . toList . configDbSchemas) ,("db-schemas", q . T.intercalate "," . toList . configDbSchemas)
,("db-config", q . T.toLower . show . configDbConfig) ,("db-config", T.toLower . show . configDbConfig)
,("db-tx-end", q . showTxEnd) ,("db-tx-end", q . showTxEnd)
,("db-uri", q . configDbUri) ,("db-uri", q . configDbUri)
,("db-use-legacy-gucs", T.toLower . show . configDbUseLegacyGucs) ,("db-use-legacy-gucs", T.toLower . show . configDbUseLegacyGucs)
@@ -369,17 +369,17 @@ parser optPath env dbSettings =
coerceInt :: (Read i, Integral i) => C.Value -> Maybe i coerceInt :: (Read i, Integral i) => C.Value -> Maybe i
coerceInt (C.Number x) = rightToMaybe $ floatingOrInteger x coerceInt (C.Number x) = rightToMaybe $ floatingOrInteger x
coerceInt (C.String x) = readMaybe $ toS x coerceInt (C.String x) = readMaybe x
coerceInt _ = Nothing coerceInt _ = Nothing
coerceBool :: C.Value -> Maybe Bool coerceBool :: C.Value -> Maybe Bool
coerceBool (C.Bool b) = Just b coerceBool (C.Bool b) = Just b
coerceBool (C.String s) = coerceBool (C.String s) =
-- parse all kinds of text: True, true, TRUE, "true", ... -- parse all kinds of text: True, true, TRUE, "true", ...
case readMaybe . toS $ T.toTitle $ T.filter isAlpha $ toS s of case readMaybe $ T.toTitle $ T.filter isAlpha $ toS s of
Just b -> Just b Just b -> Just b
-- numeric instead? -- numeric instead?
Nothing -> (> 0) <$> (readMaybe $ toS s :: Maybe Integer) Nothing -> (> 0) <$> (readMaybe s :: Maybe Integer)
coerceBool _ = Nothing coerceBool _ = Nothing
splitOnCommas :: C.Value -> [Text] splitOnCommas :: C.Value -> [Text]
+24 -13
View File
@@ -1,7 +1,8 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module PostgREST.Config.Database module PostgREST.Config.Database
( queryDbSettings ( pgVersionStatement
, queryDbSettings
, queryPgVersion , queryPgVersion
) where ) where
@@ -20,7 +21,10 @@ import Text.InterpolatedString.Perl6 (q)
import Protolude import Protolude
queryPgVersion :: Session PgVersion queryPgVersion :: Session PgVersion
queryPgVersion = statement mempty $ SQL.Statement sql HE.noParams versionRow False queryPgVersion = statement mempty pgVersionStatement
pgVersionStatement :: SQL.Statement () PgVersion
pgVersionStatement = SQL.Statement sql HE.noParams versionRow False
where where
sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')" sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')"
versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text
@@ -36,19 +40,26 @@ dbSettingsStatement :: SQL.Statement () [(Text, Text)]
dbSettingsStatement = SQL.Statement sql HE.noParams decodeSettings False dbSettingsStatement = SQL.Statement sql HE.noParams decodeSettings False
where where
sql = [q| sql = [q|
with WITH
role_setting as ( role_setting (database, setting) AS (
select setdatabase, unnest(setconfig) as setting from pg_catalog.pg_db_role_setting SELECT setdatabase,
where setrole = current_user::regrole::oid unnest(setconfig)
and setdatabase in (0, (select oid from pg_catalog.pg_database where datname = current_catalog)) FROM pg_catalog.pg_db_role_setting
WHERE setrole = CURRENT_USER::regrole::oid
AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG))
), ),
kv_settings as ( kv_settings (database, k, v) AS (
select setdatabase, split_part(setting, '=', 1) as k, split_part(setting, '=', 2) as value from role_setting SELECT database,
where setting like 'pgrst.%' substr(setting, 1, strpos(setting, '=') - 1),
substr(setting, strpos(setting, '=') + 1)
FROM role_setting
WHERE setting LIKE 'pgrst.%'
) )
select distinct on (key) replace(k, 'pgrst.', '') as key, value SELECT DISTINCT ON (key)
from kv_settings replace(k, 'pgrst.', '') AS key,
order by key, setdatabase desc; v AS value
FROM kv_settings
ORDER BY key, database DESC;
|] |]
decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text
+42
View File
@@ -0,0 +1,42 @@
{-|
Module : PostgREST.Cors
Description : Wai Middleware to set cors policy.
-}
module PostgREST.Cors (middleware) where
import qualified Data.ByteString.Char8 as BS
import qualified Data.CaseInsensitive as CI
import qualified Network.Wai as Wai
import qualified Network.Wai.Middleware.Cors as Wai
import Data.List (lookup)
import Protolude
middleware :: Wai.Middleware
middleware = Wai.cors corsPolicy
-- | CORS policy to be used in by Wai Cors middleware
corsPolicy :: Wai.Request -> Maybe Wai.CorsResourcePolicy
corsPolicy req = case lookup "origin" headers of
Just origin ->
Just Wai.CorsResourcePolicy
{ Wai.corsOrigins = Just ([origin], True)
, Wai.corsMethods = ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"]
, Wai.corsRequestHeaders = "Authorization" : accHeaders
, Wai.corsExposedHeaders = Just
[ "Content-Encoding", "Content-Location", "Content-Range", "Content-Type"
, "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit"]
, Wai.corsMaxAge = Just $ 60*60*24
, Wai.corsVaryOrigin = False
, Wai.corsRequireOrigin = False
, Wai.corsIgnoreFailures = True
}
Nothing -> Nothing
where
headers = Wai.requestHeaders req
accHeaders = case lookup "access-control-request-headers" headers of
Just hdrs -> map (CI.mk . BS.strip) $ BS.split ',' hdrs
-- Impossible case, Middleware.Cors will not evaluate this when
-- the Access-Control-Request-Headers header is not set.
Nothing -> []
+25 -19
View File
@@ -41,7 +41,9 @@ import Data.Set as S (fromList)
import Data.Text (split) import Data.Text (split)
import Text.InterpolatedString.Perl6 (q) import Text.InterpolatedString.Perl6 (q)
import PostgREST.Config.PgVersion (PgVersion, pgVersion100) import PostgREST.Config.Database (pgVersionStatement)
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
pgVersion110)
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..), import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..),
Schema, TableName) Schema, TableName)
import PostgREST.DbStructure.Proc (PgType (..), import PostgREST.DbStructure.Proc (PgType (..),
@@ -83,15 +85,16 @@ type ViewColumn = Column
-- | A SQL query that can be executed independently -- | A SQL query that can be executed independently
type SqlQuery = ByteString type SqlQuery = ByteString
queryDbStructure :: [Schema] -> [Schema] -> PgVersion -> Bool -> SQL.Transaction DbStructure queryDbStructure :: [Schema] -> [Schema] -> Bool -> SQL.Transaction DbStructure
queryDbStructure schemas extraSearchPath pgVer prepared = do queryDbStructure schemas extraSearchPath prepared = do
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
pgVer <- SQL.statement mempty pgVersionStatement
tabs <- SQL.statement mempty $ allTables pgVer prepared tabs <- SQL.statement mempty $ allTables pgVer prepared
cols <- SQL.statement schemas $ allColumns tabs prepared cols <- SQL.statement schemas $ allColumns tabs prepared
srcCols <- SQL.statement (schemas, extraSearchPath) $ pfkSourceColumns cols prepared srcCols <- SQL.statement (schemas, extraSearchPath) $ pfkSourceColumns cols prepared
m2oRels <- SQL.statement mempty $ allM2ORels tabs cols prepared m2oRels <- SQL.statement mempty $ allM2ORels tabs cols prepared
keys <- SQL.statement mempty $ allPrimaryKeys tabs prepared keys <- SQL.statement mempty $ allPrimaryKeys tabs prepared
procs <- SQL.statement schemas $ allProcs prepared procs <- SQL.statement schemas $ allProcs pgVer prepared
let rels = addO2MRels . addM2MRels $ addViewM2ORels srcCols m2oRels let rels = addO2MRels . addM2MRels $ addViewM2ORels srcCols m2oRels
keys' = addViewPrimaryKeys srcCols keys keys' = addViewPrimaryKeys srcCols keys
@@ -224,18 +227,18 @@ decodeProcs =
| v == 's' = Stable | v == 's' = Stable
| otherwise = Volatile -- only 'v' can happen here | otherwise = Volatile -- only 'v' can happen here
allProcs :: Bool -> SQL.Statement [Schema] ProcsMap allProcs :: PgVersion -> Bool -> SQL.Statement [Schema] ProcsMap
allProcs = SQL.Statement sql (arrayParam HE.text) decodeProcs allProcs pgVer = SQL.Statement sql (arrayParam HE.text) decodeProcs
where where
sql = procsSqlQuery <> " WHERE pn.nspname = ANY($1)" sql = procsSqlQuery pgVer <> " AND pn.nspname = ANY($1)"
accessibleProcs :: Bool -> SQL.Statement Schema ProcsMap accessibleProcs :: PgVersion -> Bool -> SQL.Statement Schema ProcsMap
accessibleProcs = SQL.Statement sql (param HE.text) decodeProcs accessibleProcs pgVer = SQL.Statement sql (param HE.text) decodeProcs
where where
sql = procsSqlQuery <> " WHERE pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" sql = procsSqlQuery pgVer <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')"
procsSqlQuery :: SqlQuery procsSqlQuery :: PgVersion -> SqlQuery
procsSqlQuery = [q| procsSqlQuery pgVer = [q|
-- Recursively get the base types of domains -- Recursively get the base types of domains
WITH WITH
base_types AS ( base_types AS (
@@ -297,7 +300,8 @@ procsSqlQuery = [q|
JOIN pg_namespace tn ON tn.oid = t.typnamespace JOIN pg_namespace tn ON tn.oid = t.typnamespace
LEFT JOIN pg_class comp ON comp.oid = t.typrelid LEFT JOIN pg_class comp ON comp.oid = t.typrelid
LEFT JOIN pg_catalog.pg_description as d ON d.objoid = p.oid LEFT JOIN pg_catalog.pg_description as d ON d.objoid = p.oid
|] WHERE t.oid <> 'pg_catalog.trigger'::regtype
|] <> (if pgVer >= pgVersion110 then "AND prokind = 'f'" else "AND NOT (proisagg OR proiswindow)")
schemaDescription :: Bool -> SQL.Statement Schema (Maybe Text) schemaDescription :: Bool -> SQL.Statement Schema (Maybe Text)
schemaDescription = schemaDescription =
@@ -792,7 +796,6 @@ pfkSourceColumns cols =
replace( replace(
replace( replace(
replace( replace(
replace(
regexp_replace( regexp_replace(
replace( replace(
replace( replace(
@@ -803,6 +806,7 @@ pfkSourceColumns cols =
replace( replace(
replace( replace(
replace( replace(
replace(
replace( replace(
view_definition::text, view_definition::text,
-- This conversion to json is heavily optimized for performance. -- This conversion to json is heavily optimized for performance.
@@ -814,9 +818,15 @@ pfkSourceColumns cols =
-- ----------------------------------------------- -- -----------------------------------------------
-- pattern | replacement | flags -- pattern | replacement | flags
-- ----------------------------------------------- -- -----------------------------------------------
-- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof
-- we need to make this an empty array here to prevent json_array_elements from throwing an error
-- when the targetList is null.
-- We'll need to put it first, to make the node protection below work for node lists that start with
-- null: `(<> ...`, too. This is the case for coldefexprs, when the first column does not have a default value.
'<>' , '()'
-- `,` is not part of the pg_node_tree format, but used in the regex. -- `,` is not part of the pg_node_tree format, but used in the regex.
-- This removes all `,` that might be part of column names. -- This removes all `,` that might be part of column names.
',' , '' ), ',' , ''
-- The same applies for `{` and `}`, although those are used a lot in pg_node_tree. -- The same applies for `{` and `}`, although those are used a lot in pg_node_tree.
-- We remove the escaped ones, which might be part of column names again. -- We remove the escaped ones, which might be part of column names again.
), E'\\{' , '' ), E'\\{' , ''
@@ -851,10 +861,6 @@ pfkSourceColumns cols =
), ')' , ']' ), ')' , ']'
-- pg_node_tree has ` ` between list items, but JSON uses `,` -- pg_node_tree has ` ` between list items, but JSON uses `,`
), ' ' , ',' ), ' ' , ','
-- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof
-- we need to make this an empty array here to prevent json_array_elements from throwing an error
-- when the targetList is null.
), '<>' , '[]'
)::json as view_definition )::json as view_definition
from views from views
), ),
+4 -5
View File
@@ -126,23 +126,22 @@ instance JSON.ToJSON ApiRequestError where
compressedRel :: Relationship -> JSON.Value compressedRel :: Relationship -> JSON.Value
compressedRel Relationship{..} = compressedRel Relationship{..} =
let let
fmtTbl Table{..} = tableSchema <> "." <> tableName fmtEls els = "(" <> T.intercalate ", " els <> ")"
fmtEls els = "[" <> T.intercalate ", " els <> "]"
in in
JSON.object $ JSON.object $
("embedding" .= (tableName relTable <> " with " <> tableName relForeignTable :: Text)) ("embedding" .= (tableName relTable <> " with " <> tableName relForeignTable :: Text))
: case relCardinality of : case relCardinality of
M2M Junction{..} -> [ M2M Junction{..} -> [
"cardinality" .= ("many-to-many" :: Text) "cardinality" .= ("many-to-many" :: Text)
, "relationship" .= (fmtTbl junTable <> fmtEls [junConstraint1] <> fmtEls [junConstraint2]) , "relationship" .= (tableName junTable <> " using " <> junConstraint1 <> fmtEls (colName <$> junColumns1) <> " and " <> junConstraint2 <> fmtEls (colName <$> junColumns2))
] ]
M2O cons -> [ M2O cons -> [
"cardinality" .= ("many-to-one" :: Text) "cardinality" .= ("many-to-one" :: Text)
, "relationship" .= (cons <> fmtEls (colName <$> relColumns) <> fmtEls (colName <$> relForeignColumns)) , "relationship" .= (cons <> " using " <> tableName relTable <> fmtEls (colName <$> relColumns) <> " and " <> tableName relForeignTable <> fmtEls (colName <$> relForeignColumns))
] ]
O2M cons -> [ O2M cons -> [
"cardinality" .= ("one-to-many" :: Text) "cardinality" .= ("one-to-many" :: Text)
, "relationship" .= (cons <> fmtEls (colName <$> relColumns) <> fmtEls (colName <$> relForeignColumns)) , "relationship" .= (cons <> " using " <> tableName relTable <> fmtEls (colName <$> relColumns) <> " and " <> tableName relForeignTable <> fmtEls (colName <$> relForeignColumns))
] ]
relHint :: [Relationship] -> Text relHint :: [Relationship] -> Text
+28
View File
@@ -0,0 +1,28 @@
{-|
Module : PostgREST.Logger
Description : Wai Middleware to log requests to stdout.
-}
module PostgREST.Logger (middleware) where
import qualified Network.Wai as Wai
import qualified Network.Wai.Middleware.RequestLogger as Wai
import Network.HTTP.Types.Status (status400, status500)
import System.IO.Unsafe (unsafePerformIO)
import PostgREST.Config (LogLevel (..))
import Protolude
middleware :: LogLevel -> Wai.Middleware
middleware logLevel = case logLevel of
LogInfo -> requestLogger (const True)
LogWarn -> requestLogger (>= status400)
LogError -> requestLogger (>= status500)
LogCrit -> requestLogger (const False)
where
requestLogger filterStatus = unsafePerformIO $ Wai.mkRequestLogger Wai.defaultRequestLoggerSettings
{ Wai.outputFormat = Wai.ApacheWithSettings $
Wai.defaultApacheSettings
& Wai.setApacheRequestFilter (\_ res -> filterStatus $ Wai.responseStatus res)
}
+22 -103
View File
@@ -2,47 +2,29 @@
Module : PostgREST.Middleware Module : PostgREST.Middleware
Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and pre-request function. Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and pre-request function.
-} -}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
module PostgREST.Middleware module PostgREST.Middleware
( runPgLocals ( runPgLocals
, pgrstFormat
, pgrstMiddleware
, defaultCorsPolicy
, corsPolicy
, optionalRollback , optionalRollback
) where ) where
import qualified Data.Aeson as JSON import qualified Data.Aeson as JSON
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.ByteString.Lazy.Char8 as LBS import qualified Data.HashMap.Strict as M
import qualified Data.CaseInsensitive as CI import qualified Data.Text as T
import qualified Data.HashMap.Strict as M import qualified Data.Text.Encoding as T
import qualified Data.Text as T import qualified Hasql.Decoders as HD
import qualified Data.Text.Encoding as T import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
import qualified Hasql.Decoders as HD import qualified Hasql.DynamicStatements.Statement as SQL
import qualified Hasql.DynamicStatements.Snippet as SQL hiding import qualified Hasql.Transaction as SQL
(sql) import qualified Network.Wai as Wai
import qualified Hasql.DynamicStatements.Statement as SQL
import qualified Hasql.Transaction as SQL
import qualified Network.Wai as Wai
import qualified Network.Wai.Logger as Wai
import qualified Network.Wai.Middleware.Cors as Wai
import qualified Network.Wai.Middleware.Gzip as Wai
import qualified Network.Wai.Middleware.RequestLogger as Wai
import qualified Network.Wai.Middleware.Static as Wai
import Control.Arrow ((***)) import Control.Arrow ((***))
import Data.Function (id) import Data.Scientific (FPFormat (..), formatScientific, isInteger)
import Data.List (lookup)
import Data.Scientific (FPFormat (..), formatScientific,
isInteger)
import Network.HTTP.Types.Status (Status, status400, status500,
statusCode)
import System.IO.Unsafe (unsafePerformIO)
import System.Log.FastLogger (toLogStr)
import PostgREST.Config (AppConfig (..), LogLevel (..)) import PostgREST.Config (AppConfig (..))
import PostgREST.Config.PgVersion (PgVersion (..), pgVersion140) import PostgREST.Config.PgVersion (PgVersion (..), pgVersion140)
import PostgREST.Error (Error, errorResponseFor) import PostgREST.Error (Error, errorResponseFor)
import PostgREST.GucHeader (addHeadersIfNotIncluded) import PostgREST.GucHeader (addHeadersIfNotIncluded)
@@ -90,76 +72,12 @@ runPgLocals conf claims app req jsonDbS actualPgVersion = do
_ -> mempty _ -> mempty
usesLegacyGucs = configDbUseLegacyGucs conf && actualPgVersion < pgVersion140 usesLegacyGucs = configDbUseLegacyGucs conf && actualPgVersion < pgVersion140
-- | Log in apache format. Only requests that have a status greater than minStatus are logged. unquoted :: JSON.Value -> Text
-- | There's no way to filter logs in the apache format on wai-extra: https://hackage.haskell.org/package/wai-extra-3.0.29.2/docs/Network-Wai-Middleware-RequestLogger.html#t:OutputFormat. unquoted (JSON.String t) = t
-- | So here we copy wai-logger apacheLogStr function: https://github.com/kazu-yamamoto/logger/blob/a4f51b909a099c51af7a3f75cf16e19a06f9e257/wai-logger/Network/Wai/Logger/Apache.hs#L45 unquoted (JSON.Number n) =
-- | TODO: Add the ability to filter apache logs on wai-extra and remove this function. toS $ formatScientific Fixed (if isInteger n then Just 0 else Nothing) n
pgrstFormat :: Status -> Wai.OutputFormatter unquoted (JSON.Bool b) = show b
pgrstFormat minStatus date req status responseSize = unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v
if status < minStatus
then mempty
else toLogStr (getSourceFromSocket req)
<> " - - ["
<> toLogStr date
<> "] \""
<> toLogStr (Wai.requestMethod req)
<> " "
<> toLogStr (Wai.rawPathInfo req <> Wai.rawQueryString req)
<> " "
<> toLogStr (show (Wai.httpVersion req)::Text)
<> "\" "
<> toLogStr (show (statusCode status)::Text)
<> " "
<> toLogStr (maybe "-" show responseSize::Text)
<> " \""
<> toLogStr (fromMaybe mempty $ Wai.requestHeaderReferer req)
<> "\" \""
<> toLogStr (fromMaybe mempty $ Wai.requestHeaderUserAgent req)
<> "\"\n"
where
getSourceFromSocket = BS.pack . Wai.showSockAddr . Wai.remoteHost
pgrstMiddleware :: LogLevel -> Wai.Application -> Wai.Application
pgrstMiddleware logLevel =
logger
. Wai.cors corsPolicy
. Wai.staticPolicy (Wai.only [("favicon.ico", "static/favicon.ico")])
where
logger = case logLevel of
LogCrit -> id
LogError -> unsafePerformIO $ Wai.mkRequestLogger Wai.def { Wai.outputFormat = Wai.CustomOutputFormat $ pgrstFormat status500}
LogWarn -> unsafePerformIO $ Wai.mkRequestLogger Wai.def { Wai.outputFormat = Wai.CustomOutputFormat $ pgrstFormat status400}
LogInfo -> Wai.logStdout
defaultCorsPolicy :: Wai.CorsResourcePolicy
defaultCorsPolicy = Wai.CorsResourcePolicy Nothing
["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"] ["Authorization"] Nothing
(Just $ 60*60*24) False False True
-- | CORS policy to be used in by Wai Cors middleware
corsPolicy :: Wai.Request -> Maybe Wai.CorsResourcePolicy
corsPolicy req = case lookup "origin" headers of
Just origin -> Just defaultCorsPolicy {
Wai.corsOrigins = Just ([origin], True)
, Wai.corsRequestHeaders = "Authentication" : accHeaders
, Wai.corsExposedHeaders = Just [
"Content-Encoding", "Content-Location", "Content-Range", "Content-Type"
, "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit"
]
}
Nothing -> Nothing
where
headers = Wai.requestHeaders req
accHeaders = case lookup "access-control-request-headers" headers of
Just hdrs -> map (CI.mk . BS.strip) $ BS.split ',' hdrs
Nothing -> []
unquoted :: JSON.Value -> Text
unquoted (JSON.String t) = t
unquoted (JSON.Number n) =
toS $ formatScientific Fixed (if isInteger n then Just 0 else Nothing) n
unquoted (JSON.Bool b) = show b
unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v
-- | Set a transaction to eventually roll back if requested and set respective -- | Set a transaction to eventually roll back if requested and set respective
-- headers on the response. -- headers on the response.
@@ -170,8 +88,9 @@ optionalRollback
-> ExceptT Error SQL.Transaction Wai.Response -> ExceptT Error SQL.Transaction Wai.Response
optionalRollback AppConfig{..} ApiRequest{..} transaction = do optionalRollback AppConfig{..} ApiRequest{..} transaction = do
resp <- catchError transaction $ return . errorResponseFor resp <- catchError transaction $ return . errorResponseFor
when (shouldRollback || (configDbTxRollbackAll && not shouldCommit)) when (shouldRollback || (configDbTxRollbackAll && not shouldCommit)) $ lift do
(lift SQL.condemn) SQL.sql "SET CONSTRAINTS ALL IMMEDIATE"
SQL.condemn
return $ Wai.mapResponseHeaders preferenceApplied resp return $ Wai.mapResponseHeaders preferenceApplied resp
where where
shouldCommit = shouldCommit =
+18 -16
View File
@@ -55,24 +55,26 @@ encode conf dbStructure tables procs schemaDescription =
makeMimeList :: [ContentType] -> MimeList makeMimeList :: [ContentType] -> MimeList
makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs
toSwaggerType :: Text -> SwaggerType t toSwaggerType :: Text -> Maybe (SwaggerType t)
toSwaggerType "character varying" = SwaggerString toSwaggerType "character varying" = Just SwaggerString
toSwaggerType "character" = SwaggerString toSwaggerType "character" = Just SwaggerString
toSwaggerType "text" = SwaggerString toSwaggerType "text" = Just SwaggerString
toSwaggerType "boolean" = SwaggerBoolean toSwaggerType "boolean" = Just SwaggerBoolean
toSwaggerType "smallint" = SwaggerInteger toSwaggerType "smallint" = Just SwaggerInteger
toSwaggerType "integer" = SwaggerInteger toSwaggerType "integer" = Just SwaggerInteger
toSwaggerType "bigint" = SwaggerInteger toSwaggerType "bigint" = Just SwaggerInteger
toSwaggerType "numeric" = SwaggerNumber toSwaggerType "numeric" = Just SwaggerNumber
toSwaggerType "real" = SwaggerNumber toSwaggerType "real" = Just SwaggerNumber
toSwaggerType "double precision" = SwaggerNumber toSwaggerType "double precision" = Just SwaggerNumber
toSwaggerType "ARRAY" = SwaggerArray toSwaggerType "ARRAY" = Just SwaggerArray
toSwaggerType _ = SwaggerString toSwaggerType "json" = Nothing
toSwaggerType "jsonb" = Nothing
toSwaggerType _ = Just SwaggerString
parseDefault :: Text -> Text -> Text parseDefault :: Text -> Text -> Text
parseDefault colType colDefault = parseDefault colType colDefault =
case toSwaggerType colType of case toSwaggerType colType of
SwaggerString -> wrapInQuotations $ case T.stripSuffix ("::" <> colType) colDefault of Just SwaggerString -> wrapInQuotations $ case T.stripSuffix ("::" <> colType) colDefault of
Just def -> T.dropAround (=='\'') def Just def -> T.dropAround (=='\'') def
Nothing -> colDefault Nothing -> colDefault
_ -> colDefault _ -> colDefault
@@ -124,7 +126,7 @@ makeProperty rels pks c = (colName c, Inline s)
& enum_ .~ e & enum_ .~ e
& format ?~ colType c & format ?~ colType c
& maxLength .~ (fromIntegral <$> colMaxLen c) & maxLength .~ (fromIntegral <$> colMaxLen c)
& type_ ?~ toSwaggerType (colType c) & type_ .~ toSwaggerType (colType c)
makeProcSchema :: ProcDescription -> Schema makeProcSchema :: ProcDescription -> Schema
makeProcSchema pd = makeProcSchema pd =
@@ -138,7 +140,7 @@ makeProcProperty :: ProcParam -> (Text, Referenced Schema)
makeProcProperty (ProcParam n t _ _) = (n, Inline s) makeProcProperty (ProcParam n t _ _) = (n, Inline s)
where where
s = (mempty :: Schema) s = (mempty :: Schema)
& type_ ?~ toSwaggerType t & type_ .~ toSwaggerType t
& format ?~ t & format ?~ t
makePreferParam :: [Text] -> Param makePreferParam :: [Text] -> Param
+3 -3
View File
@@ -172,10 +172,10 @@ asJsonF returnsScalar
| returnsScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar), '[]')::character varying" | returnsScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar), '[]')::character varying"
| otherwise = "coalesce(json_agg(_postgrest_t), '[]')::character varying" | otherwise = "coalesce(json_agg(_postgrest_t), '[]')::character varying"
asJsonSingleF :: Bool -> SqlFragment --TODO! unsafe when the query actually returns multiple rows, used only on inserting and returning single element asJsonSingleF :: Bool -> SqlFragment
asJsonSingleF returnsScalar asJsonSingleF returnsScalar
| returnsScalar = "coalesce(string_agg(to_json(_postgrest_t.pgrst_scalar)::text, ','), 'null')::character varying" | returnsScalar = "coalesce((json_agg(_postgrest_t.pgrst_scalar)->0)::text, 'null')"
| otherwise = "coalesce(string_agg(to_json(_postgrest_t)::text, ','), '')::character varying" | otherwise = "coalesce((json_agg(_postgrest_t)->0)::text, 'null')"
asBinaryF :: FieldName -> SqlFragment asBinaryF :: FieldName -> SqlFragment
asBinaryF fieldName = "coalesce(string_agg(_postgrest_t." <> pgFmtIdent fieldName <> ", ''), '')" asBinaryF fieldName = "coalesce(string_agg(_postgrest_t." <> pgFmtIdent fieldName <> ", ''), '')"
+3 -3
View File
@@ -364,9 +364,9 @@ userApiRequest conf@AppConfig{..} dbStructure req reqBody
headerRange = rangeRequested hdrs headerRange = rangeRequested hdrs
replaceLast x s = T.intercalate "." $ L.init (T.split (=='.') s) ++ [x] replaceLast x s = T.intercalate "." $ L.init (T.split (=='.') s) ++ [x]
limitParams :: M.HashMap Text NonnegRange limitParams :: M.HashMap Text NonnegRange
limitParams = M.fromList [(toS (replaceLast "limit" k), restrictRange (readMaybe . toS =<< v) allRange) | (k,v) <- qParams, isJust v, endingIn ["limit"] k] limitParams = M.fromList [(toS (replaceLast "limit" k), restrictRange (readMaybe =<< v) allRange) | (k,v) <- qParams, isJust v, endingIn ["limit"] k]
offsetParams :: M.HashMap Text NonnegRange offsetParams :: M.HashMap Text NonnegRange
offsetParams = M.fromList [(toS (replaceLast "limit" k), maybe allRange rangeGeq (readMaybe . toS =<< v)) | (k,v) <- qParams, isJust v, endingIn ["offset"] k] offsetParams = M.fromList [(toS (replaceLast "limit" k), maybe allRange rangeGeq (readMaybe =<< v)) | (k,v) <- qParams, isJust v, endingIn ["offset"] k]
urlRange = M.unionWith f limitParams offsetParams urlRange = M.unionWith f limitParams offsetParams
where where
@@ -505,7 +505,7 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost =
then length params == 1 && (ppType <$> headMay params) `elem` [Just "json", Just "jsonb"] then length params == 1 && (ppType <$> headMay params) `elem` [Just "json", Just "jsonb"]
-- If the function has no parameters, the arguments keys must be empty as well -- If the function has no parameters, the arguments keys must be empty as well
else if null params else if null params
then null argumentsKeys && contentType `notElem` [CTTextPlain, CTOctetStream] then null argumentsKeys && not (isInvPost && contentType `elem` [CTTextPlain, CTOctetStream])
-- A function has optional and required parameters. Optional parameters have a default value and -- A function has optional and required parameters. Optional parameters have a default value and
-- don't require arguments for the function to be executed, required parameters must have an argument present. -- don't require arguments for the function to be executed, required parameters must have an argument present.
else case L.partition ppReq params of else case L.partition ppReq params of
+1 -1
View File
@@ -56,7 +56,7 @@ import PostgREST.Request.Types
import qualified PostgREST.DbStructure.Relationship as Relationship import qualified PostgREST.DbStructure.Relationship as Relationship
import Protolude hiding (from) import Protolude hiding (from, isInfixOf)
-- | Builds the ReadRequest tree on a number of stages. -- | Builds the ReadRequest tree on a number of stages.
-- | Adds filters, order, limits on its respective nodes. -- | Adds filters, order, limits on its respective nodes.
+10 -4
View File
@@ -199,10 +199,10 @@ pOpExpr pSVal = try ( string "not" *> pDelimiter *> (OpExpr True <$> pOperation)
<|> pFts <|> pFts
<?> "operator (eq, gt, ...)" <?> "operator (eq, gt, ...)"
pTriVal = try (string "null" $> TriNull) pTriVal = try (ciString "null" $> TriNull)
<|> try (string "unknown" $> TriUnknown) <|> try (ciString "unknown" $> TriUnknown)
<|> try (string "true" $> TriTrue) <|> try (ciString "true" $> TriTrue)
<|> try (string "false" $> TriFalse) <|> try (ciString "false" $> TriFalse)
<?> "null or trilean value (unknown, true, false)" <?> "null or trilean value (unknown, true, false)"
pFts = do pFts = do
@@ -213,6 +213,12 @@ pOpExpr pSVal = try ( string "not" *> pDelimiter *> (OpExpr True <$> pOperation)
ops = M.filterWithKey (const . flip notElem ("in":"is":ftsOps)) operators ops = M.filterWithKey (const . flip notElem ("in":"is":ftsOps)) operators
ftsOps = M.keys ftsOperators ftsOps = M.keys ftsOperators
-- case insensitive char and string
ciChar :: Char -> GenParser Char state Char
ciChar c = char c <|> char (toUpper c)
ciString :: [Char] -> GenParser Char state [Char]
ciString = traverse ciChar
pSingleVal :: Parser SingleVal pSingleVal :: Parser SingleVal
pSingleVal = toS <$> many anyChar pSingleVal = toS <$> many anyChar
+4 -2
View File
@@ -30,10 +30,12 @@ prettyVersion =
-- | Version number used in docs. -- | Version number used in docs.
-- Pre-release versions link to the latest docs
-- Uses only the two first components of the version. Example: 'v1.1' -- Uses only the two first components of the version. Example: 'v1.1'
docsVersion :: Text docsVersion :: Text
docsVersion = docsVersion
"v" <> (T.intercalate "." . map show . take 2 $ versionBranch version) | isPreRelease = "latest"
| otherwise = "v" <> (T.intercalate "." . map show . take 2 $ versionBranch version)
-- | Versions with four components (e.g., '1.1.1.1') are treated as pre-releases. -- | Versions with four components (e.g., '1.1.1.1') are treated as pre-releases.
+1 -2
View File
@@ -152,11 +152,10 @@ connectionStatus appState =
loadSchemaCache :: AppState -> IO SCacheStatus loadSchemaCache :: AppState -> IO SCacheStatus
loadSchemaCache appState = do loadSchemaCache appState = do
AppConfig{..} <- AppState.getConfig appState AppConfig{..} <- AppState.getConfig appState
actualPgVersion <- AppState.getPgVersion appState
result <- result <-
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
SQL.use (AppState.getPool appState) . transaction SQL.ReadCommitted SQL.Read $ SQL.use (AppState.getPool appState) . transaction SQL.ReadCommitted SQL.Read $
queryDbStructure (toList configDbSchemas) configDbExtraSearchPath actualPgVersion configDbPreparedStatements queryDbStructure (toList configDbSchemas) configDbExtraSearchPath configDbPreparedStatements
case result of case result of
Left e -> do Left e -> do
let let
+4
View File
@@ -12,4 +12,8 @@ nix:
extra-deps: extra-deps:
- hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673 - hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673
- hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361 - hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361
- protolude-0.3.1@sha256:1cc9e5a5c26c33a43c52b554443dd9779fef13974eaa0beec7ca6d2551b400da,2647
- ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686 - ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686
- wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280
- wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671
- warp-3.3.19@sha256:c6a47029537d42844386170d732cdfe6d85b2f4279bbaefdd9b50caff6faeebb,10910
+35 -7
View File
@@ -5,29 +5,57 @@
packages: packages:
- completed: - completed:
hackage: hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673
pantry-tree: pantry-tree:
size: 641
sha256: b1b9a6a26ec765e5fe29f9a670a5c9ec7067ea00dee8491f0819284ff0201b6f sha256: b1b9a6a26ec765e5fe29f9a670a5c9ec7067ea00dee8491f0819284ff0201b6f
size: 641
hackage: hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673
original: original:
hackage: hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673 hackage: hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673
- completed: - completed:
hackage: hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361
pantry-tree: pantry-tree:
size: 310
sha256: 2f00d1467d0e226b966c2cd7bac433c8948e2f7bbdf8a44936029f66fc20b5f3 sha256: 2f00d1467d0e226b966c2cd7bac433c8948e2f7bbdf8a44936029f66fc20b5f3
size: 310
hackage: hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361
original: original:
hackage: hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361 hackage: hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361
- completed: - completed:
hackage: ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686
pantry-tree: pantry-tree:
size: 1089 sha256: 6452a6ca8d395f7d810139779bb0fd16fc1dbb00f1862630bc08ef5a100430f9
size: 1645
hackage: protolude-0.3.1@sha256:1cc9e5a5c26c33a43c52b554443dd9779fef13974eaa0beec7ca6d2551b400da,2647
original:
hackage: protolude-0.3.1@sha256:1cc9e5a5c26c33a43c52b554443dd9779fef13974eaa0beec7ca6d2551b400da,2647
- completed:
pantry-tree:
sha256: d2b8440a738719ef8430ec38fe33b129e3940e4ccf2c016a727a1110a43656bb sha256: d2b8440a738719ef8430ec38fe33b129e3940e4ccf2c016a727a1110a43656bb
size: 1089
hackage: ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686
original: original:
hackage: ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686 hackage: ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686
- completed:
pantry-tree:
sha256: a544ea95288d188e893322a8e6d68f2b1f844f772dbea1f26e5c0c1a74694f56
size: 4053
hackage: wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280
original:
hackage: wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280
- completed:
pantry-tree:
sha256: 52b5abf5c4c09bcfbc06e01f761a75c32cbd3e6ba23c8843981933fcc31ed53c
size: 474
hackage: wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671
original:
hackage: wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671
- completed:
pantry-tree:
sha256: 99ff839445ba2c9e29a294b45904e3f4575336c7d2b4504ce310d611661c761d
size: 3973
hackage: warp-3.3.19@sha256:c6a47029537d42844386170d732cdfe6d85b2f4279bbaefdd9b50caff6faeebb,10910
original:
hackage: warp-3.3.19@sha256:c6a47029537d42844386170d732cdfe6d85b2f4279bbaefdd9b50caff6faeebb,10910
snapshots: snapshots:
- completed: - completed:
sha256: 87842ecbaa8ca9cee59a7e6be52369dbed82ed075cb4e0d152614a627e8fd488
size: 586069 size: 586069
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/14.yaml url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/14.yaml
sha256: 87842ecbaa8ca9cee59a7e6be52369dbed82ed075cb4e0d152614a627e8fd488
original: lts-18.14 original: lts-18.14
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-74
View File
@@ -1,74 +0,0 @@
module Feature.CorsSpec where
-- {{{ Imports
import qualified Data.ByteString.Lazy as BL
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleBody, simpleHeaders))
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Protolude
import SpecHelper
-- }}}
spec :: SpecWith ((), Application)
spec =
describe "CORS" $ do
let preflightHeaders = [
("Accept", "*/*"),
("Origin", "http://example.com"),
("Access-Control-Request-Method", "POST"),
("Access-Control-Request-Headers", "Foo,Bar") ]
let normalCors = [
("Host", "localhost:3000"),
("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0"),
("Origin", "http://localhost:8000"),
("Accept", "text/csv, */*; q=0.01"),
("Accept-Language", "en-US,en;q=0.5"),
("Accept-Encoding", "gzip, deflate"),
("Referer", "http://localhost:8000/"),
("Connection", "keep-alive") ]
describe "preflight request" $ do
it "replies naively and permissively to preflight request" $ do
r <- request methodOptions "/items" preflightHeaders ""
liftIO $ do
let respHeaders = simpleHeaders r
respHeaders `shouldSatisfy` matchHeader
"Access-Control-Allow-Origin"
"http://example.com"
respHeaders `shouldSatisfy` matchHeader
"Access-Control-Allow-Credentials"
"true"
respHeaders `shouldSatisfy` matchHeader
"Access-Control-Allow-Methods"
"GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD"
respHeaders `shouldSatisfy` matchHeader
"Access-Control-Allow-Headers"
"Authentication, Foo, Bar, Accept, Accept-Language, Content-Language"
respHeaders `shouldSatisfy` matchHeader
"Access-Control-Max-Age"
"86400"
it "suppresses body in response" $ do
r <- request methodOptions "/" preflightHeaders ""
liftIO $ simpleBody r `shouldBe` ""
describe "regular request" $
it "exposes necesssary response headers" $ do
r <- request methodGet "/items" [("Origin", "http://example.com")] ""
liftIO $ simpleHeaders r `shouldSatisfy` matchHeader
"Access-Control-Expose-Headers"
"Content-Encoding, Content-Location, Content-Range, Content-Type, \
\Date, Location, Server, Transfer-Encoding, Range-Unit"
describe "postflight request" $
it "allows INFO body through even with CORS request headers present" $ do
r <- request methodOptions "/items" normalCors ""
liftIO $ do
simpleHeaders r `shouldSatisfy` matchHeader
"Access-Control-Allow-Origin" "\\*"
simpleBody r `shouldSatisfy` BL.null
-62
View File
@@ -1,62 +0,0 @@
\set AUTHENTICATOR current_user
DROP ROLE IF EXISTS postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author;
CREATE ROLE postgrest_test_anonymous;
CREATE ROLE postgrest_test_default_role;
CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :USER;
-- reloadable config options for io tests
ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_aud = 'https://example.org';
ALTER ROLE postgrest_test_authenticator SET pgrst.openapi_server_proxy_uri = 'https://example.org/api';
ALTER ROLE postgrest_test_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.db-config';
ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_secret = 'REALLYREALLYREALLYREALLYVERYSAFE';
ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_secret_is_base64 = 'true';
ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_tx_end = 'commit-allow-override';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_root_spec = 'root';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_prepared_statements = 'false';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_pre_request = 'test.custom_headers';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_max_rows = '1000';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_extra_search_path = 'public, extensions';
-- override with database specific setting
ALTER ROLE postgrest_test_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE';
ALTER ROLE postgrest_test_authenticator IN DATABASE :DBNAME SET pgrst.db_extra_search_path = 'public, extensions, private';
-- other database settings that should be ignored
DROP DATABASE IF EXISTS other;
CREATE DATABASE other;
ALTER ROLE postgrest_test_authenticator IN DATABASE other SET pgrst.db_max_rows = '1111';
-- non-reloadable configs for io tests
ALTER ROLE postgrest_test_authenticator SET pgrst.server_host = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.server_port = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.server_unix_socket = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.server_unix_socket_mode = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.log_level = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_anon_role = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_uri = 'postgresql://ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_channel_enabled = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_channel = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_pool = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_pool_timeout = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst.db_config = 'ignored';
-- other authenticator reloadable config options for io tests
CREATE ROLE other_authenticator LOGIN NOINHERIT;
ALTER ROLE other_authenticator SET pgrst.jwt_aud = 'https://otherexample.org';
ALTER ROLE other_authenticator SET pgrst.openapi_server_proxy_uri = 'https://otherexample.org/api';
ALTER ROLE other_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.other-db-config';
ALTER ROLE other_authenticator SET pgrst.jwt_secret = 'ODERREALLYREALLYREALLYREALLYVERYSAFE';
ALTER ROLE other_authenticator SET pgrst.jwt_secret_is_base64 = 'true';
ALTER ROLE other_authenticator SET pgrst.jwt_role_claim_key = '."other"."role"';
ALTER ROLE other_authenticator SET pgrst.db_tx_end = 'rollback-allow-override';
ALTER ROLE other_authenticator SET pgrst.db_schemas = 'test, other_tenant1, other_tenant2';
ALTER ROLE other_authenticator SET pgrst.db_root_spec = 'other_root';
ALTER ROLE other_authenticator SET pgrst.db_prepared_statements = 'false';
ALTER ROLE other_authenticator SET pgrst.db_pre_request = 'test.other_custom_headers';
ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100';
ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other';
ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled';
-4
View File
@@ -1,4 +0,0 @@
db-pool = 1
server-unix-socket = "$(POSTGREST_TEST_SOCKET)"
jwt-secret = "reallyreallyreallyreallyverysafe"
db-config = false
@@ -1,5 +1,2 @@
db-pool = 1
db-pool-timeout = 1
app.settings.external_api_secret = "0123456789abcdef" app.settings.external_api_secret = "0123456789abcdef"
db-config = false db-config = false
@@ -1,5 +1,3 @@
db-pool = 1
# Read secret from a file: /dev/stdin (alias for standard input) # Read secret from a file: /dev/stdin (alias for standard input)
jwt-secret = "@/dev/stdin" jwt-secret = "@/dev/stdin"
jwt-secret-is-base64 = true jwt-secret-is-base64 = true
@@ -1,4 +1,3 @@
db-uri = "@/dev/stdin" db-uri = "@/dev/stdin"
db-pool = 1
jwt-secret = "reallyreallyreallyreallyverysafe" jwt-secret = "reallyreallyreallyreallyverysafe"
db-config = false db-config = false
@@ -9,7 +9,7 @@ db-pre-request = "check_alias"
db-prepared-statements = true db-prepared-statements = true
db-root-spec = "open_alias" db-root-spec = "open_alias"
db-schemas = "provided_through_alias" db-schemas = "provided_through_alias"
db-config = "false" db-config = false
db-tx-end = "commit" db-tx-end = "commit"
db-uri = "required" db-uri = "required"
db-use-legacy-gucs = true db-use-legacy-gucs = true
@@ -9,7 +9,7 @@ db-pre-request = ""
db-prepared-statements = false db-prepared-statements = false
db-root-spec = "" db-root-spec = ""
db-schemas = "required" db-schemas = "required"
db-config = "false" db-config = false
db-tx-end = "commit" db-tx-end = "commit"
db-uri = "required" db-uri = "required"
db-use-legacy-gucs = true db-use-legacy-gucs = true
@@ -9,7 +9,7 @@ db-pre-request = ""
db-prepared-statements = false db-prepared-statements = false
db-root-spec = "" db-root-spec = ""
db-schemas = "required" db-schemas = "required"
db-config = "false" db-config = false
db-tx-end = "commit" db-tx-end = "commit"
db-uri = "required" db-uri = "required"
db-use-legacy-gucs = true db-use-legacy-gucs = true
@@ -9,7 +9,7 @@ db-pre-request = ""
db-prepared-statements = true db-prepared-statements = true
db-root-spec = "" db-root-spec = ""
db-schemas = "required" db-schemas = "required"
db-config = "false" db-config = false
db-tx-end = "commit" db-tx-end = "commit"
db-uri = "required" db-uri = "required"
db-use-legacy-gucs = true db-use-legacy-gucs = true
@@ -9,7 +9,7 @@ db-pre-request = "test.other_custom_headers"
db-prepared-statements = false db-prepared-statements = false
db-root-spec = "other_root" db-root-spec = "other_root"
db-schemas = "test,other_tenant1,other_tenant2" db-schemas = "test,other_tenant1,other_tenant2"
db-config = "true" db-config = true
db-tx-end = "rollback-allow-override" db-tx-end = "rollback-allow-override"
db-uri = "<REPLACED_WITH_DB_URI>" db-uri = "<REPLACED_WITH_DB_URI>"
db-use-legacy-gucs = false db-use-legacy-gucs = false
@@ -9,14 +9,14 @@ db-pre-request = "test.custom_headers"
db-prepared-statements = false db-prepared-statements = false
db-root-spec = "root" db-root-spec = "root"
db-schemas = "test,tenant1,tenant2" db-schemas = "test,tenant1,tenant2"
db-config = "true" db-config = true
db-tx-end = "commit-allow-override" db-tx-end = "commit-allow-override"
db-uri = "<REPLACED_WITH_DB_URI>" db-uri = "<REPLACED_WITH_DB_URI>"
db-use-legacy-gucs = false db-use-legacy-gucs = false
jwt-aud = "https://example.org" jwt-aud = "https://example.org"
jwt-role-claim-key = ".\"a\".\"role\"" jwt-role-claim-key = ".\"a\".\"role\""
jwt-secret = "OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE" jwt-secret = "OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE"
jwt-secret-is-base64 = true jwt-secret-is-base64 = false
log-level = "info" log-level = "info"
openapi-mode = "ignore-privileges" openapi-mode = "ignore-privileges"
openapi-server-proxy-uri = "https://example.org/api" openapi-server-proxy-uri = "https://example.org/api"
@@ -9,7 +9,7 @@ db-pre-request = "please_run_fast"
db-prepared-statements = false db-prepared-statements = false
db-root-spec = "openapi_v3" db-root-spec = "openapi_v3"
db-schemas = "multi,tenant,setup" db-schemas = "multi,tenant,setup"
db-config = "false" db-config = false
db-tx-end = "rollback-allow-override" db-tx-end = "rollback-allow-override"
db-uri = "tmp_db" db-uri = "tmp_db"
db-use-legacy-gucs = false db-use-legacy-gucs = false
@@ -9,7 +9,7 @@ db-pre-request = ""
db-prepared-statements = true db-prepared-statements = true
db-root-spec = "" db-root-spec = ""
db-schemas = "required" db-schemas = "required"
db-config = "true" db-config = true
db-tx-end = "commit" db-tx-end = "commit"
db-uri = "required" db-uri = "required"
db-use-legacy-gucs = true db-use-legacy-gucs = true
@@ -9,7 +9,7 @@ db-pre-request = "please_run_fast"
db-prepared-statements = false db-prepared-statements = false
db-root-spec = "openapi_v3" db-root-spec = "openapi_v3"
db-schemas = "multi, tenant,setup" db-schemas = "multi, tenant,setup"
db-config = "false" db-config = false
db-tx-end = "rollback-allow-override" db-tx-end = "rollback-allow-override"
db-uri = "tmp_db" db-uri = "tmp_db"
db-use-legacy-gucs = false db-use-legacy-gucs = false
@@ -1,4 +1,3 @@
db-pool = 1
jwt-role-claim-key = "$(ROLE_CLAIM_KEY)" jwt-role-claim-key = "$(ROLE_CLAIM_KEY)"
jwt-secret = "reallyreallyreallyreallyverysafe" jwt-secret = "reallyreallyreallyreallyverysafe"
db-config = false db-config = false
@@ -1,5 +1,3 @@
db-pool = 1
# Read secret from a file: /dev/stdin (alias for standard input) # Read secret from a file: /dev/stdin (alias for standard input)
jwt-secret = "@/dev/stdin" jwt-secret = "@/dev/stdin"
jwt-secret-is-base64 = false jwt-secret-is-base64 = false
@@ -1,5 +1,3 @@
db-pool = 1
jwt-secret = "$(JWT_SECRET_FILE)" jwt-secret = "$(JWT_SECRET_FILE)"
jwt-secret-is-base64 = false jwt-secret-is-base64 = false
db-config = false db-config = false
@@ -1,5 +1,4 @@
db-schemas = "test" db-schemas = "public"
db-pool = 1
app.settings.name_var = "John" app.settings.name_var = "John"
jwt-secret = "invalidinvalidinvalidinvalidinvalid" jwt-secret = "invalidinvalidinvalidinvalidinvalid"
@@ -1,3 +1,2 @@
db-pool = 1
jwt-secret = "reallyreallyreallyreallyverysafe" jwt-secret = "reallyreallyreallyreallyverysafe"
db-config = false db-config = false
+55
View File
@@ -0,0 +1,55 @@
CREATE ROLE db_config_authenticator LOGIN NOINHERIT;
-- reloadable config options
ALTER ROLE db_config_authenticator SET pgrst.jwt_aud = 'https://example.org';
ALTER ROLE db_config_authenticator SET pgrst.openapi_server_proxy_uri = 'https://example.org/api';
ALTER ROLE db_config_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.db-config';
ALTER ROLE db_config_authenticator SET pgrst.jwt_secret = 'REALLY=REALLY=REALLY=REALLY=VERY=SAFE';
ALTER ROLE db_config_authenticator SET pgrst.jwt_secret_is_base64 = 'false';
ALTER ROLE db_config_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"';
ALTER ROLE db_config_authenticator SET pgrst.db_tx_end = 'commit-allow-override';
ALTER ROLE db_config_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2';
ALTER ROLE db_config_authenticator SET pgrst.db_root_spec = 'root';
ALTER ROLE db_config_authenticator SET pgrst.db_prepared_statements = 'false';
ALTER ROLE db_config_authenticator SET pgrst.db_pre_request = 'test.custom_headers';
ALTER ROLE db_config_authenticator SET pgrst.db_max_rows = '1000';
ALTER ROLE db_config_authenticator SET pgrst.db_extra_search_path = 'public, extensions';
-- override with database specific setting
ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE';
ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.db_extra_search_path = 'public, extensions, private';
-- other database settings that should be ignored
CREATE DATABASE other;
ALTER ROLE db_config_authenticator IN DATABASE other SET pgrst.db_max_rows = '1111';
-- non-reloadable configs
ALTER ROLE db_config_authenticator SET pgrst.server_host = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.server_port = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket_mode = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.log_level = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_anon_role = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_uri = 'postgresql://ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_channel_enabled = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_channel = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_pool = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_pool_timeout = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_config = 'ignored';
-- other authenticator reloadable config options
CREATE ROLE other_authenticator LOGIN NOINHERIT;
ALTER ROLE other_authenticator SET pgrst.jwt_aud = 'https://otherexample.org';
ALTER ROLE other_authenticator SET pgrst.openapi_server_proxy_uri = 'https://otherexample.org/api';
ALTER ROLE other_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.other-db-config';
ALTER ROLE other_authenticator SET pgrst.jwt_secret = 'ODERREALLYREALLYREALLYREALLYVERYSAFE';
ALTER ROLE other_authenticator SET pgrst.jwt_secret_is_base64 = 'true';
ALTER ROLE other_authenticator SET pgrst.jwt_role_claim_key = '."other"."role"';
ALTER ROLE other_authenticator SET pgrst.db_tx_end = 'rollback-allow-override';
ALTER ROLE other_authenticator SET pgrst.db_schemas = 'test, other_tenant1, other_tenant2';
ALTER ROLE other_authenticator SET pgrst.db_root_spec = 'other_root';
ALTER ROLE other_authenticator SET pgrst.db_prepared_statements = 'false';
ALTER ROLE other_authenticator SET pgrst.db_pre_request = 'test.other_custom_headers';
ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100';
ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other';
ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled';
+81
View File
@@ -0,0 +1,81 @@
\ir db_config.sql
CREATE ROLE postgrest_test_anonymous;
CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous, postgrest_test_author TO :USER;
CREATE SCHEMA v1;
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
CREATE TABLE authors_only ();
GRANT SELECT ON authors_only TO postgrest_test_author;
CREATE TABLE projects AS SELECT FROM generate_series(1,5);
GRANT SELECT ON projects TO postgrest_test_anonymous;
create function get_guc_value(name text) returns text as $$
select nullif(current_setting(name), '')::text;
$$ language sql;
create function v1.get_guc_value(name text) returns text as $$
select nullif(current_setting(name), '')::text;
$$ language sql;
create function uses_prepared_statements() returns bool as $$
select count(name) > 0 from pg_catalog.pg_prepared_statements
$$ language sql;
create function change_max_rows_config(val int, notify bool default false) returns void as $_$
begin
execute format($$
alter role postgrest_test_authenticator set pgrst.db_max_rows = %L;
$$, val);
if notify then
perform pg_notify('pgrst', 'reload config');
end if;
end $_$ volatile security definer language plpgsql ;
create function reset_max_rows_config() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.db_max_rows;
end $_$ volatile security definer language plpgsql ;
create function change_db_schema_and_full_reload(schemas text) returns void as $_$
begin
execute format($$
alter role postgrest_test_authenticator set pgrst.db_schemas = %L;
$$, schemas);
perform pg_notify('pgrst', 'reload config');
perform pg_notify('pgrst', 'reload schema');
end $_$ volatile security definer language plpgsql ;
create function v1.reset_db_schema_config() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.db_schemas;
perform pg_notify('pgrst', 'reload config');
perform pg_notify('pgrst', 'reload schema');
end $_$ volatile security definer language plpgsql ;
create function invalid_role_claim_key_reload() returns void as $_$
begin
alter role postgrest_test_authenticator set pgrst.jwt_role_claim_key = 'test';
perform pg_notify('pgrst', 'reload config');
end $_$ volatile security definer language plpgsql ;
create function reset_invalid_role_claim_key() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.jwt_role_claim_key;
perform pg_notify('pgrst', 'reload config');
end $_$ volatile security definer language plpgsql ;
create function reload_pgrst_config() returns void as $_$
begin
perform pg_notify('pgrst', 'reload config');
end $_$ language plpgsql ;
create or replace function raise_bad_pt() returns void as $$
begin
raise sqlstate 'PT40A' using message = 'Wrong';
end;
$$ language plpgsql;
@@ -144,6 +144,7 @@ roleclaims:
data: data:
postgrest: postgrest:
a_role: postgrest_test_author a_role: postgrest_test_author
other: claims
expected_status: 200 expected_status: 200
- key: '.customObject.manyRoles[1]' - key: '.customObject.manyRoles[1]'
data: data:
@@ -151,21 +152,25 @@ roleclaims:
manyRoles: manyRoles:
- other - other
- postgrest_test_author - postgrest_test_author
other: {}
expected_status: 200 expected_status: 200
- key: '."https://www.example.com/roles"[0].value' - key: '."https://www.example.com/roles"[0].value'
data: data:
'https://www.example.com/roles': 'https://www.example.com/roles':
- value: postgrest_test_author - value: postgrest_test_author
other: 666
expected_status: 200 expected_status: 200
- key: '.myDomain[3]' - key: '.myDomain[3]'
data: data:
myDomain: myDomain:
- other - other
- postgrest_test_author - postgrest_test_author
other: 1.23
expected_status: 401 expected_status: 401
- key: '.myRole' - key: '.myRole'
data: data:
role: postgrest_test_author role: postgrest_test_author
other: true
expected_status: 401 expected_status: 401
invalidroleclaimkeys: invalidroleclaimkeys:
+97 -57
View File
@@ -7,6 +7,7 @@ from itertools import repeat
from operator import attrgetter from operator import attrgetter
import os import os
import pathlib import pathlib
import re
import shutil import shutil
import signal import signal
import socket import socket
@@ -77,7 +78,7 @@ class PostgrestProcess:
@pytest.fixture @pytest.fixture
def dburi(): def dburi():
"Postgres database connection URI." "Postgres database connection URI."
return os.getenv("PGRST_DB_URI").encode("utf-8") return os.getenv("PGRST_DB_URI").encode()
@pytest.fixture @pytest.fixture
@@ -85,7 +86,7 @@ def defaultenv():
"Default environment for PostgREST." "Default environment for PostgREST."
return { return {
"PGRST_DB_URI": os.environ["PGRST_DB_URI"], "PGRST_DB_URI": os.environ["PGRST_DB_URI"],
"PGRST_DB_SCHEMAS": os.environ["PGRST_DB_SCHEMAS"], "PGRST_DB_SCHEMAS": "public",
"PGRST_DB_ANON_ROLE": os.environ["PGRST_DB_ANON_ROLE"], "PGRST_DB_ANON_ROLE": os.environ["PGRST_DB_ANON_ROLE"],
"PGRST_DB_CONFIG": "false", "PGRST_DB_CONFIG": "false",
"PGRST_LOG_LEVEL": "info", "PGRST_LOG_LEVEL": "info",
@@ -118,7 +119,7 @@ def cli(args, env=None, stdin=None):
result = process.communicate(timeout=5)[0] result = process.communicate(timeout=5)[0]
if process.returncode != 0: if process.returncode != 0:
raise PostgrestError() raise PostgrestError()
return result.decode("utf-8") return result.decode()
finally: finally:
process.kill() process.kill()
process.wait() process.wait()
@@ -138,6 +139,8 @@ def dumpconfig(configpath=None, env=None, stdin=None):
def run(configpath=None, stdin=None, env=None, port=None): def run(configpath=None, stdin=None, env=None, port=None):
"Run PostgREST and yield an endpoint that is ready for connections." "Run PostgREST and yield an endpoint that is ready for connections."
env = env or {} env = env or {}
env["PGRST_DB_POOL"] = "1"
env["PGRST_DB_POOL_TIMEOUT"] = "1"
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
if port: if port:
@@ -156,17 +159,28 @@ def run(configpath=None, stdin=None, env=None, port=None):
command.append(configpath) command.append(configpath)
process = subprocess.Popen( process = subprocess.Popen(
command, stdin=subprocess.PIPE, stderr=subprocess.PIPE, env=env command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
) )
os.set_blocking(process.stdout.fileno(), False)
try: try:
process.stdin.write(stdin or b"") process.stdin.write(stdin or b"")
process.stdin.close() process.stdin.close()
wait_until_ready(baseurl) wait_until_ready(baseurl)
process.stdout.read()
yield PostgrestProcess(process=process, session=PostgrestSession(baseurl)) yield PostgrestProcess(process=process, session=PostgrestSession(baseurl))
finally: finally:
remaining_output = process.stdout.read()
if remaining_output:
print(remaining_output.decode())
process.terminate() process.terminate()
try: try:
process.wait(timeout=1) process.wait(timeout=1)
@@ -187,6 +201,7 @@ def wait_until_ready(url):
"Wait for the given HTTP endpoint to return a status of 200." "Wait for the given HTTP endpoint to return a status of 200."
session = requests_unixsocket.Session() session = requests_unixsocket.Session()
response = None
for _ in range(10): for _ in range(10):
try: try:
response = session.get(url, timeout=1) response = session.get(url, timeout=1)
@@ -197,7 +212,10 @@ def wait_until_ready(url):
time.sleep(0.1) time.sleep(0.1)
raise PostgrestTimedOut() if response:
raise PostgrestTimedOut(f"{response.status_code}: {response.text}")
else:
raise PostgrestTimedOut()
def authheader(token): def authheader(token):
@@ -273,7 +291,7 @@ def test_expected_config_from_environment():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"role, expectedconfig", "role, expectedconfig",
[ [
("postgrest_test_authenticator", "no-defaults-with-db.config"), ("db_config_authenticator", "no-defaults-with-db.config"),
("other_authenticator", "no-defaults-with-db-other-authenticator.config"), ("other_authenticator", "no-defaults-with-db-other-authenticator.config"),
], ],
) )
@@ -299,23 +317,6 @@ def test_expected_config_from_db_settings(defaultenv, role, expectedconfig):
assert dumpconfig(configpath=config, env=env) == expected assert dumpconfig(configpath=config, env=env) == expected
def test_read_db_setting(defaultenv):
"""
Should be able to read db settings with current_setting.
See: https://github.com/PostgREST/postgrest/pull/1729#discussion_r572946461
"""
env = {
**defaultenv,
"PGRST_DB_CONFIG": "true",
}
with run(env=env) as postgrest:
uri = "/rpc/get_guc_value?name=pgrst.jwt_secret"
response = postgrest.session.get(uri)
assert response.text == '"OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE"'
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
[conf for conf in CONFIGSDIR.iterdir() if conf.suffix == ".config"], [conf for conf in CONFIGSDIR.iterdir() if conf.suffix == ".config"],
@@ -456,7 +457,7 @@ def test_iat_claim(defaultenv):
response = postgrest.session.get("/authors_only", headers=headers) response = postgrest.session.get("/authors_only", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
time.sleep(0.5) time.sleep(0.1)
def test_app_settings(defaultenv): def test_app_settings(defaultenv):
@@ -473,7 +474,6 @@ def test_app_settings(defaultenv):
uri = "/rpc/get_guc_value?name=app.settings.external_api_secret" uri = "/rpc/get_guc_value?name=app.settings.external_api_secret"
response = postgrest.session.get(uri) response = postgrest.session.get(uri)
assert response.status_code == 200
assert response.text == '"0123456789abcdef"' assert response.text == '"0123456789abcdef"'
@@ -486,7 +486,6 @@ def test_app_settings_reload(tmp_path, defaultenv):
with run(configfile, env=defaultenv) as postgrest: with run(configfile, env=defaultenv) as postgrest:
response = postgrest.session.get(uri) response = postgrest.session.get(uri)
assert response.status_code == 200
assert response.text == '"John"' assert response.text == '"John"'
# change setting # change setting
@@ -497,7 +496,6 @@ def test_app_settings_reload(tmp_path, defaultenv):
time.sleep(0.1) time.sleep(0.1)
response = postgrest.session.get(uri) response = postgrest.session.get(uri)
assert response.status_code == 200
assert response.text == '"Jane"' assert response.text == '"Jane"'
@@ -578,16 +576,15 @@ def test_db_schema_reload(tmp_path, defaultenv):
configfile = tmp_path / "test.config" configfile = tmp_path / "test.config"
configfile.write_text(config) configfile.write_text(config)
headers = {"Accept-Profile": "v1"}
env = {key: value for key, value in defaultenv.items() if key != "PGRST_DB_SCHEMAS"} env = {key: value for key, value in defaultenv.items() if key != "PGRST_DB_SCHEMAS"}
with run(configfile, env=env) as postgrest: with run(configfile, env=env) as postgrest:
response = postgrest.session.get("/parents", headers=headers) response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
assert response.status_code == 404 assert response.text == '"public, public"'
# change setting # change setting
configfile.write_text( configfile.write_text(
config.replace('db-schemas = "test"', 'db-schemas = "test, v1"') config.replace('db-schemas = "public"', 'db-schemas = "v1"')
) )
# reload config # reload config
@@ -598,36 +595,32 @@ def test_db_schema_reload(tmp_path, defaultenv):
time.sleep(0.1) time.sleep(0.1)
response = postgrest.session.get("/parents", headers=headers) response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
assert response.status_code == 200 assert response.text == '"v1, public"'
def test_db_schema_notify_reload(defaultenv): def test_db_schema_notify_reload(defaultenv):
"DB schema and config should be reloaded when PostgREST is sent a NOTIFY" "DB schema and config should be reloaded when PostgREST is sent a NOTIFY"
env = { env = {**defaultenv, "PGRST_DB_CONFIG": "true", "PGRST_DB_CHANNEL_ENABLED": "true"}
**defaultenv,
"PGRST_DB_CONFIG": "true",
"PGRST_DB_CHANNEL_ENABLED": "true",
"PGRST_DB_SCHEMAS": "test",
}
with run(env=env) as postgrest: with run(env=env) as postgrest:
response = postgrest.session.get("/parents") response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
assert response.status_code == 404 assert response.text == '"public, public"'
# change db-schemas config on the db and reload config and cache with notify # change db-schemas config on the db and reload config and cache with notify
postgrest.session.post( postgrest.session.post(
"/rpc/change_db_schema_and_full_reload", data={"schemas": "v1"} "/rpc/change_db_schema_and_full_reload", data={"schemas": "v1"}
) )
time.sleep(0.5) time.sleep(0.1)
response = postgrest.session.get("/parents?select=*,children(*)") response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
assert response.status_code == 200 assert response.text == '"v1, public"'
# reset db-schemas config on the db # reset db-schemas config on the db
postgrest.session.post("/rpc/reset_db_schema_config") response = postgrest.session.post("/rpc/reset_db_schema_config")
assert response.status_code == 200
def test_max_rows_reload(defaultenv): def test_max_rows_reload(defaultenv):
@@ -641,6 +634,7 @@ def test_max_rows_reload(defaultenv):
with run(config, env=env) as postgrest: with run(config, env=env) as postgrest:
response = postgrest.session.head("/projects") response = postgrest.session.head("/projects")
assert response.status_code == 200
assert response.headers["Content-Range"] == "0-4/*" assert response.headers["Content-Range"] == "0-4/*"
# change max-rows config on the db # change max-rows config on the db
@@ -652,11 +646,12 @@ def test_max_rows_reload(defaultenv):
time.sleep(0.1) time.sleep(0.1)
response = postgrest.session.head("/projects") response = postgrest.session.head("/projects")
assert response.status_code == 200
assert response.headers["Content-Range"] == "0-0/*" assert response.headers["Content-Range"] == "0-0/*"
# reset max-rows config on the db # reset max-rows config on the db
postgrest.session.post("/rpc/reset_max_rows_config") response = postgrest.session.post("/rpc/reset_max_rows_config")
assert response.status_code == 200
def test_max_rows_notify_reload(defaultenv): def test_max_rows_notify_reload(defaultenv):
@@ -670,6 +665,7 @@ def test_max_rows_notify_reload(defaultenv):
with run(env=env) as postgrest: with run(env=env) as postgrest:
response = postgrest.session.head("/projects") response = postgrest.session.head("/projects")
assert response.status_code == 200
assert response.headers["Content-Range"] == "0-4/*" assert response.headers["Content-Range"] == "0-4/*"
# change max-rows config on the db and reload with notify # change max-rows config on the db and reload with notify
@@ -680,11 +676,12 @@ def test_max_rows_notify_reload(defaultenv):
time.sleep(0.1) time.sleep(0.1)
response = postgrest.session.head("/projects") response = postgrest.session.head("/projects")
assert response.status_code == 200
assert response.headers["Content-Range"] == "0-0/*" assert response.headers["Content-Range"] == "0-0/*"
# reset max-rows config on the db # reset max-rows config on the db
postgrest.session.post("/rpc/reset_max_rows_config") response = postgrest.session.post("/rpc/reset_max_rows_config")
assert response.status_code == 200
def test_invalid_role_claim_key_notify_reload(defaultenv): def test_invalid_role_claim_key_notify_reload(defaultenv):
@@ -694,20 +691,23 @@ def test_invalid_role_claim_key_notify_reload(defaultenv):
**defaultenv, **defaultenv,
"PGRST_DB_CONFIG": "true", "PGRST_DB_CONFIG": "true",
"PGRST_DB_CHANNEL_ENABLED": "true", "PGRST_DB_CHANNEL_ENABLED": "true",
"PGRST_LOG_LEVEL": "crit",
} }
with run(env=env) as postgrest: with run(env=env) as postgrest:
postgrest.session.post("/rpc/invalid_role_claim_key_reload") postgrest.session.post("/rpc/invalid_role_claim_key_reload")
# skips the first lines from stderr, the "Attempting to connect to database", "Connection successful", etc. output = None
# this is a hack to avoid readline() from locking up the test for _ in range(10):
for _ in range(6): output = postgrest.process.stdout.readline()
postgrest.process.stderr.readline() if output:
assert "failed to parse role-claim-key value" in str( break
postgrest.process.stderr.readline() time.sleep(0.1)
)
postgrest.session.post("/rpc/reset_invalid_role_claim_key") assert "failed to parse role-claim-key value" in output.decode()
response = postgrest.session.post("/rpc/reset_invalid_role_claim_key")
assert response.status_code == 200
def test_db_prepared_statements_enable(defaultenv): def test_db_prepared_statements_enable(defaultenv):
@@ -729,3 +729,43 @@ def test_db_prepared_statements_disable(defaultenv):
with run(env=env) as postgrest: with run(env=env) as postgrest:
response = postgrest.session.post("/rpc/uses_prepared_statements") response = postgrest.session.post("/rpc/uses_prepared_statements")
assert response.text == "false" assert response.text == "false"
@pytest.mark.parametrize(
"level, has_output",
[
("info", [True, True, True]),
("warn", [False, True, True]),
("error", [False, False, True]),
("crit", [False, False, False]),
],
)
def test_log_level(level, has_output, defaultenv):
"log_level should filter request logging"
env = {**defaultenv, "PGRST_LOG_LEVEL": level}
with run(env=env) as postgrest:
response = postgrest.session.get("/")
assert response.status_code == 200
if has_output[0]:
assert re.match(
r'unknownSocket - - \[.+\] "GET / HTTP/1.1" 200 - "" "python-requests/.+"',
postgrest.process.stdout.readline().decode(),
)
response = postgrest.session.get("/unknown")
assert response.status_code == 404
if has_output[1]:
assert re.match(
r'unknownSocket - - \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"',
postgrest.process.stdout.readline().decode(),
)
response = postgrest.session.get("/rpc/raise_bad_pt")
assert response.status_code == 500
if has_output[2]:
assert re.match(
r'unknownSocket - - \[.+\] "GET /rpc/raise_bad_pt HTTP/1.1" 500 - "" "python-requests/.+"',
postgrest.process.stdout.readline().decode(),
)
+34
View File
@@ -0,0 +1,34 @@
CREATE ROLE postgrest_test_anonymous;
GRANT postgrest_test_anonymous TO :USER;
CREATE SCHEMA test;
-- PUT+PATCH target needs one record and column to modify
CREATE TABLE test.actors (
PRIMARY KEY (actor),
actor INT,
name TEXT,
last_modified TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO test.actors VALUES (1, 'John Doe');
-- POST target needs generated PK
CREATE TABLE test.films (
PRIMARY KEY (film),
film INT GENERATED BY DEFAULT AS IDENTITY,
title TEXT
);
-- DELETE target remains empty
CREATE TABLE test.roles (
actor INT REFERENCES test.actors,
film INT REFERENCES test.films,
character TEXT
);
CREATE FUNCTION test.call_me (name TEXT) RETURNS TEXT
STABLE LANGUAGE SQL AS $$
SELECT 'Hello ' || name || ', how are you?';
$$;
GRANT USAGE ON SCHEMA test TO postgrest_test_anonymous;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA test TO postgrest_test_anonymous;
+3
View File
@@ -0,0 +1,3 @@
{
"last_modified": "now"
}
+3
View File
@@ -0,0 +1,3 @@
{
"title": "Workers Leaving The Lumière Factory In Lyon"
}
+5
View File
@@ -0,0 +1,5 @@
{
"actor": 1,
"name": "John Doe",
"last_modified": "now"
}
+3
View File
@@ -0,0 +1,3 @@
{
"name": "John"
}
+30
View File
@@ -0,0 +1,30 @@
GET http://postgrest/
Prefer: tx=commit
HEAD http://postgrest/actors?actor=eq.1
Prefer: tx=commit
GET http://postgrest/actors?select=*,roles(*,films(*))
Prefer: tx=commit
POST http://postgrest/films?columns=title
Prefer: tx=rollback
@post.json
PUT http://postgrest/actors?actor=eq.1&columns=name
Prefer: tx=rollback
@put.json
PATCH http://postgrest/actors?actor=eq.1
Prefer: tx=rollback
@patch.json
DELETE http://postgrest/roles
Prefer: tx=rollback
GET http://postgrest/rpc/call_me?name=John
POST http://postgrest/rpc/call_me
@rpc.json
OPTIONS http://postgrest/actors

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