Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6050c8615 | ||
|
|
7ffac522e3 | ||
|
|
6c4208d9e7 | ||
|
|
98bf4d861d | ||
|
|
bfcd289855 | ||
|
|
7c0fbf9b3f | ||
|
|
6fae07241f | ||
|
|
a4f687fdfd | ||
|
|
b1a101c253 | ||
|
|
10c363b588 | ||
|
|
9a52632024 | ||
|
|
f57caf0987 | ||
|
|
f02904a959 | ||
|
|
8b41b71db7 | ||
|
|
d4cf8e7abb | ||
|
|
9134171b95 | ||
|
|
328c3453f8 | ||
|
|
bb27eb57a9 | ||
|
|
dbc3aa28c4 | ||
|
|
ad92c207f2 | ||
|
|
dea6c5eb92 | ||
|
|
3baefa1d96 | ||
|
|
c0546e0e46 | ||
|
|
d5f1d1b1ad | ||
|
|
1c19bbde93 | ||
|
|
3da5a2875e | ||
|
|
2e6a094d48 | ||
|
|
178c5d54d5 | ||
|
|
a7c396e464 | ||
|
|
24db4a1e25 | ||
|
|
ae77cf9a08 | ||
|
|
4a0a37588f | ||
|
|
5838214910 | ||
|
|
b609d8491e | ||
|
|
18538707ab | ||
|
|
e59c72cff3 | ||
|
|
da573a1805 | ||
|
|
e24a7d005a | ||
|
|
79399686db | ||
|
|
f6ce93f2c8 | ||
|
|
8c35c9d711 | ||
|
|
be674eb41d | ||
|
|
052843ac9b | ||
|
|
c524531784 | ||
|
|
82fa1d8812 |
+202
-120
@@ -1,66 +1,69 @@
|
||||
version: 2
|
||||
|
||||
build-distro-bin: &build-distro-bin
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
# cannot interpolate env var and use as a cache key so just copy the Dockerfile to another filename
|
||||
- run: cp docker/distro_release/Dockerfile.$CIRCLE_JOB docker/distro_release/Dockerfile
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-{{ .Environment.CIRCLE_JOB }}-image-{{ checksum "docker/distro_release/Dockerfile" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-{{ .Environment.CIRCLE_JOB }}-dependencies-{{ checksum "postgrest.cabal" }}-{{ checksum "stack.yaml" }}
|
||||
- run:
|
||||
name: load or build docker image
|
||||
command: |
|
||||
if [[ -e ~/image.tar ]]; then
|
||||
docker load -i ~/image.tar
|
||||
else
|
||||
docker build --rm=false -t $CIRCLE_JOB -f docker/distro_release/Dockerfile.$CIRCLE_JOB docker/distro_release/
|
||||
docker save $CIRCLE_JOB > ~/image.tar
|
||||
fi
|
||||
- run:
|
||||
name: build binary
|
||||
command: |
|
||||
docker run -it \
|
||||
-v $HOME/.stack:/root/.stack \
|
||||
-v $(pwd):/source \
|
||||
-v $HOME/bin/:/root/.local/bin/ \
|
||||
$CIRCLE_JOB build --allow-different-user --install-ghc --copy-bins
|
||||
# volumes owned by root if chown is not done the save_cache step fails silently
|
||||
sudo chown -R circleci:circleci ~/.stack .stack-work
|
||||
- run:
|
||||
name: compress binary
|
||||
command: |
|
||||
mkdir -p /tmp/workspace/bin
|
||||
cd /tmp/workspace/bin
|
||||
tar cvJf postgrest-$CIRCLE_TAG-$CIRCLE_JOB.tar.xz -C ~/bin postgrest
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- bin/*
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/image.tar
|
||||
key: v1-{{ .Environment.CIRCLE_JOB }}-image-{{ checksum "docker/distro_release/Dockerfile" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- "~/.stack"
|
||||
- ".stack-work"
|
||||
key: v1-{{ .Environment.CIRCLE_JOB }}-dependencies-{{ checksum "postgrest.cabal" }}-{{ checksum "stack.yaml" }}
|
||||
|
||||
jobs:
|
||||
build-cache:
|
||||
docker:
|
||||
- image: circleci/buildpack-deps:trusty
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-stack-dependencies-{{ checksum "postgrest.cabal" }}-{{ checksum "stack.yaml" }}
|
||||
- run:
|
||||
name: install stack & dependencies
|
||||
command: |
|
||||
curl -L https://github.com/commercialhaskell/stack/releases/download/v2.1.3/stack-2.1.3-linux-x86_64.tar.gz | tar zx -C /tmp
|
||||
sudo mv /tmp/stack-2.1.3-linux-x86_64/stack /usr/bin
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgmp-dev
|
||||
sudo apt-get install -y --only-upgrade binutils
|
||||
sudo apt-get install -y postgresql-client
|
||||
stack setup
|
||||
- run:
|
||||
name: build src and tests dependencies
|
||||
command: |
|
||||
stack build --fast -j1 --only-dependencies
|
||||
stack build --fast --test --no-run-tests --only-dependencies
|
||||
- save_cache:
|
||||
paths:
|
||||
- "~/.stack"
|
||||
- ".stack-work"
|
||||
key: v1-stack-dependencies-{{ checksum "postgrest.cabal" }}-{{ checksum "stack.yaml" }}
|
||||
- run:
|
||||
name: build src and tests
|
||||
command: |
|
||||
stack build --fast -j1
|
||||
stack build --fast --test --no-run-tests
|
||||
|
||||
style:
|
||||
docker:
|
||||
- image: nixos/nix:2.3
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install linting and styling scripts
|
||||
command: nix-env -f default.nix -iA lint style
|
||||
- run:
|
||||
name: Run linter
|
||||
command: |
|
||||
echo 'Note: For checking this locally, use `make lint`. Or, if using `nix`, run `nix-shell --run postgrest-lint`'
|
||||
postgrest-lint
|
||||
- run:
|
||||
name: Run style check
|
||||
command: |
|
||||
echo 'Note: For checking this locally, use `make style`. Or, if using `nix`, run `nix-shell --run postgrest-style`'
|
||||
postgrest-style-check
|
||||
|
||||
build-test-9.4:
|
||||
docker:
|
||||
- image: circleci/buildpack-deps:trusty
|
||||
environment:
|
||||
- PGHOST=localhost
|
||||
- image: circleci/postgres:9.4.14
|
||||
- image: circleci/postgres:9.4.26
|
||||
environment:
|
||||
- POSTGRES_USER=circleci
|
||||
- POSTGRES_DB=circleci
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@@ -81,22 +84,11 @@ jobs:
|
||||
sudo apt-get install -y --only-upgrade binutils
|
||||
sudo apt-get install -y postgresql-client
|
||||
stack setup
|
||||
rm -rf $(stack path --dist-dir) $(stack path --local-install-root)
|
||||
stack install hlint stylish-haskell
|
||||
- run:
|
||||
name: Add stack tools to $PATH
|
||||
command: |
|
||||
echo "export PATH=/home/circleci/.local/bin:$PATH" >> $BASH_ENV
|
||||
- run:
|
||||
name: build src and tests dependencies
|
||||
command: |
|
||||
stack build --fast -j1 --only-dependencies
|
||||
stack build --fast --test --no-run-tests --only-dependencies
|
||||
- save_cache:
|
||||
paths:
|
||||
- "~/.stack"
|
||||
- ".stack-work"
|
||||
key: v1-stack-dependencies-{{ checksum "postgrest.cabal" }}-{{ checksum "stack.yaml" }}
|
||||
- run:
|
||||
name: build src and tests
|
||||
command: |
|
||||
@@ -107,22 +99,17 @@ jobs:
|
||||
command: |
|
||||
POSTGREST_TEST_CONNECTION=$(test/create_test_db "postgres://circleci@localhost" postgrest_test) stack test
|
||||
test/io-tests.sh
|
||||
- run:
|
||||
name: run linter
|
||||
command: make lint
|
||||
- run:
|
||||
name: run styler
|
||||
command: make style
|
||||
|
||||
build-test-9.6:
|
||||
docker:
|
||||
- image: circleci/buildpack-deps:trusty
|
||||
environment:
|
||||
- PGHOST=localhost
|
||||
- image: circleci/postgres:9.6.2
|
||||
- image: circleci/postgres:9.6.17
|
||||
environment:
|
||||
- POSTGRES_USER=circleci
|
||||
- POSTGRES_DB=circleci
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@@ -151,10 +138,11 @@ jobs:
|
||||
- image: circleci/buildpack-deps:trusty
|
||||
environment:
|
||||
- PGHOST=localhost
|
||||
- image: circleci/postgres:10.5
|
||||
- image: circleci/postgres:10.12
|
||||
environment:
|
||||
- POSTGRES_USER=circleci
|
||||
- POSTGRES_DB=circleci
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@@ -183,10 +171,44 @@ jobs:
|
||||
- image: circleci/buildpack-deps:trusty
|
||||
environment:
|
||||
- PGHOST=localhost
|
||||
- image: circleci/postgres:11.4
|
||||
- image: circleci/postgres:11.7
|
||||
environment:
|
||||
- POSTGRES_USER=circleci
|
||||
- POSTGRES_DB=circleci
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-stack-dependencies-{{ checksum "postgrest.cabal" }}-{{ checksum "stack.yaml" }}
|
||||
- run:
|
||||
name: install stack & dependencies
|
||||
command: |
|
||||
curl -L https://github.com/commercialhaskell/stack/releases/download/v2.1.3/stack-2.1.3-linux-x86_64.tar.gz | tar zx -C /tmp
|
||||
sudo mv /tmp/stack-2.1.3-linux-x86_64/stack /usr/bin
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgmp-dev
|
||||
sudo apt-get install -y postgresql-client
|
||||
stack setup
|
||||
- run:
|
||||
name: build src and tests
|
||||
command: |
|
||||
stack build --fast -j1
|
||||
stack build --fast --test --no-run-tests
|
||||
- run:
|
||||
name: run tests
|
||||
command: POSTGREST_TEST_CONNECTION=$(test/create_test_db "postgres://circleci@localhost" postgrest_test) stack test postgrest:spec
|
||||
|
||||
build-test-12:
|
||||
docker:
|
||||
- image: circleci/buildpack-deps:trusty
|
||||
environment:
|
||||
- PGHOST=localhost
|
||||
- image: circleci/postgres:12.2
|
||||
environment:
|
||||
- POSTGRES_USER=circleci
|
||||
- POSTGRES_DB=circleci
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@@ -216,10 +238,11 @@ jobs:
|
||||
environment:
|
||||
- PGHOST=localhost
|
||||
- TERM=xterm
|
||||
- image: circleci/postgres:9.6.2
|
||||
- image: circleci/postgres:9.6.17
|
||||
environment:
|
||||
- POSTGRES_USER=circleci
|
||||
- POSTGRES_DB=circleci
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@@ -251,23 +274,8 @@ jobs:
|
||||
name: run memory usage tests
|
||||
command: |
|
||||
test/create_test_db "postgres://circleci@localhost" postgrest_test
|
||||
psql "postgres:///postgrest_test" -f test/fixtures/database.sql
|
||||
psql "postgres:///postgrest_test" -f test/fixtures/roles.sql
|
||||
psql "postgres:///postgrest_test" -f test/fixtures/schema.sql
|
||||
psql "postgres:///postgrest_test" -f test/fixtures/jwt.sql
|
||||
psql "postgres:///postgrest_test" -f test/fixtures/jsonschema.sql
|
||||
psql "postgres:///postgrest_test" -f test/fixtures/privileges.sql
|
||||
test/memory-tests.sh
|
||||
|
||||
centos7:
|
||||
<<: *build-distro-bin
|
||||
|
||||
ubuntu:
|
||||
<<: *build-distro-bin
|
||||
|
||||
ubuntui386:
|
||||
<<: *build-distro-bin
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/golang:1.9
|
||||
@@ -294,60 +302,132 @@ jobs:
|
||||
docker tag postgrest postgrest/postgrest:latest
|
||||
docker push postgrest/postgrest:latest
|
||||
|
||||
build-nix:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install Nix
|
||||
command: |
|
||||
curl -L https://nixos.org/nix/install | sh
|
||||
echo "source $HOME/.nix-profile/etc/profile.d/nix.sh" >> $BASH_ENV
|
||||
- run:
|
||||
name: Install and use the Cachix binary cache
|
||||
command: |
|
||||
nix-env -iA cachix -f https://cachix.org/api/v1/install
|
||||
cachix use postgrest
|
||||
- run:
|
||||
name: Build the dynamic PostgREST executable
|
||||
command: |
|
||||
# We build the dynamic postgrest executable first in order to fail fast if anything
|
||||
# should be wrong with the source code.
|
||||
nix-build -A postgrest
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-cabal-nix-dependencies-{{ checksum "postgrest.cabal" }}
|
||||
- run:
|
||||
name: Run the spec tests against all supported PostgreSQL versions
|
||||
command: |
|
||||
# Our utility scripts use cabal to run the tests, which will pick up all the libraries that
|
||||
# are built with Nix. So this should only ever build the PostgREST itself and be reasonably
|
||||
# quick. We accelerate that part further by caching `~/.cabal` and `dist-newstyle`.
|
||||
nix-shell --run postgrest-test-spec-all
|
||||
- save_cache:
|
||||
paths:
|
||||
- "~/.cabal"
|
||||
- "dist-newstyle"
|
||||
key: v1-cabal-nix-dependencies-{{ checksum "postgrest.cabal" }}
|
||||
- run:
|
||||
name: Build all derivations from default.nix and push results to Cachix
|
||||
command: |
|
||||
# Only push to the cache when CircleCI makes the CACHIX_SIGNING_KEY available (e.g. not for
|
||||
# pull requests).
|
||||
if [ -n "${CACHIX_SIGNING_KEY:-""}" ]; then
|
||||
echo "Building and caching all derivations..."
|
||||
nix-build | cachix push postgrest
|
||||
else
|
||||
echo "Building all derivations (caching skipped for outside pull requests)..."
|
||||
nix-build
|
||||
fi
|
||||
|
||||
build-static-bin:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install Nix
|
||||
command: |
|
||||
curl -L https://nixos.org/nix/install | sh
|
||||
echo "source $HOME/.nix-profile/etc/profile.d/nix.sh" >> $BASH_ENV
|
||||
- run:
|
||||
name: Install and use the Cachix binary cache
|
||||
command: |
|
||||
nix-env -iA cachix -f https://cachix.org/api/v1/install
|
||||
cachix use postgrest
|
||||
- run:
|
||||
name: Build the PostgREST statically linked binary
|
||||
command: |
|
||||
nix-build -A postgrestStatic
|
||||
- run:
|
||||
name: compress binary
|
||||
command: |
|
||||
mkdir -p /tmp/workspace/bin
|
||||
cd /tmp/workspace/bin
|
||||
tar cvJf postgrest-$CIRCLE_TAG-linux-x64-static.tar.xz -C ~/project/result/bin postgrest
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- bin/*
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-test-release:
|
||||
jobs:
|
||||
- build-cache:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
- style:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
- build-test-9.4:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
requires:
|
||||
- build-cache
|
||||
- build-test-9.6:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
requires:
|
||||
- build-cache
|
||||
- build-test-10:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
requires:
|
||||
- build-cache
|
||||
- build-test-11:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
requires:
|
||||
- build-cache
|
||||
- build-test-12:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
requires:
|
||||
- build-cache
|
||||
- build-prof-test:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
- centos7:
|
||||
- build-static-bin:
|
||||
requires:
|
||||
- build-test-9.4
|
||||
- build-test-9.6
|
||||
- build-test-10
|
||||
- build-test-11
|
||||
- build-prof-test
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- ubuntu:
|
||||
requires:
|
||||
- build-test-9.4
|
||||
- build-test-9.6
|
||||
- build-test-10
|
||||
- build-test-11
|
||||
- build-prof-test
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- ubuntui386:
|
||||
requires:
|
||||
- build-test-9.4
|
||||
- build-test-9.6
|
||||
- build-test-10
|
||||
- build-test-11
|
||||
- build-nix
|
||||
- build-prof-test
|
||||
filters:
|
||||
tags:
|
||||
@@ -356,9 +436,11 @@ workflows:
|
||||
ignore: /.*/
|
||||
- release:
|
||||
requires:
|
||||
- centos7
|
||||
- ubuntu
|
||||
- ubuntui386
|
||||
- build-static-bin
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
- build-nix:
|
||||
filters:
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-1-release-amd64
|
||||
|
||||
build_task:
|
||||
env:
|
||||
ASSUME_ALWAYS_YES: YES
|
||||
install_script:
|
||||
## TODO: Some of these workaround steps might be unneeded now
|
||||
## gmake required, otherwise bsd make is used and some cryptic "No operator" errors are shown
|
||||
- pkg install -y gmake
|
||||
## gcc required, otherwise warning: macro expansion producing 'defined' has undefined behavior [-Wexpansion-to-defined]
|
||||
- pkg install -y gcc
|
||||
- pkg install -y libiconv
|
||||
- pkg install -y gmp
|
||||
- pkg install -y postgresql95-client
|
||||
- pkg install -y pkgconf
|
||||
- pkg install -y ghc
|
||||
- pkg install -y hs-cabal-install
|
||||
cabal_cache:
|
||||
folder: ~/.cabal
|
||||
fingerprint_script: cat postgrest.cabal
|
||||
build_script:
|
||||
- cabal v2-update
|
||||
- cabal v2-build
|
||||
binaries_artifacts:
|
||||
path: "dist-newstyle/build/x86_64-freebsd/ghc-8.6.5/postgrest-7.0.1/x/postgrest/build/postgrest/*"
|
||||
#binary_script:
|
||||
#- mkdir bin
|
||||
## cabal v2-install --installdir=.
|
||||
## the installdir option is not working on this cabal version(form hs-cabal-install), so the binary must be extrated from the path
|
||||
## this is too brittle so for now we don't use it
|
||||
#- ldd /tmp/cirrus-ci-build/dist-newstyle/build/x86_64-freebsd/ghc-8.6.5/postgrest-7.0.1/x/postgrest/build/postgrest/postgrest
|
||||
#- mv /tmp/cirrus-ci-build/dist-newstyle/build/x86_64-freebsd/ghc-8.6.5/postgrest-7.0.1/x/postgrest/build/postgrest/postgrest bin/
|
||||
## https://cirrus-ci.org/guide/writing-tasks/#artifacts-instruction
|
||||
#binaries_artifacts:
|
||||
#path: bin/
|
||||
cleanup_script:
|
||||
- rm -rf ~/.cabal/logs
|
||||
- rm ~/.cabal/config
|
||||
|
||||
#publish_task:
|
||||
#skip: $CIRRUS_TAG == ""
|
||||
#env:
|
||||
#TOKEN: ENCRYPTED[c99babbf04e8a33962ffbbc75bf20d3abe408f9bede5ed5d8956cd24da6fdb34266c920984cc43f8106ef3423fba0e98]
|
||||
#depends_on:
|
||||
#- build
|
||||
#script:
|
||||
## https://cirrus-ci.org/guide/writing-tasks/#artifacts-instruction
|
||||
#- wget https://api.cirrus-ci.com/v1/artifact/github/$CIRRUS_REPO_FULL_NAME/build/binaries/bin/postgrest
|
||||
#- tar cjf postgrest-$CIRRUS_TAG-freebsd.tar.xz postgrest
|
||||
### The CIRRUS_RELEASE env var is not the same as the tag. It's an id for the release.
|
||||
#- curl -X POST --data-binary @$NAME \
|
||||
#-H "Authorization:token ${TOKEN}" \
|
||||
#-H "Content-Type:application/octet-stream" \
|
||||
#"https://uploads.github.com/repos/$CIRRUS_REPO_FULL_NAME/releases/${CIRRUS_RELEASE}/assets?name=${NAME}"
|
||||
@@ -12,3 +12,6 @@ site
|
||||
*~
|
||||
*#*
|
||||
.#*
|
||||
*.swp
|
||||
result*
|
||||
dist-newstyle
|
||||
|
||||
@@ -30,6 +30,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
|
||||
- [Daniel Babiak](https://github.com/d-babiak)
|
||||
- Evans Fernandes
|
||||
- [Jan Sommer](https://github.com/nerfpops)
|
||||
|
||||
## Backers
|
||||
|
||||
@@ -43,6 +44,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
- Nathan Bouscal
|
||||
- Daniel Rafaj
|
||||
- David Fenko
|
||||
- Remo Rechkemmer
|
||||
|
||||
## Former Backers
|
||||
|
||||
|
||||
@@ -9,6 +9,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Fixed
|
||||
|
||||
## [7.0.1] - 2020-05-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- #1473, Fix overloaded computed columns on RPC - @wolfgangwalther
|
||||
- #1471, Fix POST, PATCH, DELETE with ?select= and return=minimal and PATCH with empty body - @wolfgangwalther
|
||||
- #1500, Fix missing `openapi-server-proxy-uri` config option - @steve-chavez
|
||||
- #1508, Fix `Content-Profile` not working for POST RPC - @steve-chavez
|
||||
- #1452, Fix PUT restriction for all columns - @steve-chavez
|
||||
|
||||
## [7.0.0] - 2020-04-03
|
||||
|
||||
### Added
|
||||
|
||||
@@ -7,6 +7,8 @@ check: lint style test
|
||||
clean: prompt-clean
|
||||
stack clean --full
|
||||
|
||||
# For running these you'll need to install hlint and stylish-haskell first. Run:
|
||||
# stack install hlint stylish-haskell
|
||||
lint:
|
||||
git ls-files | grep '\.l\?hs$$' | xargs stack exec -- hlint -X QuasiQuotes -X NoPatternSynonyms "$$@"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"POSTGREST_VER": {
|
||||
"description": "Version of PostgREST to deploy",
|
||||
"value": "7.0.0"
|
||||
"value": "7.0.1"
|
||||
},
|
||||
"DB_URI": {
|
||||
"description": "Database connection string, e.g. postgres://user:pass@xxxxxxx.rds.amazonaws.com/mydb",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
## AppVeyor is only used for building a Windows binary, no tests are run here.
|
||||
platform: x64
|
||||
image: Visual Studio 2015
|
||||
|
||||
cache:
|
||||
- "c:\\sr"
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
let
|
||||
name =
|
||||
"postgrest";
|
||||
|
||||
compiler =
|
||||
"ghc883";
|
||||
|
||||
# PostgREST source files, filtered based on the rules in the .gitignore files
|
||||
# and file extensions. We want to include as litte as possible, as the files
|
||||
# added here will increase the space used in the Nix store and trigger the
|
||||
# build of new Nix derivations when changed.
|
||||
src =
|
||||
pkgs.lib.sourceFilesBySuffices
|
||||
(pkgs.gitignoreSource ./.)
|
||||
[ ".cabal" ".hs" ".lhs" "LICENSE" ];
|
||||
|
||||
# Commit of the Nixpkgs repository that we want to use.
|
||||
nixpkgsVersion =
|
||||
import nix/nixpkgs-version.nix;
|
||||
|
||||
# Nix files that describe the Nixpkgs repository. We evaluate the expression
|
||||
# using `import` below.
|
||||
nixpkgs =
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
|
||||
sha256 = nixpkgsVersion.tarballHash;
|
||||
};
|
||||
|
||||
overlays =
|
||||
[
|
||||
(import nix/overlays/postgresql-legacy.nix)
|
||||
(import nix/overlays/gitignore.nix)
|
||||
(import nix/overlays/haskell-packages)
|
||||
];
|
||||
|
||||
# Evaluated expression of the Nixpkgs repository.
|
||||
pkgs =
|
||||
import nixpkgs { inherit overlays; };
|
||||
|
||||
postgresqlVersions =
|
||||
[
|
||||
pkgs.postgresql_12
|
||||
pkgs.postgresql_11
|
||||
pkgs.postgresql_10
|
||||
pkgs.postgresql_9_6
|
||||
pkgs.postgresql_9_5
|
||||
pkgs.postgresql_9_4
|
||||
];
|
||||
|
||||
patches =
|
||||
pkgs.callPackage nix/patches {};
|
||||
|
||||
# Base dynamic derivation for the PostgREST package.
|
||||
drv =
|
||||
pkgs.haskellPackages.callCabal2nix name src {};
|
||||
|
||||
# Static derivation for the PostgREST executable.
|
||||
drvStatic =
|
||||
import nix/static { inherit nixpkgs name src compiler patches; };
|
||||
|
||||
lib =
|
||||
pkgs.haskell.lib;
|
||||
in
|
||||
rec {
|
||||
inherit nixpkgs pkgs;
|
||||
|
||||
# Derivation for the PostgREST Haskell package, including the executable,
|
||||
# libraries and documentation. We disable running the test suite on Nix
|
||||
# builds, as they require a database to be set up.
|
||||
postgrestWithLib =
|
||||
lib.dontCheck drv;
|
||||
|
||||
# Derivation for just the PostgREST binary, where we strip all dynamic
|
||||
# libraries and documentation, leaving only the executable. Note that the
|
||||
# executable is static with regards to Haskell libraries, but not system
|
||||
# libraries like glibc and libpq.
|
||||
postgrest =
|
||||
lib.justStaticExecutables postgrestWithLib;
|
||||
|
||||
# Static executable.
|
||||
postgrestStatic =
|
||||
lib.justStaticExecutables (lib.dontCheck drvStatic);
|
||||
|
||||
# Docker image and loading script.
|
||||
docker =
|
||||
pkgs.callPackage nix/docker { postgrest = postgrestStatic; };
|
||||
|
||||
# Environment in which PostgREST can be built with cabal, useful e.g. for
|
||||
# defining a shell for nix-shell.
|
||||
env =
|
||||
drv.env;
|
||||
|
||||
# Utility for updating the pinned version of Nixpkgs.
|
||||
nixpkgsUpgrade =
|
||||
pkgs.callPackage nix/nixpkgs-upgrade.nix {};
|
||||
|
||||
tests =
|
||||
pkgs.callPackage nix/tests.nix
|
||||
{
|
||||
inherit postgresqlVersions;
|
||||
postgrestBuildEnv = env;
|
||||
};
|
||||
|
||||
style =
|
||||
pkgs.callPackage nix/style.nix {};
|
||||
|
||||
lint =
|
||||
pkgs.callPackage nix/lint.nix {};
|
||||
}
|
||||
+3
-3
@@ -17,8 +17,7 @@ import Data.IORef (IORef, atomicWriteIORef, newIORef,
|
||||
readIORef)
|
||||
import Data.String (IsString (..))
|
||||
import Data.Text (pack, replace, strip, stripPrefix)
|
||||
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
|
||||
import Data.Text.IO (hPutStrLn, readFile)
|
||||
import Data.Text.IO (hPutStrLn)
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import Network.Wai.Handler.Warp (defaultSettings, runSettings,
|
||||
setHost, setPort, setServerName)
|
||||
@@ -34,7 +33,8 @@ import PostgREST.OpenAPI (isMalformedProxyUri)
|
||||
import PostgREST.Types (ConnectionStatus (..), DbStructure,
|
||||
PgVersion (..), Schema,
|
||||
minimumPgVersion)
|
||||
import Protolude hiding (hPutStrLn, head, replace)
|
||||
import Protolude hiding (hPutStrLn, head, replace, toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
|
||||
#ifndef mingw32_HOST_OS
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# Nix development and build environment
|
||||
|
||||
With Nix it's possible to quickly and reliably recreate the full environments
|
||||
for developing, testing and building PostgREST.
|
||||
|
||||
## Getting started with Nix
|
||||
|
||||
You'll need to [get Nix](https://nixos.org/download.html). The installer will
|
||||
create your Nix store in the `/nix/` directory, where all build artifacts and
|
||||
their dependencies will be stored. It will also link the Nix executables like
|
||||
`nix-env`, `nix-build` and `nix-shell` into your PATH. Nix will manage all
|
||||
other PostgREST dependencies from here on out. To clean up older build
|
||||
artifacts from the `/nix/store`, you can run `nix-collect-garbage`.
|
||||
|
||||
## Building PostgREST
|
||||
|
||||
To build PostgREST from your local checkout of the repository, run:
|
||||
|
||||
```bash
|
||||
nix-build --attr postgrest
|
||||
|
||||
```
|
||||
|
||||
This will create a `result` directory that contains the PostgREST binary at
|
||||
`result/bin/postgrest`. The `--attr` parameter (or short: `-A`) tells Nix to
|
||||
build the `postgrest` attribute from the Nix expression it finds in our
|
||||
`default.nix` (see below for details). Nix will take care of getting the right
|
||||
GHC version and all the build dependencies.
|
||||
|
||||
## Developing
|
||||
|
||||
A development environment for PostgREST is available with `nix-shell`. The
|
||||
following command will put you into a new shell that has GHC and Cabal on the
|
||||
PATH:
|
||||
|
||||
```bash
|
||||
nix-shell
|
||||
|
||||
```
|
||||
|
||||
Within `nix-shell`, you can run Cabal commands as usual. You can also run
|
||||
stack with the `--nix` option, which causes stack to pick up the non-Haskell
|
||||
dependencies from the same pinned Nixpkgs version that the Nix builds use.
|
||||
|
||||
## Aside: Working with `nix-shell` and the PostgREST utility scripts
|
||||
|
||||
The PostgREST utilities available in `nix-shell` all have names that begin
|
||||
with `postgrest-`, so you can use tab completion (`postgrest-<tab>`) in
|
||||
`nix-shell` to see all that are available:
|
||||
|
||||
```
|
||||
# Note: The utilities listed here might not be up to date.
|
||||
[nix-shell:~/Projects/postgrest]$ postgrest-
|
||||
postgrest-lint postgrest-test-spec-postgresql-10.12
|
||||
postgrest-style postgrest-test-spec-postgresql-11.7
|
||||
postgrest-style-check postgrest-test-spec-postgresql-12.2
|
||||
postgrest-test-all postgrest-test-spec-postgresql-9.4.24
|
||||
postgrest-test-spec postgrest-test-spec-postgresql-9.5.21
|
||||
postgrest-test-spec-all postgrest-test-spec-postgresql-9.6.17
|
||||
|
||||
[nix-shell:~/Projects/postgrest]$
|
||||
|
||||
```
|
||||
|
||||
To run one-off commands, you can also use `nix-shell --run <command>`, which
|
||||
will lauch the Nix shell, run that one command and exit. Note that the tab
|
||||
completion will not work with `nix-shell --run`, as Nix has yet to evaluate
|
||||
our Nix expressions to see which utilities are available.
|
||||
|
||||
```
|
||||
$ nix-shell --run postgrest-style
|
||||
|
||||
# Note that you need to quote any arguments that you would like to pass to
|
||||
# the command to be run in nix-shell:
|
||||
$ nix-shell --run "postgrest-foo --bar"
|
||||
|
||||
```
|
||||
|
||||
A third option is to install utilities that you use very often locally:
|
||||
|
||||
```
|
||||
$ nix-env -f default.nix -iA style
|
||||
|
||||
# `postgrest-style` can now be run directly:
|
||||
$ postgrest-style
|
||||
|
||||
```
|
||||
|
||||
Note that this does not yet work for all utilities (e.g. `postgrest-test-spec` currently
|
||||
needs to be run within the Nix shell environment).
|
||||
|
||||
If you use `nix-shell` very often, you might like to use https://github.com/xzfc/cached-nix-shell,
|
||||
which skips evaluating all our Nix expressions if nothing changed, reducing startup time for the
|
||||
shell considerably.
|
||||
|
||||
## Testing
|
||||
|
||||
In nix-shell, you'll find utility scripts that make it very easy to run the
|
||||
Haskell test suite, including setting up all required dependencies and
|
||||
temporary test databases:
|
||||
|
||||
```bash
|
||||
# Run the tests against the most recent version of PostgreSQL:
|
||||
$ nix-shell --run postgrest-test-spec
|
||||
|
||||
# Run the tests against all supported versions of PostgreSQL:
|
||||
$ nix-shell --run postgrest-test-spec-all
|
||||
|
||||
# Run the tests against a specific version of PostgreSQL (use tab-completion in
|
||||
# nix-shell to see all available versions):
|
||||
$ nix-shell --run postgrest-test-spec-postgresql-9.5.21
|
||||
|
||||
```
|
||||
|
||||
## Linting and styling code
|
||||
|
||||
The nix-shell also contains scripts for linting and styling the PostgREST
|
||||
source code:
|
||||
|
||||
```bash
|
||||
# Linting
|
||||
$ nix-shell --run postgrest-lint
|
||||
|
||||
# Styling / auto-formatting code
|
||||
$ nix-shell --run postgrest-style
|
||||
|
||||
```
|
||||
|
||||
## Tour
|
||||
|
||||
The following is not required for working on PostgREST with Nix, but it will
|
||||
give you some more background and details on how it works.
|
||||
|
||||
### `default.nix`
|
||||
|
||||
[`default.nix`](../default.nix) is our 'respository expression' that pulls all
|
||||
the pieces that we define with Nix together. It returns a set (like a dict in
|
||||
other programming languages), where each attribute is a derivation that Nix
|
||||
knows how to build, like the `postgrest` attribute from earlier.
|
||||
|
||||
Internally, our `default.nix` uses the `pkgs.callPackage` function to import
|
||||
the modules that we defined in the `nix` directory. It automatically passes the
|
||||
arguments those modules require if they are available in `pkgs` (this means
|
||||
that `pkgs` is defined in terms of itself, better not to think too much about
|
||||
that).
|
||||
|
||||
We also use `default.nix` to load our pinned version of the `nixpkgs`
|
||||
repository. This set of packages will always be the same, independently from
|
||||
where or when you use it. The pinned version can be upgraded with the small
|
||||
`nixpkgs-upgrade` utility. Running `nixpkgs-upgrade > nix/nixpkgs-version.nix`
|
||||
in `nix-shell` will upgrade the pinned version to the latest `nixpkgs-unstable`
|
||||
version.
|
||||
|
||||
### `shell.nix`
|
||||
|
||||
[`shell.nix`](../shell.nix) defines an environment in which PostgREST can be
|
||||
built and developed. It extends the build enviroment from our `postgrest`
|
||||
attribute with useful utilities that will be put on the PATH in `nix-shell`.
|
||||
|
||||
### `nix/overlays`
|
||||
|
||||
Our overlays to the Nix package set are defined here. They allow us to tweak our
|
||||
`pkgs` in `default.nix` by adding new packages or overriding existing ones.
|
||||
@@ -0,0 +1,63 @@
|
||||
{ postgrest, dockerTools, writeShellScriptBin }:
|
||||
let
|
||||
image =
|
||||
tag:
|
||||
dockerTools.buildImage {
|
||||
inherit tag;
|
||||
|
||||
name = "postgrest/postgrest";
|
||||
contents = postgrest;
|
||||
|
||||
# Set the current time as the image creation date. This makes the build
|
||||
# non-reproducible, but that should not be an issue for us.
|
||||
created = "now";
|
||||
|
||||
extraCommands =
|
||||
''
|
||||
mkdir etc
|
||||
cp ${./postgrest.conf} etc/postgrest.conf
|
||||
'';
|
||||
|
||||
config = {
|
||||
Cmd = [ "/bin/postgrest" "/etc/postgrest.conf" ];
|
||||
Env = [
|
||||
"PGRST_DB_URI=postgresql://?user=postgres"
|
||||
"PGRST_DB_SCHEMA=public"
|
||||
"PGRST_DB_ANON_ROLE="
|
||||
"PGRST_DB_POOL=100"
|
||||
"PGRST_DB_EXTRA_SEARCH_PATH=public"
|
||||
"PGRST_SERVER_HOST=*4"
|
||||
"PGRST_SERVER_PORT=3000"
|
||||
"PGRST_OPENAPI_SERVER_PROXY_URI="
|
||||
"PGRST_JWT_SECRET="
|
||||
"PGRST_SECRET_IS_BASE64=false"
|
||||
"PGRST_JWT_AUD="
|
||||
"PGRST_MAX_ROWS="
|
||||
"PGRST_PRE_REQUEST="
|
||||
"PGRST_ROLE_CLAIM_KEY=.role"
|
||||
"PGRST_ROOT_SPEC="
|
||||
"PGRST_RAW_MEDIA_TYPES="
|
||||
];
|
||||
User = "1000";
|
||||
ExposedPorts = {
|
||||
"3000/tcp" = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
rec {
|
||||
imageLatest =
|
||||
image "latest";
|
||||
|
||||
imageWithVersion =
|
||||
image "v${postgrest.version}";
|
||||
|
||||
load =
|
||||
writeShellScriptBin "postgrest-docker-load"
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
docker load -i ${imageLatest}
|
||||
docker load -i ${imageWithVersion}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
db-uri = "$(PGRST_DB_URI)"
|
||||
db-schema = "$(PGRST_DB_SCHEMA)"
|
||||
db-anon-role = "$(PGRST_DB_ANON_ROLE)"
|
||||
db-pool = "$(PGRST_DB_POOL)"
|
||||
db-extra-search-path = "$(PGRST_DB_EXTRA_SEARCH_PATH)"
|
||||
|
||||
server-host = "$(PGRST_SERVER_HOST)"
|
||||
server-port = "$(PGRST_SERVER_PORT)"
|
||||
|
||||
openapi-server-proxy-uri = "$(PGRST_OPENAPI_SERVER_PROXY_URI)"
|
||||
jwt-secret = "$(PGRST_JWT_SECRET)"
|
||||
secret-is-base64 = "$(PGRST_SECRET_IS_BASE64)"
|
||||
jwt-aud = "$(PGRST_JWT_AUD)"
|
||||
role-claim-key = "$(PGRST_ROLE_CLAIM_KEY)"
|
||||
|
||||
max-rows = "$(PGRST_MAX_ROWS)"
|
||||
pre-request = "$(PGRST_PRE_REQUEST)"
|
||||
root-spec = "$(PGRST_ROOT_SPEC)"
|
||||
raw-media-types = "$(PGRST_RAW_MEDIA_TYPES)"
|
||||
@@ -0,0 +1,12 @@
|
||||
{ writeShellScriptBin, git, silver-searcher, hlint }:
|
||||
|
||||
writeShellScriptBin "postgrest-lint"
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
rootdir="$(${git}/bin/git rev-parse --show-toplevel)"
|
||||
|
||||
# Lint Haskell files
|
||||
${silver-searcher}/bin/ag -l -g '\.l?hs$' "$rootdir" \
|
||||
| xargs ${hlint}/bin/hlint -X QuasiQuotes -X NoPatternSynonyms
|
||||
''
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
{ curl
|
||||
, jq
|
||||
, writeShellScriptBin
|
||||
, nix
|
||||
}:
|
||||
# Utility script for pinning the latest unstable version of Nixpkgs.
|
||||
|
||||
# Instead of pinning Nixpkgs based on the huge Git repository, we reference a
|
||||
# specific tarball that only contains the source of the revision that we want
|
||||
# to pin.
|
||||
let
|
||||
name =
|
||||
"nixpkgs-upgrade";
|
||||
|
||||
refUrl =
|
||||
https://api.github.com/repos/nixos/nixpkgs/git/ref/heads/nixpkgs-unstable;
|
||||
|
||||
githubV3Header =
|
||||
"Accept: application/vnd.github.v3+json";
|
||||
|
||||
tarballUrlBase =
|
||||
https://github.com/nixos/nixpkgs/archive/;
|
||||
in
|
||||
writeShellScriptBin name
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
commitHash="$(${curl}/bin/curl "${refUrl}" -H "${githubV3Header}" | ${jq}/bin/jq -r .object.sha)"
|
||||
tarballUrl="${tarballUrlBase}$commitHash.tar.gz"
|
||||
tarballHash="$(${nix}/bin/nix-prefetch-url --unpack "$tarballUrl")"
|
||||
currentDate="$(date --iso)"
|
||||
|
||||
cat << EOF
|
||||
# Pinned version of Nixpkgs, generated with ${name}.
|
||||
{
|
||||
date = "$currentDate";
|
||||
rev = "$commitHash";
|
||||
tarballHash = "$tarballHash";
|
||||
}
|
||||
EOF
|
||||
''
|
||||
@@ -0,0 +1,6 @@
|
||||
# Pinned version of Nixpkgs, generated with nixpkgs-upgrade.
|
||||
{
|
||||
date = "2020-04-19";
|
||||
rev = "10100a97c8964e82b30f180fda41ade8e6f69e41";
|
||||
tarballHash = "011f36kr3c1ria7rag7px26bh73d1b0xpqadd149bysf4hg17rln";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
self: super:
|
||||
# Overlay that adds the `gitignoreSource` function from Hercules-CI.
|
||||
# This function is useful for filtering which files are added to the Nix store.
|
||||
# See: https://github.com/hercules-ci/gitignore.nix
|
||||
|
||||
# To update to a newer revision, the simplest way is to add a new commit hash
|
||||
# from GitHub under `rev` and to then add the hash that Nix suggests on first
|
||||
# use.
|
||||
{
|
||||
gitignoreSource =
|
||||
let
|
||||
gitignoreSrc = super.fetchFromGitHub {
|
||||
owner = "hercules-ci";
|
||||
repo = "gitignore";
|
||||
rev = "2ced4519f865341adcb143c5d668f955a2cb997f";
|
||||
sha256 = "sha256:0fc5bgv9syfcblp23y05kkfnpgh3gssz6vn24frs8dzw39algk2z";
|
||||
};
|
||||
in (super.callPackage gitignoreSrc {}).gitignoreSource;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
The `.nix` files in this directory pin specific versions of Haskell packages
|
||||
from Hackage. They were generated with `cabal2nix`, e.g.:
|
||||
|
||||
```bash
|
||||
cabal2nix cabal://protolude > protolude.nix
|
||||
|
||||
```
|
||||
|
||||
Those overrides might become obsolete as the pinned versions are adopted into
|
||||
our pinned version of nixpkgs.
|
||||
@@ -0,0 +1,11 @@
|
||||
self: super:
|
||||
|
||||
{
|
||||
haskellPackages =
|
||||
super.haskellPackages.override {
|
||||
overrides =
|
||||
final: prev: rec {
|
||||
protolude = prev.callPackage ./protolude.nix {};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{ mkDerivation
|
||||
, array
|
||||
, async
|
||||
, base
|
||||
, bytestring
|
||||
, containers
|
||||
, deepseq
|
||||
, ghc-prim
|
||||
, hashable
|
||||
, mtl
|
||||
, mtl-compat
|
||||
, stdenv
|
||||
, stm
|
||||
, text
|
||||
, transformers
|
||||
, transformers-compat
|
||||
}:
|
||||
mkDerivation {
|
||||
pname = "protolude";
|
||||
version = "0.3.0";
|
||||
sha256 = "4083385a9e03fab9201f63ce198b9ced3fbc1c50d6d42574db5e36c757bedcac";
|
||||
isLibrary = true;
|
||||
isExecutable = true;
|
||||
libraryHaskellDepends = [
|
||||
array
|
||||
async
|
||||
base
|
||||
bytestring
|
||||
containers
|
||||
deepseq
|
||||
ghc-prim
|
||||
hashable
|
||||
mtl
|
||||
mtl-compat
|
||||
stm
|
||||
text
|
||||
transformers
|
||||
transformers-compat
|
||||
];
|
||||
homepage = "https://github.com/sdiehl/protolude";
|
||||
description = "A small prelude";
|
||||
license = stdenv.lib.licenses.mit;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
self: super:
|
||||
# Overlay that adds legacy versions of PostgreSQL that are supported by
|
||||
# PostgREST.
|
||||
{
|
||||
# PostgreSQL 9.4 was removed from Nixpkgs with
|
||||
# https://github.com/NixOS/nixpkgs/commit/8e2fc57a80d761c46702c3250e61c1bffe021e25
|
||||
# We pin its parent commit 3b5b9a7 to get the last version that was available.
|
||||
postgresql_9_4 =
|
||||
let
|
||||
rev = "3b5b9a73f59ff93d50156e250203410bdd07f4e0";
|
||||
tarballHash = "0kr25xqbv1ldp72jbmml21pc5hl7xcfqhclv5qxa5f860jddjznk";
|
||||
|
||||
pinnedPkgs =
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
|
||||
sha256 = tarballHash;
|
||||
};
|
||||
in
|
||||
(import pinnedPkgs {}).pkgs.postgresql_9_4;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{ runCommand }:
|
||||
|
||||
{
|
||||
# Apply patches to a directory.
|
||||
applyPatches =
|
||||
name: path: patches:
|
||||
runCommand name { inherit patches; }
|
||||
''
|
||||
set -eou pipefail
|
||||
|
||||
cp -r ${path} $out
|
||||
chmod -R u+w $out
|
||||
|
||||
for patch in $patches; do
|
||||
echo "Applying patch $patch"
|
||||
patch -d "$out" -p1 < "$patch"
|
||||
done
|
||||
'';
|
||||
|
||||
static-haskell-nix-postgrest-libpq =
|
||||
./static-haskell-nix-postgrest-libpq.patch;
|
||||
|
||||
nixpkgs-revert-ghc-bootstrap =
|
||||
./nixpkgs-revert-ghc-bootstrap.patch;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
From 93cc144b215654ca9cb0d2bcb58bb8922895ccab Mon Sep 17 00:00:00 2001
|
||||
From: Remo <remo>
|
||||
Date: Sat, 25 Apr 2020 17:31:55 +0200
|
||||
Subject: [PATCH] Revert "ghc: 8.6.3-binary -> 8.6.5-binary"
|
||||
|
||||
This reverts commit 3c7ef6bcd85e3e133ac6f2ab5e238934d56d902e.
|
||||
---
|
||||
.../{8.6.5-binary.nix => 8.6.3-binary.nix} | 13 ++++++------
|
||||
pkgs/top-level/haskell-packages.nix | 20 +++++++++----------
|
||||
2 files changed, 16 insertions(+), 17 deletions(-)
|
||||
rename pkgs/development/compilers/ghc/{8.6.5-binary.nix => 8.6.3-binary.nix} (95%)
|
||||
|
||||
diff --git a/pkgs/development/compilers/ghc/8.6.5-binary.nix b/pkgs/development/compilers/ghc/8.6.3-binary.nix
|
||||
similarity index 95%
|
||||
rename from pkgs/development/compilers/ghc/8.6.5-binary.nix
|
||||
rename to pkgs/development/compilers/ghc/8.6.3-binary.nix
|
||||
index 97793d912895..9e0fd12b96cf 100644
|
||||
--- a/pkgs/development/compilers/ghc/8.6.5-binary.nix
|
||||
+++ b/pkgs/development/compilers/ghc/8.6.3-binary.nix
|
||||
@@ -27,19 +27,18 @@ let
|
||||
in
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
- version = "8.6.5";
|
||||
+ version = "8.6.3";
|
||||
|
||||
name = "ghc-${version}-binary";
|
||||
|
||||
- # https://downloads.haskell.org/~ghc/8.6.5/
|
||||
src = fetchurl ({
|
||||
i686-linux = {
|
||||
- url = "http://haskell.org/ghc/dist/${version}/ghc-${version}-i386-deb9-linux.tar.xz";
|
||||
- sha256 = "1p2h29qghql19ajk755xa0yxkn85slbds8m9n5196ris743vkp8w";
|
||||
+ url = "http://haskell.org/ghc/dist/${version}/ghc-${version}-i386-deb8-linux.tar.xz";
|
||||
+ sha256 = "0bw8a7fxcbskf93rb4m542ff66vrmx5i5kj77qx37cbhijx70w5m";
|
||||
};
|
||||
x86_64-linux = {
|
||||
- url = "http://haskell.org/ghc/dist/${version}/ghc-${version}-x86_64-deb9-linux.tar.xz";
|
||||
- sha256 = "1pqlx6rdjs2110g0y1i9f8x18lmdizibjqd15f5xahcz39hgaxdw";
|
||||
+ url = "http://haskell.org/ghc/dist/${version}/ghc-${version}-x86_64-deb8-linux.tar.xz";
|
||||
+ sha256 = "1m9gaga2pzi2cx5gvasg0rx1dlvr68gmi20l67652kag6xjsa719";
|
||||
};
|
||||
aarch64-linux = {
|
||||
url = "http://haskell.org/ghc/dist/${version}/ghc-${version}-aarch64-ubuntu18.04-linux.tar.xz";
|
||||
@@ -47,7 +46,7 @@ stdenv.mkDerivation rec {
|
||||
};
|
||||
x86_64-darwin = {
|
||||
url = "http://haskell.org/ghc/dist/${version}/ghc-${version}-x86_64-apple-darwin.tar.xz";
|
||||
- sha256 = "0s9188vhhgf23q3rjarwhbr524z6h2qga5xaaa2pma03sfqvvhfz";
|
||||
+ sha256 = "1hbzk57v45176kxcx848p5jn5p1xbp2129ramkbzsk6plyhnkl3r";
|
||||
};
|
||||
}.${stdenv.hostPlatform.system}
|
||||
or (throw "cannot bootstrap GHC on this platform"));
|
||||
diff --git a/pkgs/top-level/haskell-packages.nix b/pkgs/top-level/haskell-packages.nix
|
||||
index aa7ef02a3886..712a98e147e0 100644
|
||||
--- a/pkgs/top-level/haskell-packages.nix
|
||||
+++ b/pkgs/top-level/haskell-packages.nix
|
||||
@@ -4,7 +4,7 @@ let
|
||||
# These are attributes in compiler and packages that don't support integer-simple.
|
||||
integerSimpleExcludes = [
|
||||
"ghc822Binary"
|
||||
- "ghc865Binary"
|
||||
+ "ghc863Binary"
|
||||
"ghc844"
|
||||
"ghcjs"
|
||||
"ghcjs86"
|
||||
@@ -42,7 +42,7 @@ in {
|
||||
|
||||
ghc822Binary = callPackage ../development/compilers/ghc/8.2.2-binary.nix { };
|
||||
|
||||
- ghc865Binary = callPackage ../development/compilers/ghc/8.6.5-binary.nix { };
|
||||
+ ghc863Binary = callPackage ../development/compilers/ghc/8.6.3-binary.nix { };
|
||||
|
||||
ghc844 = callPackage ../development/compilers/ghc/8.4.4.nix {
|
||||
bootPkgs = packages.ghc822Binary;
|
||||
@@ -57,31 +57,31 @@ in {
|
||||
llvmPackages = pkgs.llvmPackages_6;
|
||||
};
|
||||
ghc881 = callPackage ../development/compilers/ghc/8.8.1.nix {
|
||||
- bootPkgs = packages.ghc865Binary;
|
||||
+ bootPkgs = packages.ghc863Binary;
|
||||
inherit (buildPackages.python3Packages) sphinx;
|
||||
buildLlvmPackages = buildPackages.llvmPackages_7;
|
||||
llvmPackages = pkgs.llvmPackages_7;
|
||||
};
|
||||
ghc882 = callPackage ../development/compilers/ghc/8.8.2.nix {
|
||||
- bootPkgs = packages.ghc865Binary;
|
||||
+ bootPkgs = packages.ghc863Binary;
|
||||
inherit (buildPackages.python3Packages) sphinx;
|
||||
buildLlvmPackages = buildPackages.llvmPackages_7;
|
||||
llvmPackages = pkgs.llvmPackages_7;
|
||||
};
|
||||
ghc883 = callPackage ../development/compilers/ghc/8.8.3.nix {
|
||||
- bootPkgs = packages.ghc865Binary;
|
||||
+ bootPkgs = packages.ghc863Binary;
|
||||
inherit (buildPackages.python3Packages) sphinx;
|
||||
buildLlvmPackages = buildPackages.llvmPackages_7;
|
||||
llvmPackages = pkgs.llvmPackages_7;
|
||||
};
|
||||
ghc8101 = callPackage ../development/compilers/ghc/8.10.1.nix {
|
||||
- bootPkgs = packages.ghc865Binary;
|
||||
+ bootPkgs = packages.ghc863Binary;
|
||||
inherit (buildPackages.python3Packages) sphinx;
|
||||
buildLlvmPackages = buildPackages.llvmPackages_9;
|
||||
llvmPackages = pkgs.llvmPackages_9;
|
||||
};
|
||||
ghcHEAD = callPackage ../development/compilers/ghc/head.nix {
|
||||
- bootPkgs = packages.ghc883; # no binary yet
|
||||
+ bootPkgs = packages.ghc863Binary;
|
||||
inherit (buildPackages.python3Packages) sphinx;
|
||||
buildLlvmPackages = buildPackages.llvmPackages_9;
|
||||
llvmPackages = pkgs.llvmPackages_9;
|
||||
@@ -118,9 +118,9 @@ in {
|
||||
compilerConfig = callPackage ../development/haskell-modules/configuration-ghc-8.2.x.nix { };
|
||||
packageSetConfig = bootstrapPackageSet;
|
||||
};
|
||||
- ghc865Binary = callPackage ../development/haskell-modules {
|
||||
- buildHaskellPackages = bh.packages.ghc865Binary;
|
||||
- ghc = bh.compiler.ghc865Binary;
|
||||
+ ghc863Binary = callPackage ../development/haskell-modules {
|
||||
+ buildHaskellPackages = bh.packages.ghc863Binary;
|
||||
+ ghc = bh.compiler.ghc863Binary;
|
||||
compilerConfig = callPackage ../development/haskell-modules/configuration-ghc-8.6.x.nix { };
|
||||
packageSetConfig = bootstrapPackageSet;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
From bb4c1e27e391eff01591fe60830ff68a9ada41ef Mon Sep 17 00:00:00 2001
|
||||
From: Remo <remo>
|
||||
Date: Wed, 22 Apr 2020 11:58:07 +0200
|
||||
Subject: [PATCH] Add postgrest libpq linking fix
|
||||
|
||||
---
|
||||
survey/default.nix | 5 +++++
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
diff --git a/survey/default.nix b/survey/default.nix
|
||||
index b28606c..fd7cde7 100644
|
||||
--- a/survey/default.nix
|
||||
+++ b/survey/default.nix
|
||||
@@ -1052,6 +1052,11 @@ let
|
||||
super.squeal-postgresql
|
||||
[ final.openssl ]
|
||||
"--libs openssl";
|
||||
+ postgrest =
|
||||
+ addStaticLinkerFlagsWithPkgconfig
|
||||
+ super.postgrest
|
||||
+ [ final.openssl ]
|
||||
+ "--libs openssl";
|
||||
|
||||
xml-to-json =
|
||||
addStaticLinkerFlagsWithPkgconfig
|
||||
@@ -0,0 +1,90 @@
|
||||
{ mkDerivation
|
||||
, array
|
||||
, base
|
||||
, base-compat
|
||||
, base-orphans
|
||||
, binary
|
||||
, bytestring
|
||||
, containers
|
||||
, deepseq
|
||||
, Diff
|
||||
, directory
|
||||
, filepath
|
||||
, integer-logarithms
|
||||
, mtl
|
||||
, optparse-applicative
|
||||
, parsec
|
||||
, pretty
|
||||
, process
|
||||
, QuickCheck
|
||||
, stdenv
|
||||
, stm
|
||||
, tagged
|
||||
, tar
|
||||
, tasty
|
||||
, tasty-golden
|
||||
, tasty-hunit
|
||||
, tasty-quickcheck
|
||||
, temporary
|
||||
, text
|
||||
, time
|
||||
, transformers
|
||||
, tree-diff
|
||||
, unix
|
||||
}:
|
||||
mkDerivation {
|
||||
pname = "Cabal";
|
||||
version = "3.0.0.0";
|
||||
sha256 = "5143ec26d740c1a508c93a8860e64407e7546c29b9817db20ff1595c1968d287";
|
||||
setupHaskellDepends = [ mtl parsec ];
|
||||
libraryHaskellDepends = [
|
||||
array
|
||||
base
|
||||
binary
|
||||
bytestring
|
||||
containers
|
||||
deepseq
|
||||
directory
|
||||
filepath
|
||||
mtl
|
||||
parsec
|
||||
pretty
|
||||
process
|
||||
text
|
||||
time
|
||||
transformers
|
||||
unix
|
||||
];
|
||||
testHaskellDepends = [
|
||||
array
|
||||
base
|
||||
base-compat
|
||||
base-orphans
|
||||
binary
|
||||
bytestring
|
||||
containers
|
||||
deepseq
|
||||
Diff
|
||||
directory
|
||||
filepath
|
||||
integer-logarithms
|
||||
optparse-applicative
|
||||
pretty
|
||||
process
|
||||
QuickCheck
|
||||
stm
|
||||
tagged
|
||||
tar
|
||||
tasty
|
||||
tasty-golden
|
||||
tasty-hunit
|
||||
tasty-quickcheck
|
||||
temporary
|
||||
text
|
||||
tree-diff
|
||||
];
|
||||
doCheck = false;
|
||||
homepage = "http://www.haskell.org/cabal/";
|
||||
description = "A framework for packaging Haskell software";
|
||||
license = stdenv.lib.licenses.bsd3;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# Turn a Haskell source package (given its name and source) into a derivation
|
||||
# for a fully static executable.
|
||||
{ nixpkgs, compiler, name, src, patches }:
|
||||
let
|
||||
# The nh2/static-haskell-nix project does all the hard work for us for
|
||||
# building static Haskell executables. We apply a patch for PostgREST below
|
||||
# until the respective pull request is merged. See:
|
||||
# https://github.com/nh2/static-haskell-nix/pull/91
|
||||
# statix-haskell-nix builds everything based on Musl instead of glibc, so
|
||||
# there will be a _lot_ to rebuild if you don't use a binary cache.
|
||||
static-haskell-nix =
|
||||
let
|
||||
rev = "749707fc90b781c3e653e67917a7d571fe82ae7b";
|
||||
in
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/nh2/static-haskell-nix/archive/${rev}.tar.gz";
|
||||
sha256 = "155spda2lww378bhx68w6dxwqd5y6s9kin3qbgl2m23r3vmk3m3w";
|
||||
};
|
||||
|
||||
patched-static-haskell-nix =
|
||||
patches.applyPatches "patched-static-haskell-nix" static-haskell-nix
|
||||
[
|
||||
patches.static-haskell-nix-postgrest-libpq
|
||||
];
|
||||
|
||||
haskellPackagesOverrides =
|
||||
lib: final: prev:
|
||||
{
|
||||
# Add our source package.
|
||||
"${name}" = prev.callCabal2nix name src {};
|
||||
|
||||
# cabal2nix depends on Cabal 3.0.*, while our pinned version of Nixpkgs
|
||||
# only provides 2.4 or 3.2. So we pinned 3.0.0.0 in ./Cabal.nix
|
||||
cabal2nix =
|
||||
prev.cabal2nix.overrideScope
|
||||
(self: super: { Cabal = self.callPackage ./Cabal.nix {}; });
|
||||
|
||||
# Pinned version of protolude.
|
||||
protolude =
|
||||
prev.callPackage ../overlays/haskell-packages/protolude.nix {};
|
||||
|
||||
# The tests for the packages below took a long time on static
|
||||
# builds, so we disable them for now - to be investigated.
|
||||
happy = lib.dontCheck prev.happy;
|
||||
};
|
||||
|
||||
# This overlay adds our source package and applies adjustments to the
|
||||
# derivation of other packages that it depends on. The overlay applies to
|
||||
# Haskell packages for the given compiler, which we will later use
|
||||
# with the static-haskell-nix survey.
|
||||
overlay =
|
||||
self: super:
|
||||
# Override the set of Haskell packages at
|
||||
# pkgs.haskell.packages."${compiler}".
|
||||
{
|
||||
haskell = super.haskell // {
|
||||
packages = super.haskell.packages // {
|
||||
"${compiler}" =
|
||||
super.haskell.packages."${compiler}".override
|
||||
{ overrides = haskellPackagesOverrides self.haskell.lib; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
patchedNixpkgs =
|
||||
patches.applyPatches "patched-nixpkgs" nixpkgs
|
||||
[
|
||||
patches.nixpkgs-revert-ghc-bootstrap
|
||||
];
|
||||
|
||||
# Apply our overlay to the given pkgs.
|
||||
normalPkgs =
|
||||
(import patchedNixpkgs {}).appendOverlays [ overlay ];
|
||||
|
||||
# Each version of GHC needs a specific version of Cabal.
|
||||
defaultCabalPackageVersionComingWithGhc =
|
||||
{
|
||||
ghc883 = "Cabal_3_2_0_0";
|
||||
}."${compiler}";
|
||||
|
||||
# Let the static-haskell-nix project do the hard work of deriving a set of
|
||||
# fully static Haskell executables, including one for the our source package
|
||||
# that we added through the overlay.
|
||||
survey =
|
||||
import "${patched-static-haskell-nix}/survey"
|
||||
{ inherit normalPkgs compiler defaultCabalPackageVersionComingWithGhc; };
|
||||
in
|
||||
# Return the fully static derivation of our source package.
|
||||
survey.haskellPackages."${name}"
|
||||
@@ -0,0 +1,37 @@
|
||||
{ writeShellScriptBin
|
||||
, git
|
||||
, nixpkgs-fmt
|
||||
, silver-searcher
|
||||
, stylish-haskell
|
||||
, buildEnv
|
||||
}:
|
||||
let
|
||||
style =
|
||||
writeShellScriptBin "postgrest-style"
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
rootdir="$(${git}/bin/git rev-parse --show-toplevel)"
|
||||
|
||||
# Format Nix files
|
||||
${nixpkgs-fmt}/bin/nixpkgs-fmt "$rootdir" > /dev/null 2> /dev/null
|
||||
|
||||
# Format Haskell files
|
||||
${silver-searcher}/bin/ag -l -g '\.l?hs$' "$rootdir" \
|
||||
| xargs ${stylish-haskell}/bin/stylish-haskell -i
|
||||
'';
|
||||
|
||||
check =
|
||||
writeShellScriptBin "postgrest-style-check"
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
${style}/bin/${style.name}
|
||||
|
||||
${git}/bin/git diff-index --exit-code HEAD -- '*.hs' '*.lhs' '*.nix'
|
||||
'';
|
||||
in
|
||||
buildEnv {
|
||||
name = "postgrest-style";
|
||||
paths = [ style check ];
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# Utilities for running the PostgREST test suite
|
||||
|
||||
{ buildEnv
|
||||
, cabal-install
|
||||
, git
|
||||
, lib
|
||||
, postgrestBuildEnv
|
||||
, postgresqlVersions
|
||||
, runtimeShell
|
||||
, writeShellScript
|
||||
, writeShellScriptBin
|
||||
}:
|
||||
let
|
||||
# Wrap the `test/with_tmp_db` script with the required dependencies from Nix.
|
||||
withTmpDb =
|
||||
postgresql:
|
||||
writeShellScript "postgrest-test-${postgresql.name}"
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
export PATH=${postgresql}/bin:${git}/bin:${runtimeShell}/bin:"$PATH"
|
||||
|
||||
exec ${../test/with_tmp_db} "$@"
|
||||
'';
|
||||
|
||||
# Script to run the Haskell test suite against a specific version of
|
||||
# PostgreSQL.
|
||||
testSpec =
|
||||
name: postgresql:
|
||||
writeShellScriptBin name
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
cat << EOF
|
||||
|
||||
Running spec against ${postgresql.name}...
|
||||
|
||||
EOF
|
||||
|
||||
# TODO: Make this work outside nix-shell when installed with nix-env.
|
||||
# Probably using postgrestBuildEnv somehow?
|
||||
${withTmpDb postgresql} ${cabal-install}/bin/cabal v2-test \
|
||||
--test-show-detail=direct
|
||||
|
||||
cat << EOF
|
||||
|
||||
Done running spec against ${postgresql.name}.
|
||||
|
||||
EOF
|
||||
'';
|
||||
|
||||
# The PostgreSQL version that we run the tests against by default.
|
||||
defaultPostgresql =
|
||||
builtins.head postgresqlVersions;
|
||||
|
||||
defaultTestSpec =
|
||||
testSpec "postgrest-test-spec" defaultPostgresql;
|
||||
|
||||
# Create a `testSpec` for each PostgreSQL version that we want to test
|
||||
# against.
|
||||
testSpecVersions =
|
||||
map
|
||||
(postgresql: testSpec "postgrest-test-spec-${postgresql.name}" postgresql)
|
||||
postgresqlVersions;
|
||||
|
||||
# Helper script for running the tests against all PostgreSQL versions.
|
||||
testSpecAllVersions =
|
||||
let
|
||||
testRunners =
|
||||
map (test: "${test}/bin/${test.name}") testSpecVersions;
|
||||
in
|
||||
writeShellScriptBin "postgrest-test-spec-all"
|
||||
''
|
||||
set -euo pipefail
|
||||
|
||||
${lib.concatStringsSep "\n" testRunners}
|
||||
'';
|
||||
in
|
||||
# Create an environment that contains all the utility scripts for running tests
|
||||
# that we defined above.
|
||||
buildEnv {
|
||||
name =
|
||||
"postgrest-tests";
|
||||
|
||||
paths =
|
||||
[
|
||||
defaultTestSpec
|
||||
testSpecAllVersions
|
||||
] ++ testSpecVersions;
|
||||
}
|
||||
+34
-25
@@ -1,5 +1,5 @@
|
||||
name: postgrest
|
||||
version: 7.0.0
|
||||
version: 7.0.1
|
||||
synopsis: REST API for any Postgres database
|
||||
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
||||
for the tables and views, supporting all HTTP verbs that security
|
||||
@@ -43,12 +43,12 @@ library
|
||||
PostgREST.Private.Common
|
||||
PostgREST.Private.QueryFragment
|
||||
hs-source-dirs: src
|
||||
build-depends: base >= 4.9 && < 4.14
|
||||
build-depends: base >= 4.9 && < 4.15
|
||||
, HTTP >= 4000.3.7 && < 4000.4
|
||||
, Ranged-sets >= 0.3 && < 0.5
|
||||
, aeson >= 0.11.3 && < 1.5
|
||||
, aeson >= 1.4.7 && < 1.5
|
||||
, ansi-wl-pprint >= 0.6.7 && < 0.7
|
||||
, base64-bytestring >= 1 && < 1.1
|
||||
, base64-bytestring >= 1 && < 1.2
|
||||
, bytestring >= 0.10.8 && < 0.11
|
||||
, case-insensitive >= 1.2 && < 1.3
|
||||
, cassava >= 0.4.5 && < 0.6
|
||||
@@ -67,17 +67,17 @@ library
|
||||
, insert-ordered-containers >= 0.2.2 && < 0.3
|
||||
, interpolatedstring-perl6 >= 1 && < 1.1
|
||||
, jose >= 0.8.1 && < 0.9
|
||||
, lens >= 4.14 && < 4.19
|
||||
, lens >= 4.14 && < 4.20
|
||||
, lens-aeson >= 1.0.1 && < 1.2
|
||||
, network-uri >= 2.6.1 && < 2.7
|
||||
, network-uri >= 2.6.1 && < 2.8
|
||||
, optparse-applicative >= 0.13 && < 0.16
|
||||
, parsec >= 3.1.11 && < 3.2
|
||||
, protolude >= 0.2.2 && < 0.3
|
||||
, protolude >= 0.3 && < 0.4
|
||||
, regex-tdfa >= 1.2.2 && < 1.4
|
||||
, scientific >= 0.3.4 && < 0.4
|
||||
, swagger2 >= 2.4 && < 2.6
|
||||
, swagger2 >= 2.4 && < 2.7
|
||||
, text >= 1.2.2 && < 1.3
|
||||
, time >= 1.6 && < 1.10
|
||||
, time >= 1.6 && < 1.11
|
||||
, unordered-containers >= 0.2.8 && < 0.3
|
||||
, vector >= 0.11 && < 0.13
|
||||
, wai >= 3.2.1 && < 3.3
|
||||
@@ -88,13 +88,20 @@ library
|
||||
default-extensions: OverloadedStrings
|
||||
QuasiQuotes
|
||||
NoImplicitPrelude
|
||||
ghc-options: -O2 -Werror -Wall -fwarn-identities
|
||||
-fno-spec-constr -optP-Wno-nonportable-include-path
|
||||
-- -fno-spec-constr may help keep compile time memory use in check,
|
||||
-- see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304
|
||||
-- -optP-Wno-nonportable-include-path
|
||||
-- prevents build failures on case-insensitive filesystems (macos),
|
||||
-- see https://github.com/commercialhaskell/stack/issues/3918
|
||||
|
||||
executable postgrest
|
||||
main-is: Main.hs
|
||||
hs-source-dirs: main
|
||||
build-depends: base >= 4.9 && < 4.14
|
||||
build-depends: base >= 4.9 && < 4.15
|
||||
, auto-update >= 0.1.4 && < 0.2
|
||||
, base64-bytestring >= 1 && < 1.1
|
||||
, base64-bytestring >= 1 && < 1.2
|
||||
, bytestring >= 0.10.8 && < 0.11
|
||||
, directory >= 1.2.6 && < 1.4
|
||||
, either >= 4.4.1 && < 5.1
|
||||
@@ -103,10 +110,10 @@ executable postgrest
|
||||
, hasql-transaction >= 0.7.2 && < 1.1
|
||||
, network < 3.2
|
||||
, postgrest
|
||||
, protolude >= 0.2.2 && < 0.3
|
||||
, protolude >= 0.3 && < 0.4
|
||||
, retry >= 0.7.4 && < 0.9
|
||||
, text >= 1.2.2 && < 1.3
|
||||
, time >= 1.6 && < 1.10
|
||||
, time >= 1.6 && < 1.11
|
||||
, wai >= 3.2.1 && < 3.3
|
||||
, warp >= 3.2.12 && < 3.4
|
||||
default-language: Haskell2010
|
||||
@@ -114,6 +121,8 @@ executable postgrest
|
||||
QuasiQuotes
|
||||
NoImplicitPrelude
|
||||
ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2"
|
||||
-O2 -Werror -Wall -fwarn-identities
|
||||
-fno-spec-constr -optP-Wno-nonportable-include-path
|
||||
|
||||
if !os(windows)
|
||||
build-depends: unix
|
||||
@@ -154,12 +163,12 @@ test-suite spec
|
||||
SpecHelper
|
||||
TestTypes
|
||||
hs-source-dirs: test
|
||||
build-depends: base >= 4.9 && < 4.14
|
||||
, aeson >= 0.11.3 && < 1.5
|
||||
build-depends: base >= 4.9 && < 4.15
|
||||
, aeson >= 1.4.7 && < 1.5
|
||||
, aeson-qq >= 0.8.1 && < 0.9
|
||||
, async >= 2.1.1 && < 2.3
|
||||
, auto-update >= 0.1.4 && < 0.2
|
||||
, base64-bytestring >= 1 && < 1.1
|
||||
, base64-bytestring >= 1 && < 1.2
|
||||
, bytestring >= 0.10.8 && < 0.11
|
||||
, case-insensitive >= 1.2 && < 1.3
|
||||
, cassava >= 0.4.5 && < 0.6
|
||||
@@ -173,15 +182,15 @@ test-suite spec
|
||||
, hspec-wai >= 0.10 && < 0.11
|
||||
, hspec-wai-json >= 0.10 && < 0.11
|
||||
, http-types >= 0.12.3 && < 0.13
|
||||
, lens >= 4.14 && < 4.19
|
||||
, lens >= 4.14 && < 4.20
|
||||
, lens-aeson >= 1.0.1 && < 1.2
|
||||
, monad-control >= 1.0.1 && < 1.1
|
||||
, postgrest
|
||||
, process >= 1.4.2 && < 1.7
|
||||
, protolude >= 0.2.2 && < 0.3
|
||||
, protolude >= 0.3 && < 0.4
|
||||
, regex-tdfa >= 1.2.2 && < 1.4
|
||||
, text >= 1.2.2 && < 1.3
|
||||
, time >= 1.6 && < 1.10
|
||||
, time >= 1.6 && < 1.11
|
||||
, transformers-base >= 0.4.4 && < 0.5
|
||||
, wai >= 3.2.1 && < 3.3
|
||||
, wai-extra >= 3.0.19 && < 3.1
|
||||
@@ -198,12 +207,12 @@ Test-Suite spec-querycost
|
||||
Hs-Source-Dirs: test
|
||||
Main-Is: QueryCost.hs
|
||||
Other-Modules: SpecHelper
|
||||
Build-Depends: base >= 4.9 && < 4.14
|
||||
, aeson >= 0.11.3 && < 1.5
|
||||
Build-Depends: base >= 4.9 && < 4.15
|
||||
, aeson >= 1.4.7 && < 1.5
|
||||
, aeson-qq >= 0.8.1 && < 0.9
|
||||
, async >= 2.1.1 && < 2.3
|
||||
, auto-update >= 0.1.4 && < 0.2
|
||||
, base64-bytestring >= 1 && < 1.1
|
||||
, base64-bytestring >= 1 && < 1.2
|
||||
, bytestring >= 0.10.8 && < 0.11
|
||||
, case-insensitive >= 1.2 && < 1.3
|
||||
, cassava >= 0.4.5 && < 0.6
|
||||
@@ -217,15 +226,15 @@ Test-Suite spec-querycost
|
||||
, hspec-wai >= 0.10 && < 0.11
|
||||
, hspec-wai-json >= 0.10 && < 0.11
|
||||
, http-types >= 0.12.3 && < 0.13
|
||||
, lens >= 4.14 && < 4.19
|
||||
, lens >= 4.14 && < 4.20
|
||||
, lens-aeson >= 1.0.1 && < 1.2
|
||||
, monad-control >= 1.0.1 && < 1.1
|
||||
, postgrest
|
||||
, process >= 1.4.2 && < 1.7
|
||||
, protolude >= 0.2.2 && < 0.3
|
||||
, protolude >= 0.3 && < 0.4
|
||||
, regex-tdfa >= 1.2.2 && < 1.4
|
||||
, text >= 1.2.2 && < 1.3
|
||||
, time >= 1.6 && < 1.10
|
||||
, time >= 1.6 && < 1.11
|
||||
, transformers-base >= 0.4.4 && < 0.5
|
||||
, wai >= 3.2.1 && < 3.3
|
||||
, wai-extra >= 3.0.19 && < 3.1
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
with (import ./default.nix);
|
||||
pkgs.lib.overrideDerivation env (
|
||||
base: {
|
||||
buildInputs =
|
||||
base.buildInputs ++ [
|
||||
pkgs.cabal-install
|
||||
pkgs.stack
|
||||
pkgs.cabal2nix
|
||||
pkgs.postgresql
|
||||
nixpkgsUpgrade
|
||||
tests
|
||||
style
|
||||
lint
|
||||
];
|
||||
|
||||
shellHook =
|
||||
''
|
||||
# Set our pinned version of Nixpkgs in the NIX_PATH so that
|
||||
# `stack --nix` also uses that version.
|
||||
NIX_PATH="nixpkgs=${nixpkgs}"
|
||||
'';
|
||||
}
|
||||
)
|
||||
+11
-11
@@ -28,8 +28,8 @@ import qualified Data.Vector as V
|
||||
|
||||
import Control.Arrow ((***))
|
||||
import Data.Aeson.Types (emptyArray, emptyObject)
|
||||
import Data.List (elem, last, lookup, partition)
|
||||
import Data.List.NonEmpty (NonEmpty, head)
|
||||
import Data.List (last, lookup, partition)
|
||||
import Data.List.NonEmpty (head)
|
||||
import Data.Maybe (fromJust)
|
||||
import Data.Ranged.Ranges (Range (..), emptyRange,
|
||||
rangeIntersection)
|
||||
@@ -49,7 +49,8 @@ import PostgREST.RangeQuery (NonnegRange, allRange, rangeGeq,
|
||||
rangeLimit, rangeOffset, rangeRequested,
|
||||
restrictRange)
|
||||
import PostgREST.Types
|
||||
import Protolude hiding (head)
|
||||
import Protolude hiding (head, toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
type RequestBody = BL.ByteString
|
||||
|
||||
@@ -190,11 +191,10 @@ userApiRequest confSchemas rootSpec req reqBody
|
||||
(CTOther "application/x-www-form-urlencoded", _) ->
|
||||
let json = M.fromList . map (toS *** JSON.String . toS) . parseSimpleQuery $ toS reqBody
|
||||
keys = S.fromList $ M.keys json in
|
||||
Right $ ProcessedJSON (JSON.encode json) PJObject keys
|
||||
Right $ ProcessedJSON (JSON.encode json) keys
|
||||
(ct, _) ->
|
||||
Left $ toS $ "Content-Type not acceptable: " <> toMime ct
|
||||
rpcPrmsToJson = ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> rpcQParams)
|
||||
PJObject (S.fromList $ fst <$> rpcQParams)
|
||||
rpcPrmsToJson = ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> rpcQParams) (S.fromList $ fst <$> rpcQParams)
|
||||
topLevelRange = fromMaybe allRange $ M.lookup "limit" ranges -- if no limit is specified, get all the request rows
|
||||
action =
|
||||
case method of
|
||||
@@ -219,9 +219,9 @@ userApiRequest confSchemas rootSpec req reqBody
|
||||
profile
|
||||
| length confSchemas <= 1 -- only enable content negotiation by profile when there are multiple schemas specified in the config
|
||||
= Nothing
|
||||
| action `elem` [ActionCreate, ActionUpdate, ActionSingleUpsert, ActionDelete] -- POST/PATCH/PUT/DELETE don't use the same header as per the spec
|
||||
| action `elem` [ActionCreate, ActionUpdate, ActionSingleUpsert, ActionDelete, ActionInvoke InvPost] -- POST/PATCH/PUT/DELETE don't use the same header as per the spec
|
||||
= Just $ maybe defaultSchema toS $ lookupHeader "Content-Profile"
|
||||
| action `elem` [ActionRead True, ActionRead False, ActionInvoke InvGet, ActionInvoke InvHead, ActionInvoke InvPost,
|
||||
| action `elem` [ActionRead True, ActionRead False, ActionInvoke InvGet, ActionInvoke InvHead,
|
||||
ActionInspect False, ActionInspect True, ActionInfo]
|
||||
= Just $ maybe defaultSchema toS $ lookupHeader "Accept-Profile"
|
||||
| otherwise = Nothing
|
||||
@@ -333,14 +333,14 @@ payloadAttributes raw json =
|
||||
JSON.Object x -> S.fromList (M.keys x) == canonicalKeys
|
||||
_ -> False) arr in
|
||||
if areKeysUniform
|
||||
then Just $ ProcessedJSON raw (PJArray $ V.length arr) canonicalKeys
|
||||
then Just $ ProcessedJSON raw canonicalKeys
|
||||
else Nothing
|
||||
Just _ -> Nothing
|
||||
Nothing -> Just emptyPJArray
|
||||
|
||||
JSON.Object o -> Just $ ProcessedJSON raw PJObject (S.fromList $ M.keys o)
|
||||
JSON.Object o -> Just $ ProcessedJSON raw (S.fromList $ M.keys o)
|
||||
|
||||
-- truncate everything else to an empty array.
|
||||
_ -> Just emptyPJArray
|
||||
where
|
||||
emptyPJArray = ProcessedJSON (JSON.encode emptyArray) (PJArray 0) S.empty
|
||||
emptyPJArray = ProcessedJSON (JSON.encode emptyArray) S.empty
|
||||
|
||||
+16
-20
@@ -40,13 +40,13 @@ import Network.HTTP.Types.Status
|
||||
import Network.Wai
|
||||
|
||||
import PostgREST.ApiRequest (Action (..), ApiRequest (..),
|
||||
ContentType (..),
|
||||
InvokeMethod (..), Target (..),
|
||||
mutuallyAgreeable, userApiRequest)
|
||||
import PostgREST.Auth (containsRole, jwtClaims,
|
||||
parseSecret)
|
||||
import PostgREST.Config (AppConfig (..))
|
||||
import PostgREST.DbRequestBuilder (mutateRequest, readRequest)
|
||||
import PostgREST.DbRequestBuilder (mutateRequest, readRequest,
|
||||
returningCols)
|
||||
import PostgREST.DbStructure
|
||||
import PostgREST.Error (PgError (..), SimpleError (..),
|
||||
errorResponseFor, singularityError)
|
||||
@@ -64,7 +64,8 @@ import PostgREST.Statements (callProcStatement,
|
||||
createReadStatement,
|
||||
createWriteStatement)
|
||||
import PostgREST.Types
|
||||
import Protolude hiding (Proxy, intercalate)
|
||||
import Protolude hiding (Proxy, intercalate, toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
postgrest :: AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
||||
postgrest conf refDbStructure pool getTime worker =
|
||||
@@ -127,7 +128,7 @@ app dbStructure proc cols conf apiRequest =
|
||||
(ActionRead headersOnly, TargetIdent (QualifiedIdentifier tSchema tName), Nothing) ->
|
||||
case readSqlParts tSchema tName of
|
||||
Left errorResponse -> return errorResponse
|
||||
Right (q, cq, bField) -> do
|
||||
Right (q, cq, bField, _) -> do
|
||||
let cQuery = if estimatedCount
|
||||
then limitedQuery cq ((+ 1) <$> maxRows) -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
||||
else cq
|
||||
@@ -180,7 +181,7 @@ app dbStructure proc cols conf apiRequest =
|
||||
, Just $ contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing
|
||||
, if null pkCols && isNothing (iOnConflict apiRequest)
|
||||
then Nothing
|
||||
else (\x -> ("Preference-Applied", show x)) <$> iPreferResolution apiRequest
|
||||
else (\x -> ("Preference-Applied", encodeUtf8 (show x))) <$> iPreferResolution apiRequest
|
||||
] ++ ctHeaders)) (unwrapGucHeader <$> ghdrs)
|
||||
if contentType == CTSingularJSON && queryTotal /= 1
|
||||
then do
|
||||
@@ -218,22 +219,14 @@ app dbStructure proc cols conf apiRequest =
|
||||
else
|
||||
return $ responseLBS status headers rBody
|
||||
|
||||
(ActionSingleUpsert, TargetIdent (QualifiedIdentifier tSchema tName), Just ProcessedJSON{pjRaw, pjType, pjKeys}) ->
|
||||
(ActionSingleUpsert, TargetIdent (QualifiedIdentifier tSchema tName), Just pJson) ->
|
||||
case mutateSqlParts tSchema tName of
|
||||
Left errorResponse -> return errorResponse
|
||||
Right (sq, mq) -> do
|
||||
let isSingle = case pjType of
|
||||
PJArray len -> len == 1
|
||||
PJObject -> True
|
||||
colNames = colName <$> tableCols dbStructure tSchema tName
|
||||
Right (sq, mq) ->
|
||||
if topLevelRange /= allRange
|
||||
then return . errorResponseFor $ PutRangeNotAllowedError
|
||||
else if not isSingle
|
||||
then return . errorResponseFor $ PutSingletonError
|
||||
else if S.fromList colNames /= pjKeys
|
||||
then return . errorResponseFor $ PutPayloadIncompleteError
|
||||
else do
|
||||
row <- H.statement (toS pjRaw) $
|
||||
row <- H.statement (toS $ pjRaw pJson) $
|
||||
createWriteStatement sq mq (contentType == CTSingularJSON) False
|
||||
(contentType == CTTextCSV) (iPreferRepresentation apiRequest) [] pgVer
|
||||
let (_, queryTotal, _, body, gucHeaders) = row
|
||||
@@ -293,10 +286,10 @@ app dbStructure proc cols conf apiRequest =
|
||||
let tName = fromMaybe pName $ procTableName =<< proc in
|
||||
case readSqlParts tSchema tName of
|
||||
Left errorResponse -> return errorResponse
|
||||
Right (q, cq, bField) -> do
|
||||
Right (q, cq, bField, returning) -> do
|
||||
let
|
||||
preferParams = iPreferParameters apiRequest
|
||||
pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams
|
||||
pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams returning
|
||||
stm = callProcStatement returnsScalar pq q cq shouldCount (contentType == CTSingularJSON)
|
||||
(contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects)
|
||||
bField pgVer
|
||||
@@ -351,11 +344,14 @@ app dbStructure proc cols conf apiRequest =
|
||||
readSqlParts s t =
|
||||
let
|
||||
readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest
|
||||
returnings :: ReadRequest -> Either Response [FieldName]
|
||||
returnings rr = Right (returningCols rr)
|
||||
in
|
||||
(,,) <$>
|
||||
(,,,) <$>
|
||||
(readRequestToQuery <$> readReq) <*>
|
||||
(readRequestToCountQuery <$> readReq) <*>
|
||||
(binaryField contentType rawContentTypes returnsScalar =<< readReq)
|
||||
(binaryField contentType rawContentTypes returnsScalar =<< readReq) <*>
|
||||
(returnings =<< readReq)
|
||||
|
||||
mutateSqlParts s t =
|
||||
let
|
||||
|
||||
@@ -31,7 +31,8 @@ import Control.Lens.Operators
|
||||
import Crypto.JWT
|
||||
|
||||
import PostgREST.Types
|
||||
import Protolude
|
||||
import Protolude hiding (toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
{-|
|
||||
Possible situations encountered with client JWTs
|
||||
|
||||
@@ -32,17 +32,15 @@ import qualified Data.CaseInsensitive as CI
|
||||
import qualified Data.Configurator as C
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as L
|
||||
|
||||
import Control.Exception (Handler (..))
|
||||
import Control.Lens (preview)
|
||||
import Control.Monad (fail)
|
||||
import Crypto.JWT (StringOrURI, stringOrUri)
|
||||
import Data.List (lookup)
|
||||
import Data.List.NonEmpty (NonEmpty, fromList)
|
||||
import Data.List.NonEmpty (fromList)
|
||||
import Data.Scientific (floatingOrInteger)
|
||||
import Data.Text (dropEnd, dropWhileEnd,
|
||||
intercalate, lines, splitOn,
|
||||
strip, take, unpack)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
intercalate, splitOn, strip, take,
|
||||
unpack)
|
||||
import Data.Text.IO (hPutStrLn)
|
||||
import Data.Version (versionBranch)
|
||||
import Development.GitRev (gitHash)
|
||||
@@ -63,8 +61,8 @@ import PostgREST.Error (ApiRequestError (..))
|
||||
import PostgREST.Parsers (pRoleClaimKey)
|
||||
import PostgREST.Types (JSPath, JSPathExp (..))
|
||||
import Protolude hiding (concat, hPutStrLn, intercalate, null,
|
||||
take, (<>))
|
||||
|
||||
take, toS, (<>))
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
|
||||
-- | Config file settings for the server
|
||||
@@ -155,7 +153,7 @@ readOptions = do
|
||||
AppConfig
|
||||
<$> reqString "db-uri"
|
||||
<*> reqString "db-anon-role"
|
||||
<*> optString "server-proxy-uri"
|
||||
<*> optString "openapi-server-proxy-uri"
|
||||
<*> (fromList . splitOnCommas <$> reqValue "db-schema")
|
||||
<*> (fromMaybe "!4" <$> optString "server-host")
|
||||
<*> (fromMaybe 3000 <$> optInt "server-port")
|
||||
|
||||
@@ -14,6 +14,7 @@ A query tree is built in case of resource embedding. By inferring the relationsh
|
||||
module PostgREST.DbRequestBuilder (
|
||||
readRequest
|
||||
, mutateRequest
|
||||
, returningCols
|
||||
) where
|
||||
|
||||
import qualified Data.HashMap.Strict as M
|
||||
@@ -40,7 +41,7 @@ readRequest :: Schema -> TableName -> Maybe Integer -> [Relation] -> ApiRequest
|
||||
readRequest schema rootTableName maxRows allRels apiRequest =
|
||||
mapLeft errorResponseFor $
|
||||
treeRestrictRange maxRows =<<
|
||||
augumentRequestWithJoin schema rootRels =<<
|
||||
augmentRequestWithJoin schema rootRels =<<
|
||||
addFiltersOrdersRanges apiRequest <*>
|
||||
(initReadRequest rootName <$> pRequestSelect sel)
|
||||
where
|
||||
@@ -89,8 +90,8 @@ treeRestrictRange maxRows request = pure $ nodeRestrictRange maxRows <$> request
|
||||
nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode
|
||||
nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i)
|
||||
|
||||
augumentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||
augumentRequestWithJoin schema allRels request =
|
||||
augmentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||
augmentRequestWithJoin schema allRels request =
|
||||
addRels schema allRels Nothing request
|
||||
>>= addJoinConditions Nothing
|
||||
|
||||
|
||||
+113
-170
@@ -35,14 +35,13 @@ import Data.Set as S (fromList)
|
||||
import Data.Text (breakOn, dropAround, split,
|
||||
splitOn, strip)
|
||||
import GHC.Exts (groupWith)
|
||||
import Protolude hiding (toS)
|
||||
import Protolude.Conv (toS)
|
||||
import Protolude.Unsafe (unsafeHead)
|
||||
import Text.InterpolatedString.Perl6 (q, qc)
|
||||
import Unsafe (unsafeHead)
|
||||
|
||||
import Control.Applicative
|
||||
|
||||
import PostgREST.Private.Common
|
||||
import PostgREST.Types
|
||||
import Protolude
|
||||
|
||||
getDbStructure :: [Schema] -> PgVersion -> HT.Transaction DbStructure
|
||||
getDbStructure schemas pgVer = do
|
||||
@@ -190,14 +189,14 @@ accessibleProcs = H.Statement (toS sql) (param HE.text) decodeProcs True
|
||||
procsSqlQuery :: SqlQuery
|
||||
procsSqlQuery = [q|
|
||||
SELECT
|
||||
pn.nspname as "proc_schema",
|
||||
p.proname as "proc_name",
|
||||
d.description as "proc_description",
|
||||
pg_get_function_arguments(p.oid) as "args",
|
||||
tn.nspname as "rettype_schema",
|
||||
coalesce(comp.relname, t.typname) as "rettype_name",
|
||||
p.proretset as "rettype_is_setof",
|
||||
t.typtype as "rettype_typ",
|
||||
pn.nspname as proc_schema,
|
||||
p.proname as proc_name,
|
||||
d.description as proc_description,
|
||||
pg_get_function_arguments(p.oid) as args,
|
||||
tn.nspname as rettype_schema,
|
||||
coalesce(comp.relname, t.typname) as rettype_name,
|
||||
p.proretset as rettype_is_setof,
|
||||
t.typtype as rettype_typ,
|
||||
p.provolatile
|
||||
FROM pg_proc p
|
||||
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
||||
@@ -229,11 +228,22 @@ accessibleTables =
|
||||
n.nspname as table_schema,
|
||||
relname as table_name,
|
||||
d.description as table_description,
|
||||
c.relkind = 'r' or (c.relkind IN ('v', 'f')) and (pg_relation_is_updatable(c.oid::regclass, false) & 8) = 8
|
||||
or (exists (
|
||||
select 1
|
||||
from pg_trigger
|
||||
where pg_trigger.tgrelid = c.oid and (pg_trigger.tgtype::integer & 69) = 69)
|
||||
(
|
||||
c.relkind in ('r', 'v', 'f')
|
||||
and (pg_relation_is_updatable(c.oid::regclass, false) & 8) = 8
|
||||
-- The function `pg_relation_is_updateable` returns a bitmask where 8
|
||||
-- corresponds to `1 << CMD_INSERT` in the PostgreSQL source code, i.e.
|
||||
-- it's possible to insert into the relation.
|
||||
or (exists (
|
||||
select 1
|
||||
from pg_trigger
|
||||
where
|
||||
pg_trigger.tgrelid = c.oid
|
||||
and (pg_trigger.tgtype::integer & 69) = 69)
|
||||
-- The trigger type `tgtype` is a bitmask where 69 corresponds to
|
||||
-- TRIGGER_TYPE_ROW + TRIGGER_TYPE_INSTEAD + TRIGGER_TYPE_INSERT
|
||||
-- in the PostgreSQL source code.
|
||||
)
|
||||
) as insertable
|
||||
from
|
||||
pg_class c
|
||||
@@ -243,9 +253,9 @@ accessibleTables =
|
||||
c.relkind in ('v', 'r', 'm', 'f')
|
||||
and n.nspname = $1
|
||||
and (
|
||||
pg_has_role(c.relowner, 'USAGE'::text)
|
||||
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text)
|
||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text)
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
|
||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
order by relname |]
|
||||
|
||||
@@ -374,13 +384,17 @@ allTables =
|
||||
n.nspname AS table_schema,
|
||||
c.relname AS table_name,
|
||||
NULL AS table_description,
|
||||
c.relkind = 'r' OR (c.relkind IN ('v','f'))
|
||||
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
|
||||
OR (EXISTS
|
||||
( SELECT 1
|
||||
(
|
||||
c.relkind IN ('r', 'v','f')
|
||||
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_trigger
|
||||
WHERE pg_trigger.tgrelid = c.oid
|
||||
AND (pg_trigger.tgtype::integer & 69) = 69) ) AS insertable
|
||||
WHERE
|
||||
pg_trigger.tgrelid = c.oid
|
||||
AND (pg_trigger.tgtype::integer & 69) = 69
|
||||
)
|
||||
) AS insertable
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind IN ('v','r','m','f')
|
||||
@@ -407,9 +421,7 @@ allColumns tabs =
|
||||
info.column_default AS default_value,
|
||||
array_to_string(enum_info.vals, ',') AS enum
|
||||
FROM (
|
||||
/*
|
||||
-- CTE based on pg_catalog to get PRIMARY/FOREIGN key and UNIQUE columns outside api schema
|
||||
*/
|
||||
WITH key_columns AS (
|
||||
SELECT
|
||||
r.oid AS r_oid,
|
||||
@@ -437,19 +449,16 @@ allColumns tabs =
|
||||
-- limit columns to the ones in the api schema or PK/FK columns
|
||||
*/
|
||||
columns AS (
|
||||
SELECT current_database()::information_schema.sql_identifier AS table_catalog,
|
||||
nc.nspname::information_schema.sql_identifier AS table_schema,
|
||||
c.relname::information_schema.sql_identifier AS table_name,
|
||||
a.attname::information_schema.sql_identifier AS column_name,
|
||||
SELECT
|
||||
nc.nspname::name AS table_schema,
|
||||
c.relname::name AS table_name,
|
||||
a.attname::name AS column_name,
|
||||
d.description AS description,
|
||||
a.attnum::information_schema.cardinal_number AS ordinal_position,
|
||||
pg_get_expr(ad.adbin, ad.adrelid)::information_schema.character_data AS column_default,
|
||||
a.attnum::integer AS ordinal_position,
|
||||
pg_get_expr(ad.adbin, ad.adrelid)::text AS column_default,
|
||||
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
|
||||
CASE
|
||||
WHEN a.attnotnull OR t.typtype = 'd'::"char" AND t.typnotnull THEN 'NO'::text
|
||||
ELSE 'YES'::text
|
||||
END::information_schema.yes_or_no AS is_nullable,
|
||||
CASE
|
||||
WHEN t.typtype = 'd'::"char" THEN
|
||||
WHEN t.typtype = 'd' THEN
|
||||
CASE
|
||||
WHEN bt.typelem <> 0::oid AND bt.typlen = (-1) THEN 'ARRAY'::text
|
||||
WHEN nbt.nspname = 'pg_catalog'::name THEN format_type(t.typbasetype, NULL::integer)
|
||||
@@ -461,77 +470,42 @@ allColumns tabs =
|
||||
WHEN nt.nspname = 'pg_catalog'::name THEN format_type(a.atttypid, NULL::integer)
|
||||
ELSE format_type(a.atttypid, a.atttypmod)
|
||||
END
|
||||
END::information_schema.character_data AS data_type,
|
||||
information_schema._pg_char_max_length(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS character_maximum_length,
|
||||
information_schema._pg_char_octet_length(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS character_octet_length,
|
||||
information_schema._pg_numeric_precision(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_precision,
|
||||
information_schema._pg_numeric_precision_radix(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_precision_radix,
|
||||
information_schema._pg_numeric_scale(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_scale,
|
||||
information_schema._pg_datetime_precision(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS datetime_precision,
|
||||
information_schema._pg_interval_type(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.character_data AS interval_type,
|
||||
NULL::integer::information_schema.cardinal_number AS interval_precision,
|
||||
NULL::character varying::information_schema.sql_identifier AS character_set_catalog,
|
||||
NULL::character varying::information_schema.sql_identifier AS character_set_schema,
|
||||
NULL::character varying::information_schema.sql_identifier AS character_set_name,
|
||||
CASE
|
||||
WHEN nco.nspname IS NOT NULL THEN current_database()
|
||||
ELSE NULL::name
|
||||
END::information_schema.sql_identifier AS collation_catalog,
|
||||
nco.nspname::information_schema.sql_identifier AS collation_schema,
|
||||
co.collname::information_schema.sql_identifier AS collation_name,
|
||||
CASE
|
||||
WHEN t.typtype = 'd'::"char" THEN current_database()
|
||||
ELSE NULL::name
|
||||
END::information_schema.sql_identifier AS domain_catalog,
|
||||
CASE
|
||||
WHEN t.typtype = 'd'::"char" THEN nt.nspname
|
||||
ELSE NULL::name
|
||||
END::information_schema.sql_identifier AS domain_schema,
|
||||
CASE
|
||||
WHEN t.typtype = 'd'::"char" THEN t.typname
|
||||
ELSE NULL::name
|
||||
END::information_schema.sql_identifier AS domain_name,
|
||||
current_database()::information_schema.sql_identifier AS udt_catalog,
|
||||
COALESCE(nbt.nspname, nt.nspname)::information_schema.sql_identifier AS udt_schema,
|
||||
COALESCE(bt.typname, t.typname)::information_schema.sql_identifier AS udt_name,
|
||||
NULL::character varying::information_schema.sql_identifier AS scope_catalog,
|
||||
NULL::character varying::information_schema.sql_identifier AS scope_schema,
|
||||
NULL::character varying::information_schema.sql_identifier AS scope_name,
|
||||
NULL::integer::information_schema.cardinal_number AS maximum_cardinality,
|
||||
a.attnum::information_schema.sql_identifier AS dtd_identifier,
|
||||
'NO'::character varying::information_schema.yes_or_no AS is_self_referencing,
|
||||
'NO'::character varying::information_schema.yes_or_no AS is_identity,
|
||||
NULL::character varying::information_schema.character_data AS identity_generation,
|
||||
NULL::character varying::information_schema.character_data AS identity_start,
|
||||
NULL::character varying::information_schema.character_data AS identity_increment,
|
||||
NULL::character varying::information_schema.character_data AS identity_maximum,
|
||||
NULL::character varying::information_schema.character_data AS identity_minimum,
|
||||
NULL::character varying::information_schema.yes_or_no AS identity_cycle,
|
||||
'NEVER'::character varying::information_schema.character_data AS is_generated,
|
||||
NULL::character varying::information_schema.character_data AS generation_expression,
|
||||
CASE
|
||||
WHEN c.relkind = 'r'::"char" OR (c.relkind = ANY (ARRAY['v'::"char", 'f'::"char"])) AND pg_column_is_updatable(c.oid::regclass, a.attnum, false) THEN 'YES'::text
|
||||
ELSE 'NO'::text
|
||||
END::information_schema.yes_or_no AS is_updatable
|
||||
END::text AS data_type,
|
||||
information_schema._pg_char_max_length(
|
||||
information_schema._pg_truetypid(a.*, t.*),
|
||||
information_schema._pg_truetypmod(a.*, t.*)
|
||||
)::integer AS character_maximum_length,
|
||||
information_schema._pg_numeric_precision(
|
||||
information_schema._pg_truetypid(a.*, t.*),
|
||||
information_schema._pg_truetypmod(a.*, t.*)
|
||||
)::integer AS numeric_precision,
|
||||
COALESCE(bt.typname, t.typname)::name AS udt_name,
|
||||
(
|
||||
c.relkind in ('r', 'v', 'f')
|
||||
AND pg_column_is_updatable(c.oid::regclass, a.attnum, false)
|
||||
)::bool is_updatable
|
||||
FROM pg_attribute a
|
||||
LEFT JOIN key_columns kc ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid
|
||||
LEFT JOIN pg_catalog.pg_description AS d ON d.objoid = a.attrelid and d.objsubid = a.attnum
|
||||
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
||||
JOIN (pg_class c
|
||||
JOIN pg_namespace nc ON c.relnamespace = nc.oid) ON a.attrelid = c.oid
|
||||
JOIN (pg_type t
|
||||
JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON a.atttypid = t.oid
|
||||
LEFT JOIN (pg_type bt
|
||||
JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) ON t.typtype = 'd'::"char" AND t.typbasetype = bt.oid
|
||||
LEFT JOIN (pg_collation co
|
||||
JOIN pg_namespace nco ON co.collnamespace = nco.oid) ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
|
||||
LEFT JOIN key_columns kc
|
||||
ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid
|
||||
LEFT JOIN pg_catalog.pg_description AS d
|
||||
ON d.objoid = a.attrelid and d.objsubid = a.attnum
|
||||
LEFT JOIN pg_attrdef ad
|
||||
ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
||||
JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid)
|
||||
ON a.attrelid = c.oid
|
||||
JOIN (pg_type t JOIN pg_namespace nt ON t.typnamespace = nt.oid)
|
||||
ON a.atttypid = t.oid
|
||||
LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid)
|
||||
ON t.typtype = 'd' AND t.typbasetype = bt.oid
|
||||
LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
|
||||
ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
|
||||
WHERE
|
||||
NOT pg_is_other_temp_schema(nc.oid)
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char", 'm'::"char"]))
|
||||
AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) /*--filter only columns that are FK/PK or in the api schema */
|
||||
/*--AND (pg_has_role(c.relowner, 'USAGE'::text) OR has_column_privilege(c.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/
|
||||
AND c.relkind in ('r', 'v', 'f', 'm')
|
||||
-- Filter only columns that are FK/PK or in the api schema:
|
||||
AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL)
|
||||
)
|
||||
SELECT
|
||||
table_schema,
|
||||
@@ -546,7 +520,6 @@ allColumns tabs =
|
||||
numeric_precision,
|
||||
column_default,
|
||||
udt_name
|
||||
/*-- FROM information_schema.columns*/
|
||||
FROM columns
|
||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||
) AS info
|
||||
@@ -618,69 +591,36 @@ allPrimaryKeys tabs =
|
||||
H.Statement sql HE.noParams (decodePks tabs) True
|
||||
where
|
||||
sql = [q|
|
||||
/*
|
||||
-- CTE to replace information_schema.table_constraints to remove owner limit
|
||||
*/
|
||||
WITH tc AS (
|
||||
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
||||
nc.nspname::information_schema.sql_identifier AS constraint_schema,
|
||||
c.conname::information_schema.sql_identifier AS constraint_name,
|
||||
current_database()::information_schema.sql_identifier AS table_catalog,
|
||||
nr.nspname::information_schema.sql_identifier AS table_schema,
|
||||
r.relname::information_schema.sql_identifier AS table_name,
|
||||
CASE c.contype
|
||||
WHEN 'c'::"char" THEN 'CHECK'::text
|
||||
WHEN 'f'::"char" THEN 'FOREIGN KEY'::text
|
||||
WHEN 'p'::"char" THEN 'PRIMARY KEY'::text
|
||||
WHEN 'u'::"char" THEN 'UNIQUE'::text
|
||||
ELSE NULL::text
|
||||
END::information_schema.character_data AS constraint_type,
|
||||
CASE
|
||||
WHEN c.condeferrable THEN 'YES'::text
|
||||
ELSE 'NO'::text
|
||||
END::information_schema.yes_or_no AS is_deferrable,
|
||||
CASE
|
||||
WHEN c.condeferred THEN 'YES'::text
|
||||
ELSE 'NO'::text
|
||||
END::information_schema.yes_or_no AS initially_deferred
|
||||
SELECT
|
||||
c.conname::name AS constraint_name,
|
||||
nr.nspname::name AS table_schema,
|
||||
r.relname::name AS table_name
|
||||
FROM pg_namespace nc,
|
||||
pg_namespace nr,
|
||||
pg_constraint c,
|
||||
pg_class r
|
||||
WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace AND c.conrelid = r.oid AND (c.contype <> ALL (ARRAY['t'::"char", 'x'::"char"])) AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid)
|
||||
/*--AND (pg_has_role(r.relowner, 'USAGE'::text) OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES'::text))*/
|
||||
UNION ALL
|
||||
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
||||
nr.nspname::information_schema.sql_identifier AS constraint_schema,
|
||||
(((((nr.oid::text || '_'::text) || r.oid::text) || '_'::text) || a.attnum::text) || '_not_null'::text)::information_schema.sql_identifier AS constraint_name,
|
||||
current_database()::information_schema.sql_identifier AS table_catalog,
|
||||
nr.nspname::information_schema.sql_identifier AS table_schema,
|
||||
r.relname::information_schema.sql_identifier AS table_name,
|
||||
'CHECK'::character varying::information_schema.character_data AS constraint_type,
|
||||
'NO'::character varying::information_schema.yes_or_no AS is_deferrable,
|
||||
'NO'::character varying::information_schema.yes_or_no AS initially_deferred
|
||||
FROM pg_namespace nr,
|
||||
pg_class r,
|
||||
pg_attribute a
|
||||
WHERE nr.oid = r.relnamespace AND r.oid = a.attrelid AND a.attnotnull AND a.attnum > 0 AND NOT a.attisdropped AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid)
|
||||
/*--AND (pg_has_role(r.relowner, 'USAGE'::text) OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES'::text))*/
|
||||
WHERE
|
||||
nc.oid = c.connamespace
|
||||
AND nr.oid = r.relnamespace
|
||||
AND c.conrelid = r.oid
|
||||
AND r.relkind = 'r'
|
||||
AND NOT pg_is_other_temp_schema(nr.oid)
|
||||
AND c.contype = 'p'
|
||||
),
|
||||
/*
|
||||
-- CTE to replace information_schema.key_column_usage to remove owner limit
|
||||
*/
|
||||
kc AS (
|
||||
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
||||
ss.nc_nspname::information_schema.sql_identifier AS constraint_schema,
|
||||
ss.conname::information_schema.sql_identifier AS constraint_name,
|
||||
current_database()::information_schema.sql_identifier AS table_catalog,
|
||||
ss.nr_nspname::information_schema.sql_identifier AS table_schema,
|
||||
ss.relname::information_schema.sql_identifier AS table_name,
|
||||
a.attname::information_schema.sql_identifier AS column_name,
|
||||
(ss.x).n::information_schema.cardinal_number AS ordinal_position,
|
||||
CASE
|
||||
WHEN ss.contype = 'f'::"char" THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n])
|
||||
ELSE NULL::integer
|
||||
END::information_schema.cardinal_number AS position_in_unique_constraint
|
||||
SELECT
|
||||
ss.conname::name AS constraint_name,
|
||||
ss.nr_nspname::name AS table_schema,
|
||||
ss.relname::name AS table_name,
|
||||
a.attname::name AS column_name,
|
||||
(ss.x).n::integer AS ordinal_position,
|
||||
CASE
|
||||
WHEN ss.contype = 'f' THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n])
|
||||
ELSE NULL::integer
|
||||
END::integer AS position_in_unique_constraint
|
||||
FROM pg_attribute a,
|
||||
( SELECT r.oid AS roid,
|
||||
r.relname,
|
||||
@@ -692,28 +632,31 @@ allPrimaryKeys tabs =
|
||||
c.contype,
|
||||
c.conindid,
|
||||
c.confkey,
|
||||
c.confrelid,
|
||||
information_schema._pg_expandarray(c.conkey) AS x
|
||||
FROM pg_namespace nr,
|
||||
pg_class r,
|
||||
pg_namespace nc,
|
||||
pg_constraint c
|
||||
WHERE nr.oid = r.relnamespace AND r.oid = c.conrelid AND nc.oid = c.connamespace AND (c.contype = ANY (ARRAY['p'::"char", 'u'::"char", 'f'::"char"])) AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid)) ss
|
||||
WHERE ss.roid = a.attrelid AND a.attnum = (ss.x).x AND NOT a.attisdropped
|
||||
/*--AND (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/
|
||||
WHERE
|
||||
nr.oid = r.relnamespace
|
||||
AND r.oid = c.conrelid
|
||||
AND nc.oid = c.connamespace
|
||||
AND c.contype in ('p', 'u', 'f')
|
||||
AND r.relkind = 'r'
|
||||
AND NOT pg_is_other_temp_schema(nr.oid)
|
||||
) ss
|
||||
WHERE
|
||||
ss.roid = a.attrelid
|
||||
AND a.attnum = (ss.x).x
|
||||
AND NOT a.attisdropped
|
||||
)
|
||||
SELECT
|
||||
kc.table_schema,
|
||||
kc.table_name,
|
||||
kc.column_name
|
||||
FROM
|
||||
/*
|
||||
--information_schema.table_constraints tc,
|
||||
--information_schema.key_column_usage kc
|
||||
*/
|
||||
tc, kc
|
||||
WHERE
|
||||
tc.constraint_type = 'PRIMARY KEY' AND
|
||||
kc.table_name = tc.table_name AND
|
||||
kc.table_schema = tc.table_schema AND
|
||||
kc.constraint_name = tc.constraint_name AND
|
||||
@@ -744,7 +687,7 @@ allSourceColumns cols pgVer =
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
join pg_rewrite r on r.ev_class = c.oid
|
||||
where (c.relkind in ('v', 'm')) and n.nspname = ANY ($1)
|
||||
where c.relkind in ('v', 'm') and n.nspname = ANY ($1)
|
||||
),
|
||||
removed_subselects as(
|
||||
select
|
||||
|
||||
+12
-20
@@ -23,12 +23,12 @@ import qualified Network.HTTP.Types.Status as HT
|
||||
|
||||
import Data.Aeson ((.=))
|
||||
import Network.Wai (Response, responseLBS)
|
||||
import Text.Read (readMaybe)
|
||||
|
||||
import Network.HTTP.Types.Header
|
||||
|
||||
import PostgREST.Types
|
||||
import Protolude
|
||||
import Protolude hiding (toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
|
||||
class (JSON.ToJSON a) => PgrstError a where
|
||||
@@ -223,10 +223,8 @@ data SimpleError
|
||||
= GucHeadersError
|
||||
| BinaryFieldError ContentType
|
||||
| ConnectionLostError
|
||||
| PutSingletonError
|
||||
| PutMatchingPkError
|
||||
| PutRangeNotAllowedError
|
||||
| PutPayloadIncompleteError
|
||||
| JwtTokenMissing
|
||||
| JwtTokenInvalid Text
|
||||
| SingularityError Integer
|
||||
@@ -234,17 +232,15 @@ data SimpleError
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance PgrstError SimpleError where
|
||||
status GucHeadersError = HT.status500
|
||||
status (BinaryFieldError _) = HT.status406
|
||||
status ConnectionLostError = HT.status503
|
||||
status PutSingletonError = HT.status400
|
||||
status PutMatchingPkError = HT.status400
|
||||
status PutRangeNotAllowedError = HT.status400
|
||||
status PutPayloadIncompleteError = HT.status400
|
||||
status JwtTokenMissing = HT.status500
|
||||
status (JwtTokenInvalid _) = HT.unauthorized401
|
||||
status (SingularityError _) = HT.status406
|
||||
status (ContentTypeError _) = HT.status415
|
||||
status GucHeadersError = HT.status500
|
||||
status (BinaryFieldError _) = HT.status406
|
||||
status ConnectionLostError = HT.status503
|
||||
status PutMatchingPkError = HT.status400
|
||||
status PutRangeNotAllowedError = HT.status400
|
||||
status JwtTokenMissing = HT.status500
|
||||
status (JwtTokenInvalid _) = HT.unauthorized401
|
||||
status (SingularityError _) = HT.status406
|
||||
status (ContentTypeError _) = HT.status415
|
||||
|
||||
headers (SingularityError _) = [toHeader CTSingularJSON]
|
||||
headers (JwtTokenInvalid m) = [toHeader CTApplicationJSON, invalidTokenHeader m]
|
||||
@@ -258,12 +254,8 @@ instance JSON.ToJSON SimpleError where
|
||||
toJSON ConnectionLostError = JSON.object [
|
||||
"message" .= ("Database connection lost, retrying the connection." :: Text)]
|
||||
|
||||
toJSON PutSingletonError = JSON.object [
|
||||
"message" .= ("PUT payload must contain a single row" :: Text)]
|
||||
toJSON PutRangeNotAllowedError = JSON.object [
|
||||
"message" .= ("Range header and limit/offset querystring parameters are not allowed for PUT" :: Text)]
|
||||
toJSON PutPayloadIncompleteError = JSON.object [
|
||||
"message" .= ("You must specify all columns in the payload when using PUT" :: Text)]
|
||||
toJSON PutMatchingPkError = JSON.object [
|
||||
"message" .= ("Payload values do not match URL in primary key column(s)" :: Text)]
|
||||
|
||||
@@ -280,7 +272,7 @@ instance JSON.ToJSON SimpleError where
|
||||
|
||||
invalidTokenHeader :: Text -> Header
|
||||
invalidTokenHeader m =
|
||||
("WWW-Authenticate", "Bearer error=\"invalid_token\", " <> "error_description=" <> show m)
|
||||
("WWW-Authenticate", "Bearer error=\"invalid_token\", " <> "error_description=" <> encodeUtf8 (show m))
|
||||
|
||||
singularityError :: (Integral a) => a -> SimpleError
|
||||
singularityError = SingularityError . toInteger
|
||||
|
||||
@@ -27,7 +27,8 @@ import PostgREST.Config (AppConfig (..), corsPolicy)
|
||||
import PostgREST.Error (SimpleError (JwtTokenInvalid, JwtTokenMissing),
|
||||
errorResponseFor)
|
||||
import PostgREST.QueryBuilder (setLocalQuery, setLocalSearchPathQuery)
|
||||
import Protolude hiding (head)
|
||||
import Protolude hiding (head, toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
runWithClaims :: AppConfig -> JWTAttempt ->
|
||||
(ApiRequest -> H.Transaction Response) ->
|
||||
|
||||
@@ -32,7 +32,8 @@ import PostgREST.Types (Column (..), ForeignKey (..), PgArg (..),
|
||||
PrimaryKey (..), ProcDescription (..),
|
||||
Proxy (..), Table (..), toMime)
|
||||
import Protolude hiding (Proxy, dropWhile, get,
|
||||
intercalate, (&))
|
||||
intercalate, toLower, toS, (&))
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
makeMimeList :: [ContentType] -> MimeList
|
||||
makeMimeList cs = MimeList $ map (fromString . toS . toMime) cs
|
||||
@@ -117,7 +118,7 @@ makeProcParam pd =
|
||||
[ Inline $ (mempty :: Param)
|
||||
& name .~ "args"
|
||||
& required ?~ True
|
||||
& schema .~ (ParamBody $ Inline $ makeProcSchema pd)
|
||||
& schema .~ ParamBody (Inline $ makeProcSchema pd)
|
||||
, Ref $ Reference "preferParams"
|
||||
]
|
||||
|
||||
@@ -221,7 +222,7 @@ makePathItem (t, cs, _) = ("/" ++ unpack tn, p $ tableInsertable t)
|
||||
& description .~ "OK"
|
||||
& schema ?~ Inline (mempty
|
||||
& type_ ?~ SwaggerArray
|
||||
& items ?~ (SwaggerItemsObject $ Ref $ Reference $ tableName t)
|
||||
& items ?~ SwaggerItemsObject (Ref $ Reference $ tableName t)
|
||||
)
|
||||
)
|
||||
postOp = tOp
|
||||
|
||||
@@ -9,10 +9,8 @@ module PostgREST.Parsers where
|
||||
import qualified Data.HashMap.Strict as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
import Control.Monad ((>>))
|
||||
import Data.Either.Combinators (mapLeft)
|
||||
import Data.Foldable (foldl1)
|
||||
import Data.Functor (($>))
|
||||
import Data.List (init, last)
|
||||
import Data.Text (intercalate, replace, strip)
|
||||
import Text.Read (read)
|
||||
@@ -24,7 +22,9 @@ import Text.ParserCombinators.Parsec hiding (many, (<|>))
|
||||
import PostgREST.Error (ApiRequestError (ParseRequestError))
|
||||
import PostgREST.RangeQuery (NonnegRange)
|
||||
import PostgREST.Types
|
||||
import Protolude hiding (intercalate, option, replace, try)
|
||||
import Protolude hiding (intercalate, option, replace, toS,
|
||||
try)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
pRequestSelect :: Text -> Either ApiRequestError [Tree SelectItem]
|
||||
pRequestSelect selStr =
|
||||
|
||||
@@ -11,12 +11,13 @@ import qualified Data.HashMap.Strict as HM
|
||||
import Data.Maybe
|
||||
import Data.Text (intercalate,
|
||||
isInfixOf, replace,
|
||||
toLower, unwords)
|
||||
toLower)
|
||||
import qualified Data.Text as T (map, null,
|
||||
takeWhile)
|
||||
import PostgREST.Types
|
||||
import Protolude hiding (cast,
|
||||
intercalate, replace)
|
||||
intercalate, replace,
|
||||
toLower)
|
||||
import Text.InterpolatedString.Perl6 (qc)
|
||||
|
||||
noLocationF :: SqlFragment
|
||||
@@ -186,8 +187,8 @@ countF :: SqlQuery -> Bool -> (SqlFragment, SqlFragment)
|
||||
countF countQuery shouldCount =
|
||||
if shouldCount
|
||||
then (
|
||||
", pg_source_count AS (" <> countQuery <> ")"
|
||||
, "(SELECT pg_catalog.count(*) FROM pg_source_count)" )
|
||||
", pgrst_source_count AS (" <> countQuery <> ")"
|
||||
, "(SELECT pg_catalog.count(*) FROM pgrst_source_count)" )
|
||||
else (
|
||||
mempty
|
||||
, "null::bigint")
|
||||
|
||||
@@ -22,7 +22,7 @@ module PostgREST.QueryBuilder (
|
||||
|
||||
import qualified Data.Set as S
|
||||
|
||||
import Data.Text (intercalate, unwords)
|
||||
import Data.Text (intercalate)
|
||||
import Data.Tree (Tree (..))
|
||||
|
||||
import Data.Maybe
|
||||
@@ -94,7 +94,10 @@ mutateRequestToQuery (Insert mainQi iCols onConflct putConditions returnings) =
|
||||
cols = intercalate ", " $ pgFmtIdent <$> S.toList iCols
|
||||
mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
||||
if S.null uCols
|
||||
then "WITH " <> ignoredBody <> "SELECT null WHERE false" -- if there are no columns we cannot do UPDATE table SET {empty}, it'd be invalid syntax
|
||||
-- if there are no columns we cannot do UPDATE table SET {empty}, it'd be invalid syntax
|
||||
-- selecting an empty resultset from mainQi gives us the column names to prevent errors when using &select=
|
||||
-- the select has to be based on "returnings" to make computed overloaded functions not throw
|
||||
then "WITH " <> ignoredBody <> "SELECT " <> empty_body_returned_columns <> " FROM " <> fromQi mainQi <> " WHERE false"
|
||||
else
|
||||
unwords [
|
||||
"WITH " <> normalizedBody,
|
||||
@@ -105,6 +108,10 @@ mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
||||
]
|
||||
where
|
||||
cols = intercalate ", " (pgFmtIdent <> const " = _." <> pgFmtIdent <$> S.toList uCols)
|
||||
empty_body_returned_columns :: SqlFragment
|
||||
empty_body_returned_columns
|
||||
| null returnings = "NULL"
|
||||
| otherwise = intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName mainQi) <$> returnings)
|
||||
mutateRequestToQuery (Delete mainQi logicForest returnings) =
|
||||
unwords [
|
||||
"WITH " <> ignoredBody,
|
||||
@@ -113,15 +120,15 @@ mutateRequestToQuery (Delete mainQi logicForest returnings) =
|
||||
returningF mainQi returnings
|
||||
]
|
||||
|
||||
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> SqlQuery
|
||||
requestToCallProcQuery qi pgArgs returnsScalar preferParams =
|
||||
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> [FieldName] -> SqlQuery
|
||||
requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
||||
unwords [
|
||||
"WITH",
|
||||
argsCTE,
|
||||
sourceBody ]
|
||||
where
|
||||
paramsAsSingleObject = preferParams == Just SingleObject
|
||||
paramsAsMulitpleObjects = preferParams == Just MultipleObjects
|
||||
paramsAsMultipleObjects = preferParams == Just MultipleObjects
|
||||
|
||||
(argsCTE, args)
|
||||
| null pgArgs = (ignoredBody, "")
|
||||
@@ -132,7 +139,7 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams =
|
||||
"pgrst_args AS (",
|
||||
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (\a -> " " <> pgaType a) <> ")",
|
||||
")"]
|
||||
, if paramsAsMulitpleObjects
|
||||
, if paramsAsMultipleObjects
|
||||
then fmtArgs (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a))
|
||||
else fmtArgs (\a -> " := (SELECT " <> pgFmtIdent (pgaName a) <> " FROM pgrst_args LIMIT 1)")
|
||||
)
|
||||
@@ -142,20 +149,25 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams =
|
||||
|
||||
sourceBody :: SqlFragment
|
||||
sourceBody
|
||||
| paramsAsMulitpleObjects =
|
||||
| paramsAsMultipleObjects =
|
||||
if returnsScalar
|
||||
then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args"
|
||||
else unwords [ "SELECT pgrst_lat_args.*"
|
||||
, "FROM pgrst_args,"
|
||||
, "LATERAL ( SELECT * FROM " <> callIt <> " ) pgrst_lat_args" ]
|
||||
, "LATERAL ( SELECT " <> returned_columns <> " FROM " <> callIt <> " ) pgrst_lat_args" ]
|
||||
| otherwise =
|
||||
if returnsScalar
|
||||
then "SELECT " <> callIt <> " AS pgrst_scalar"
|
||||
else "SELECT * FROM " <> callIt
|
||||
else "SELECT " <> returned_columns <> " FROM " <> callIt
|
||||
|
||||
callIt :: SqlFragment
|
||||
callIt = fromQi qi <> "(" <> args <> ")"
|
||||
|
||||
returned_columns :: SqlFragment
|
||||
returned_columns
|
||||
| null returnings = "*"
|
||||
| otherwise = intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName qi) <$> returnings)
|
||||
|
||||
|
||||
-- | SQL query meant for COUNTing the root node of the Tree.
|
||||
-- It only takes WHERE into account and doesn't include LIMIT/OFFSET because it would reduce the COUNT.
|
||||
|
||||
@@ -26,7 +26,8 @@ import Data.Ranged.Ranges
|
||||
import Network.HTTP.Types.Header
|
||||
import Network.HTTP.Types.Status
|
||||
|
||||
import Protolude
|
||||
import Protolude hiding (toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
type NonnegRange = Range Integer
|
||||
|
||||
@@ -91,9 +92,9 @@ rangeStatusHeader topLevelRange queryTotal tableTotal =
|
||||
|
||||
contentRangeH :: (Integral a, Show a) => a -> a -> Maybe a -> Header
|
||||
contentRangeH lower upper total =
|
||||
("Content-Range", headerValue)
|
||||
("Content-Range", toUtf8 headerValue)
|
||||
where
|
||||
headerValue = rangeString <> "/" <> totalString
|
||||
headerValue = rangeString <> "/" <> totalString :: Text
|
||||
rangeString
|
||||
| totalNotZero && fromInRange = show lower <> "-" <> show upper
|
||||
| otherwise = "*"
|
||||
|
||||
@@ -22,8 +22,6 @@ import Data.Aeson as JSON
|
||||
import qualified Data.Aeson.Lens as L
|
||||
import qualified Data.ByteString.Char8 as BS
|
||||
import Data.Maybe
|
||||
import Data.Text (unwords)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import qualified Hasql.Decoders as HD
|
||||
import qualified Hasql.Encoders as HE
|
||||
import qualified Hasql.Statement as H
|
||||
@@ -31,7 +29,8 @@ import PostgREST.Private.Common
|
||||
import PostgREST.Private.QueryFragment
|
||||
import PostgREST.Types
|
||||
import Protolude hiding (cast,
|
||||
replace)
|
||||
replace, toS)
|
||||
import Protolude.Conv (toS)
|
||||
import Text.InterpolatedString.Perl6 (qc)
|
||||
|
||||
{-| The generic query result format used by API responses. The location header
|
||||
@@ -55,7 +54,7 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
|
||||
{locF} AS header,
|
||||
{bodyF} AS body,
|
||||
{responseHeadersF pgVer} AS response_headers
|
||||
FROM ({selectQuery}) _postgrest_t |]
|
||||
FROM ({selectF}) _postgrest_t |]
|
||||
|
||||
locF =
|
||||
if isInsert && rep `elem` [Full, HeadersOnly]
|
||||
@@ -72,6 +71,11 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
|
||||
| wantSingle = asJsonSingleF
|
||||
| otherwise = asJsonF
|
||||
|
||||
selectF
|
||||
-- prevent using any of the column names in ?select= when no response is returned from the CTE
|
||||
| rep `elem` [None, HeadersOnly] = "SELECT * FROM " <> sourceCTEName
|
||||
| otherwise = selectQuery
|
||||
|
||||
decodeStandard :: HD.Result ResultsWithCount
|
||||
decodeStandard =
|
||||
fromMaybe (Nothing, 0, [], mempty, Right []) <$> HD.rowMaybe standardRow
|
||||
|
||||
@@ -24,7 +24,8 @@ import Network.HTTP.Types.Header (Header, hContentType)
|
||||
import Data.Tree
|
||||
|
||||
import PostgREST.RangeQuery (NonnegRange)
|
||||
import Protolude
|
||||
import Protolude hiding (toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
-- | Enumeration of currently supported response content types
|
||||
data ContentType = CTApplicationJSON | CTSingularJSON
|
||||
@@ -308,9 +309,8 @@ data PayloadJSON =
|
||||
ProcessedJSON {
|
||||
-- | This is the raw ByteString that comes from the request body.
|
||||
-- We cache this instead of an Aeson Value because it was detected that for large payloads the encoding
|
||||
-- had high memory usage, see #1005 for more details
|
||||
-- had high memory usage, see https://github.com/PostgREST/postgrest/pull/1005 for more details
|
||||
pjRaw :: BL.ByteString
|
||||
, pjType :: PJType
|
||||
-- | Keys of the object or if it's an array these keys are guaranteed to be the same across all its objects
|
||||
, pjKeys :: S.Set Text
|
||||
}|
|
||||
@@ -522,7 +522,7 @@ pgVersion121 :: PgVersion
|
||||
pgVersion121 = PgVersion 120001 "12.1"
|
||||
|
||||
sourceCTEName :: SqlFragment
|
||||
sourceCTEName = "pg_source"
|
||||
sourceCTEName = "pgrst_source"
|
||||
|
||||
-- | full jspath, e.g. .property[0].attr.detail
|
||||
type JSPath = [JSPathExp]
|
||||
|
||||
+7
-16
@@ -1,20 +1,11 @@
|
||||
# stack is used for circleci and appveyor CI builds
|
||||
resolver: lts-14.3
|
||||
ghc-options:
|
||||
# -fno-spec-constr may help keep compile time memory use in check,
|
||||
# see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304
|
||||
# -optP-Wno-nonportable-include-path
|
||||
# prevents build failures on case-insensitive filesystems (macos),
|
||||
# see https://github.com/commercialhaskell/stack/issues/3918
|
||||
postgrest: -O2 -Werror -Wall -fwarn-identities
|
||||
-fno-spec-constr -optP-Wno-nonportable-include-path
|
||||
resolver: lts-15.8
|
||||
nix:
|
||||
packages: [pcre, pkgconfig, postgresql, zlib]
|
||||
|
||||
# needed by stylish haskell, this only runs on ci
|
||||
# disable pure by default so that the test enviroment can be passed
|
||||
pure: false
|
||||
extra-deps:
|
||||
- HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311
|
||||
- HsYAML-aeson-0.2.0.0@sha256:04796abfc01cffded83f37a10e6edba4f0c0a15d45bef44fc5bb4313d9c87757,1791
|
||||
- configurator-pg-0.2.0@sha256:08dcfadbe31e9e505d0bed1ab034105e1735141499734ad52d1c5ac980cde4a6,2939
|
||||
- hspec-wai-0.10.1@sha256:56dd9ec1d56f47ef1946f71f7cbf070e4c285f718cac1b158400ae5e7172ef47,2290
|
||||
- hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
||||
- configurator-pg-0.2.2@sha256:8f3f7c66dd4a53852283c70806ebc101a7219f8b01c3e80c5130e9749c998dc8,2987
|
||||
- hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
||||
- interpolatedstring-perl6-1.0.2@sha256:7ce49c8a69a2a1b89c001ed79db2aab656ffd0faf2a7a701a553b6deb5c8ba7f,1073
|
||||
- protolude-0.3.0@sha256:8361b811b420585b122a7ba715aa5923834db6e8c36309bf267df2dbf66b95ef,2693
|
||||
|
||||
+22
-29
@@ -5,33 +5,12 @@
|
||||
|
||||
packages:
|
||||
- completed:
|
||||
hackage: HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311
|
||||
hackage: configurator-pg-0.2.2@sha256:8f3f7c66dd4a53852283c70806ebc101a7219f8b01c3e80c5130e9749c998dc8,2987
|
||||
pantry-tree:
|
||||
size: 1340
|
||||
sha256: 21f61bf9cad31674126b106071dd9b852e408796aeffc90eec1792f784107eff
|
||||
size: 2462
|
||||
sha256: a669366632e19f396dfe52599d54414b9c92d5664ea4391c2c797be271f9aa0e
|
||||
original:
|
||||
hackage: HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311
|
||||
- completed:
|
||||
hackage: HsYAML-aeson-0.2.0.0@sha256:04796abfc01cffded83f37a10e6edba4f0c0a15d45bef44fc5bb4313d9c87757,1791
|
||||
pantry-tree:
|
||||
size: 234
|
||||
sha256: 67cc9ba17c79e71d3abdb465a3ee2825477856fff3b8b7d543cbbbefdae9a9d9
|
||||
original:
|
||||
hackage: HsYAML-aeson-0.2.0.0@sha256:04796abfc01cffded83f37a10e6edba4f0c0a15d45bef44fc5bb4313d9c87757,1791
|
||||
- completed:
|
||||
hackage: configurator-pg-0.2.0@sha256:08dcfadbe31e9e505d0bed1ab034105e1735141499734ad52d1c5ac980cde4a6,2939
|
||||
pantry-tree:
|
||||
size: 1748
|
||||
sha256: 760eb12ee3d81b95b68ee10d5d85171b117826f44242b2749d48791efad6c891
|
||||
original:
|
||||
hackage: configurator-pg-0.2.0@sha256:08dcfadbe31e9e505d0bed1ab034105e1735141499734ad52d1c5ac980cde4a6,2939
|
||||
- completed:
|
||||
hackage: hspec-wai-0.10.1@sha256:56dd9ec1d56f47ef1946f71f7cbf070e4c285f718cac1b158400ae5e7172ef47,2290
|
||||
pantry-tree:
|
||||
size: 809
|
||||
sha256: 17af1c2e709cd84bfda066b9ebb04cdde7f92660c51a1f7401a1e9f766524e93
|
||||
original:
|
||||
hackage: hspec-wai-0.10.1@sha256:56dd9ec1d56f47ef1946f71f7cbf070e4c285f718cac1b158400ae5e7172ef47,2290
|
||||
hackage: configurator-pg-0.2.2@sha256:8f3f7c66dd4a53852283c70806ebc101a7219f8b01c3e80c5130e9749c998dc8,2987
|
||||
- completed:
|
||||
hackage: hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
||||
pantry-tree:
|
||||
@@ -39,9 +18,23 @@ packages:
|
||||
sha256: fb9e89b79cde3276baa484c860c6b9eeebdbc1a5c43301293351a25bc4c08e87
|
||||
original:
|
||||
hackage: hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
||||
- completed:
|
||||
hackage: interpolatedstring-perl6-1.0.2@sha256:7ce49c8a69a2a1b89c001ed79db2aab656ffd0faf2a7a701a553b6deb5c8ba7f,1073
|
||||
pantry-tree:
|
||||
size: 300
|
||||
sha256: f7f0b06c58db814d1f12ed6f4f4cb67ff8c72e51da61c04db12c2aedb896542e
|
||||
original:
|
||||
hackage: interpolatedstring-perl6-1.0.2@sha256:7ce49c8a69a2a1b89c001ed79db2aab656ffd0faf2a7a701a553b6deb5c8ba7f,1073
|
||||
- completed:
|
||||
hackage: protolude-0.3.0@sha256:8361b811b420585b122a7ba715aa5923834db6e8c36309bf267df2dbf66b95ef,2693
|
||||
pantry-tree:
|
||||
size: 1644
|
||||
sha256: babf32b414f25f790b7a4ce6bae5c960bc51a11a289e7c47335b222e6762560c
|
||||
original:
|
||||
hackage: protolude-0.3.0
|
||||
snapshots:
|
||||
- completed:
|
||||
size: 523878
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/3.yaml
|
||||
sha256: 470c46c27746a48c7c50f829efc0cf00112787a7804ee4ac7a27754658f6d92c
|
||||
original: lts-14.3
|
||||
size: 492015
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/15/8.yaml
|
||||
sha256: 926bc3d70249dd0ba05277ff00943c0addb35b627cb641752669e7cf771310d0
|
||||
original: lts-15.8
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
module Feature.ConcurrentSpec where
|
||||
|
||||
import Control.Concurrent.Async (mapConcurrently)
|
||||
import Control.Monad (void)
|
||||
import Network.Wai (Application)
|
||||
|
||||
import Control.Monad.Base
|
||||
|
||||
@@ -27,15 +27,30 @@ spec =
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "*/1"]
|
||||
}
|
||||
|
||||
it "ignores ?select= when return not set or return=minimal" $ do
|
||||
request methodDelete "/items?id=eq.3&select=id" [] ""
|
||||
`shouldRespondWith` ""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
request methodDelete "/items?id=eq.3&select=id" [("Prefer", "return=minimal")] ""
|
||||
`shouldRespondWith` ""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "returns the deleted item and shapes the response" $
|
||||
request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [str|[{"id":2,"name":"Two"}]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "can rename and cast the selected columns" $
|
||||
request methodDelete "/complex_items?id=eq.3&select=ciId:id::text,ciName:name" [("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [str|[{"ciId":"3","ciName":"Three"}]|]
|
||||
|
||||
it "can embed (parent) entities" $
|
||||
request methodDelete "/tasks?id=eq.8&select=id,name,project:projects(id)" [("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [str|[{"id":8,"name":"Code OSX","project":{"id":4}}]|]
|
||||
|
||||
@@ -324,20 +324,20 @@ spec =
|
||||
]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds childs recursively" $
|
||||
get "/family_tree?id=eq.1&select=id,name, childs:family_tree!parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
it "embeds children recursively" $
|
||||
get "/family_tree?id=eq.1&select=id,name, children:family_tree!parent(id,name,children:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": "1", "name": "Parental Unit", "childs": [
|
||||
{ "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] },
|
||||
{ "id": "3", "name": "Kid Two", "childs": [ { "id": "5", "name": "Grandkid Two" } ] }
|
||||
"id": "1", "name": "Parental Unit", "children": [
|
||||
{ "id": "2", "name": "Kid One", "children": [ { "id": "4", "name": "Grandkid One" } ] },
|
||||
{ "id": "3", "name": "Kid Two", "children": [ { "id": "5", "name": "Grandkid Two" } ] }
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds parent and then embeds childs" $
|
||||
get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
it "embeds parent and then embeds children" $
|
||||
get "/family_tree?id=eq.2&select=id,name,parent(id,name,children:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": "2", "name": "Kid One", "parent": {
|
||||
"id": "1", "name": "Parental Unit", "childs": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ]
|
||||
"id": "1", "name": "Parental Unit", "children": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ]
|
||||
}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
@@ -356,7 +356,7 @@ spec =
|
||||
}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds childs" $ do
|
||||
it "embeds children" $ do
|
||||
get "/organizations?select=id,name,refereeds:organizations!referee(id,name)&id=eq.1" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 1, "name": "Referee Org",
|
||||
|
||||
+167
-35
@@ -2,7 +2,6 @@ module Feature.InsertSpec where
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
|
||||
import Control.Monad (replicateM_, void)
|
||||
import Data.List (lookup)
|
||||
import Data.Maybe (fromJust)
|
||||
import Network.Wai (Application)
|
||||
@@ -48,6 +47,22 @@ spec actualPgVersion = do
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "ignores &select when return not set or using return=minimal" $ do
|
||||
request methodPost "/menagerie?select=integer,varchar" []
|
||||
[json| [{
|
||||
"integer": 15, "double": 3.14159, "varchar": "testing!"
|
||||
, "boolean": false, "date": "1900-01-01", "money": "$3.99"
|
||||
, "enum": "foo"
|
||||
}] |] `shouldRespondWith` ""
|
||||
{ matchStatus = 201 }
|
||||
request methodPost "/menagerie?select=integer,varchar" [("Prefer", "return=minimal")]
|
||||
[json| [{
|
||||
"integer": 16, "double": 3.14159, "varchar": "testing!"
|
||||
, "boolean": false, "date": "1900-01-01", "money": "$3.99"
|
||||
, "enum": "foo"
|
||||
}] |] `shouldRespondWith` ""
|
||||
{ matchStatus = 201 }
|
||||
|
||||
context "non uniform json array" $ do
|
||||
it "rejects json array that isn't exclusivily composed of objects" $
|
||||
post "/articles"
|
||||
@@ -189,7 +204,16 @@ spec actualPgVersion = do
|
||||
it "fails with 400 and error" $
|
||||
post "/simple_pk" "}{ x = 2"
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Error in $: Failed reading: not a valid json value"}|]
|
||||
[json|{"message":"Error in $: Failed reading: not a valid json value at '}{x=2'"}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
context "with no payload" $
|
||||
it "fails with 400 and error" $
|
||||
post "/simple_pk" ""
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Error in $: not enough input"}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
@@ -251,6 +275,20 @@ spec actualPgVersion = do
|
||||
, matchHeaders = []
|
||||
}
|
||||
|
||||
it "successfully inserts a row with all-default columns with prefer=rep" $
|
||||
request methodPost "/items" [("Prefer", "return=representation")] "{}"
|
||||
`shouldRespondWith` [json|[{ id: 20 }]|]
|
||||
{ matchStatus = 201,
|
||||
matchHeaders = []
|
||||
}
|
||||
|
||||
it "successfully inserts a row with all-default columns with prefer=rep and &select=" $
|
||||
request methodPost "/items?select=id" [("Prefer", "return=representation")] "{}"
|
||||
`shouldRespondWith` [json|[{ id: 21 }]|]
|
||||
{ matchStatus = 201,
|
||||
matchHeaders = []
|
||||
}
|
||||
|
||||
context "POST with ?columns parameter" $ do
|
||||
it "ignores json keys not included in ?columns" $ do
|
||||
request methodPost "/articles?columns=id,body" [("Prefer", "return=representation")]
|
||||
@@ -383,6 +421,24 @@ spec actualPgVersion = do
|
||||
matchHeaders = []
|
||||
}
|
||||
|
||||
context "with invalid json payload" $
|
||||
it "fails with 400 and error" $
|
||||
request methodPatch "/simple_pk" [] "}{ x = 2"
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Error in $: Failed reading: not a valid json value at '}{x=2'"}|]
|
||||
{ matchStatus = 400,
|
||||
matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
context "with no payload" $
|
||||
it "fails with 400 and error" $
|
||||
request methodPatch "/items" [] ""
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Error in $: not enough input"}|]
|
||||
{ matchStatus = 400,
|
||||
matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
context "in a nonempty table" $ do
|
||||
it "can update a single item" $ do
|
||||
g <- get "/items?id=eq.42"
|
||||
@@ -464,47 +520,123 @@ spec actualPgVersion = do
|
||||
matchHeaders = []
|
||||
}
|
||||
|
||||
it "can provide a representation" $ do
|
||||
_ <- post "/items"
|
||||
[json| { id: 1 } |]
|
||||
request methodPatch
|
||||
"/items?id=eq.1"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| { id: 99 } |]
|
||||
`shouldRespondWith` [json| [{id:99}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
-- put value back for other tests
|
||||
void $ request methodPatch "/items?id=eq.99" [] [json| { "id":1 } |]
|
||||
context "with representation requested" $ do
|
||||
it "can provide a representation" $ do
|
||||
_ <- post "/items"
|
||||
[json| { id: 1 } |]
|
||||
request methodPatch
|
||||
"/items?id=eq.1"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| { id: 99 } |]
|
||||
`shouldRespondWith` [json| [{id:99}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
-- put value back for other tests
|
||||
void $ request methodPatch "/items?id=eq.99" [] [json| { "id":1 } |]
|
||||
|
||||
it "makes no updates and returns 204, when patching with an empty json object/array" $ do
|
||||
request methodPatch "/items" [] [json| {} |]
|
||||
it "can return computed columns" $
|
||||
request methodPatch
|
||||
"/items?id=eq.1&select=id,always_true"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| { id: 1 } |]
|
||||
`shouldRespondWith` [json| [{ id: 1, always_true: true }] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can select overloaded computed columns" $ do
|
||||
request methodPatch
|
||||
"/items?id=eq.1&select=id,computed_overload"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| { id: 1 } |]
|
||||
`shouldRespondWith` [json| [{ id: 1, computed_overload: true }] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
request methodPatch
|
||||
"/items2?id=eq.1&select=id,computed_overload"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| { id: 1 } |]
|
||||
`shouldRespondWith` [json| [{ id: 1, computed_overload: true }] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "ignores ?select= when return not set or return=minimal" $ do
|
||||
request methodPatch "/items?id=eq.1&select=id" [] [json| { id:1 } |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
matchHeaders = ["Content-Range" <:> "0-0/*"]
|
||||
}
|
||||
|
||||
request methodPatch "/items" [] [json| [] |]
|
||||
request methodPatch "/items?id=eq.1&select=id" [("Prefer", "return=minimal")] [json| { id:1 } |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
matchHeaders = ["Content-Range" <:> "0-0/*"]
|
||||
}
|
||||
|
||||
request methodPatch "/items" [] [json| [{}] |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
context "when patching with an empty body" $ do
|
||||
it "makes no updates and returns 204 without return= and without ?select=" $ do
|
||||
request methodPatch "/items" [] [json| {} |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "makes no updates and returns 200, when patching with an empty json object and return=rep" $
|
||||
request methodPatch "/items" [("Prefer", "return=representation")] [json| {} |]
|
||||
`shouldRespondWith` "[]"
|
||||
{
|
||||
matchStatus = 200,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
request methodPatch "/items" [] [json| [] |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
request methodPatch "/items" [] [json| [{}] |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "makes no updates and returns 204 without return= and with ?select=" $ do
|
||||
request methodPatch "/items?select=id" [] [json| {} |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
request methodPatch "/items?select=id" [] [json| [] |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
request methodPatch "/items?select=id" [] [json| [{}] |]
|
||||
`shouldRespondWith` ""
|
||||
{
|
||||
matchStatus = 204,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "makes no updates and returns 200 with return=rep and without ?select=" $
|
||||
request methodPatch "/items" [("Prefer", "return=representation")] [json| {} |]
|
||||
`shouldRespondWith` "[]"
|
||||
{
|
||||
matchStatus = 200,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "makes no updates and returns 200 with return=rep and with ?select=" $
|
||||
request methodPatch "/items?select=id" [("Prefer", "return=representation")] [json| {} |]
|
||||
`shouldRespondWith` "[]"
|
||||
{
|
||||
matchStatus = 200,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
it "makes no updates and returns 200 with return=rep and with ?select= for overloaded computed columns" $
|
||||
request methodPatch "/items?select=id,computed_overload" [("Prefer", "return=representation")] [json| {} |]
|
||||
`shouldRespondWith` "[]"
|
||||
{
|
||||
matchStatus = 200,
|
||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
}
|
||||
|
||||
context "with unicode values" $
|
||||
it "succeeds and returns values intact" $ do
|
||||
@@ -561,7 +693,7 @@ spec actualPgVersion = do
|
||||
, matchHeaders = [ matchContentTypeJson , "Location" <:> "/web_content?id=eq.6" ]
|
||||
}
|
||||
|
||||
it "embeds childs after update" $
|
||||
it "embeds children after update" $
|
||||
request methodPatch "/web_content?id=eq.0&select=id,name,web_content(name)"
|
||||
[("Prefer", "return=representation")]
|
||||
[json|{"name": "tardis-patched"}|]
|
||||
@@ -573,7 +705,7 @@ spec actualPgVersion = do
|
||||
matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "embeds parent, childs and grandchilds after update" $
|
||||
it "embeds parent, children and grandchildren after update" $
|
||||
request methodPatch "/web_content?id=eq.0&select=id,name,web_content(name,web_content(name)),parent_content:p_web_id(name)"
|
||||
[("Prefer", "return=representation")]
|
||||
[json|{"name": "tardis-patched-2"}|]
|
||||
@@ -594,7 +726,7 @@ spec actualPgVersion = do
|
||||
matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "embeds childs after update without explicitly including the id in the ?select" $
|
||||
it "embeds children after update without explicitly including the id in the ?select" $
|
||||
request methodPatch "/web_content?id=eq.0&select=name,web_content(name)"
|
||||
[("Prefer", "return=representation")]
|
||||
[json|{"name": "tardis-patched"}|]
|
||||
|
||||
@@ -77,7 +77,7 @@ spec actualPgVersion =
|
||||
|
||||
context "Inserting tables on different schemas" $ do
|
||||
it "succeeds inserting on default schema and returning it" $
|
||||
request methodPost "/childs" [("Prefer", "return=representation")] [json|{"name": "child v1-1", "parent_id": 1}|]
|
||||
request methodPost "/children" [("Prefer", "return=representation")] [json|{"name": "child v1-1", "parent_id": 1}|]
|
||||
`shouldRespondWith`
|
||||
[json|[{"id":1, "name": "child v1-1", "parent_id": 1}]|]
|
||||
{
|
||||
@@ -86,7 +86,7 @@ spec actualPgVersion =
|
||||
}
|
||||
|
||||
it "succeeds inserting on the v1 schema and returning its parent" $
|
||||
request methodPost "/childs?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v1")]
|
||||
request methodPost "/children?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v1")]
|
||||
[json|{"name": "child v1-2", "parent_id": 2}|]
|
||||
`shouldRespondWith`
|
||||
[json|[{"id":2, "parent": {"id": 2, "name": "parent v1-2"}}]|]
|
||||
@@ -96,7 +96,7 @@ spec actualPgVersion =
|
||||
}
|
||||
|
||||
it "succeeds inserting on the v2 schema and returning its parent" $
|
||||
request methodPost "/childs?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v2")]
|
||||
request methodPost "/children?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v2")]
|
||||
[json|{"name": "child v2-3", "parent_id": 3}|]
|
||||
`shouldRespondWith`
|
||||
[json|[{"id":1, "parent": {"id": 3, "name": "parent v2-3"}}]|]
|
||||
@@ -106,7 +106,7 @@ spec actualPgVersion =
|
||||
}
|
||||
|
||||
it "fails when inserting on an unknown schema" $
|
||||
request methodPost "/childs" [("Content-Profile", "unknown")]
|
||||
request methodPost "/children" [("Content-Profile", "unknown")]
|
||||
[json|{"name": "child 4", "parent_id": 4}|]
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"The schema must be one of the following: v1, v2"}|]
|
||||
@@ -125,22 +125,34 @@ spec actualPgVersion =
|
||||
}
|
||||
|
||||
it "succeeds in calling the v1 schema proc and embedding" $
|
||||
request methodGet "/rpc/get_parents_below?id=6&select=id,name,childs(id,name)" [("Accept-Profile", "v1")] ""
|
||||
request methodGet "/rpc/get_parents_below?id=6&select=id,name,children(id,name)" [("Accept-Profile", "v1")] ""
|
||||
`shouldRespondWith`
|
||||
[json| [
|
||||
{"id":1,"name":"parent v1-1","childs":[{"id":1,"name":"child v1-1"}]},
|
||||
{"id":2,"name":"parent v1-2","childs":[{"id":2,"name":"child v1-2"}]}] |]
|
||||
{"id":1,"name":"parent v1-1","children":[{"id":1,"name":"child v1-1"}]},
|
||||
{"id":2,"name":"parent v1-2","children":[{"id":2,"name":"child v1-2"}]}] |]
|
||||
{
|
||||
matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v1"]
|
||||
}
|
||||
|
||||
it "succeeds in calling the v2 schema proc and embedding" $
|
||||
request methodGet "/rpc/get_parents_below?id=6&select=id,name,childs(id,name)" [("Accept-Profile", "v2")] ""
|
||||
request methodGet "/rpc/get_parents_below?id=6&select=id,name,children(id,name)" [("Accept-Profile", "v2")] ""
|
||||
`shouldRespondWith`
|
||||
[json| [
|
||||
{"id":3,"name":"parent v2-3","childs":[{"id":1,"name":"child v2-3"}]},
|
||||
{"id":4,"name":"parent v2-4","childs":[]}] |]
|
||||
{"id":3,"name":"parent v2-3","children":[{"id":1,"name":"child v2-3"}]},
|
||||
{"id":4,"name":"parent v2-4","children":[]}] |]
|
||||
{
|
||||
matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
||||
}
|
||||
|
||||
it "succeeds in calling the v2 schema proc with POST by using Content-Profile" $
|
||||
request methodPost "/rpc/get_parents_below?select=id,name" [("Content-Profile", "v2")]
|
||||
[json|{"id": "6"}|]
|
||||
`shouldRespondWith`
|
||||
[json| [
|
||||
{"id":3,"name":"parent v2-3"},
|
||||
{"id":4,"name":"parent v2-4"}]|]
|
||||
{
|
||||
matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
||||
@@ -148,7 +160,7 @@ spec actualPgVersion =
|
||||
|
||||
context "Modifying tables on different schemas" $ do
|
||||
it "succeeds in patching on the v1 schema and returning its parent" $
|
||||
request methodPatch "/childs?select=name,parent(name)&id=eq.1" [("Content-Profile", "v1"), ("Prefer", "return=representation")]
|
||||
request methodPatch "/children?select=name,parent(name)&id=eq.1" [("Content-Profile", "v1"), ("Prefer", "return=representation")]
|
||||
[json|{"name": "child v1-1 updated"}|]
|
||||
`shouldRespondWith`
|
||||
[json|[{"name":"child v1-1 updated", "parent": {"name": "parent v1-1"}}]|]
|
||||
@@ -158,7 +170,7 @@ spec actualPgVersion =
|
||||
}
|
||||
|
||||
it "succeeds in patching on the v2 schema and returning its parent" $
|
||||
request methodPatch "/childs?select=name,parent(name)&id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")]
|
||||
request methodPatch "/children?select=name,parent(name)&id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")]
|
||||
[json|{"name": "child v2-1 updated"}|]
|
||||
`shouldRespondWith`
|
||||
[json|[{"name":"child v2-1 updated", "parent": {"name": "parent v2-3"}}]|]
|
||||
@@ -168,13 +180,13 @@ spec actualPgVersion =
|
||||
}
|
||||
|
||||
it "succeeds on deleting on the v2 schema" $ do
|
||||
request methodDelete "/childs?id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] ""
|
||||
request methodDelete "/children?id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [json|[{"id": 1, "name": "child v2-1 updated", "parent_id": 3}]|]
|
||||
{
|
||||
matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
||||
}
|
||||
request methodGet "/childs?id=eq.1" [("Accept-Profile", "v2")] ""
|
||||
request methodGet "/children?id=eq.1" [("Accept-Profile", "v2")] ""
|
||||
`shouldRespondWith` "[]"
|
||||
{
|
||||
matchStatus = 200
|
||||
@@ -183,7 +195,7 @@ spec actualPgVersion =
|
||||
|
||||
when (actualPgVersion >= pgVersion96) $
|
||||
it "succeeds on PUT on the v2 schema" $
|
||||
request methodPut "/childs?id=eq.111" [("Content-Profile", "v2"), ("Prefer", "return=representation")]
|
||||
request methodPut "/children?id=eq.111" [("Content-Profile", "v2"), ("Prefer", "return=representation")]
|
||||
[json| [ { "id": 111, "name": "child v2-111", "parent_id": null } ]|]
|
||||
`shouldRespondWith`
|
||||
[json|[{ "id": 111, "name": "child v2-111", "parent_id": null }]|]
|
||||
|
||||
+33
-10
@@ -237,7 +237,6 @@ spec actualPgVersion = do
|
||||
[json| [{"myId":1}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
|
||||
it "one simple column with casting (text)" $
|
||||
get "/complex_items?select=id::text" `shouldRespondWith`
|
||||
[json| [{"id":"1"},{"id":"2"},{"id":"3"}] |]
|
||||
@@ -327,6 +326,30 @@ spec actualPgVersion = do
|
||||
[json|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "computed columns" $ do
|
||||
it "computed column on table" $
|
||||
get "/items?id=eq.1&select=id,always_true" `shouldRespondWith`
|
||||
[json|[{"id":1,"always_true":true}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "computed column on rpc" $
|
||||
get "/rpc/search?id=1&select=id,always_true" `shouldRespondWith`
|
||||
[json|[{"id":1,"always_true":true}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "overloaded computed columns on both tables" $ do
|
||||
get "/items?id=eq.1&select=id,computed_overload" `shouldRespondWith`
|
||||
[json|[{"id":1,"computed_overload":true}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/items2?id=eq.1&select=id,computed_overload" `shouldRespondWith`
|
||||
[json|[{"id":1,"computed_overload":true}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "overloaded computed column on rpc" $
|
||||
get "/rpc/search?id=1&select=id,computed_overload" `shouldRespondWith`
|
||||
[json|[{"id":1,"computed_overload":true}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "view embedding" $ do
|
||||
it "can detect fk relations through views to tables in the public schema" $
|
||||
get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200
|
||||
@@ -482,18 +505,18 @@ spec actualPgVersion = do
|
||||
"designTasks":[ ] }
|
||||
]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with two aliased childs embeds plus and/or" $
|
||||
get "/entities?select=id,childs:child_entities(id,gChilds:grandchild_entities(id))&childs.and=(id.in.(1,2,3))&childs.gChilds.or=(id.eq.1,id.eq.2)" `shouldRespondWith`
|
||||
it "works with two aliased children embeds plus and/or" $
|
||||
get "/entities?select=id,children:child_entities(id,gChildren:grandchild_entities(id))&children.and=(id.in.(1,2,3))&children.gChildren.or=(id.eq.1,id.eq.2)" `shouldRespondWith`
|
||||
[json|[
|
||||
{ "id":1,
|
||||
"childs":[
|
||||
{"id":1,"gChilds":[{"id":1}, {"id":2}]},
|
||||
{"id":2,"gChilds":[]}]},
|
||||
"children":[
|
||||
{"id":1,"gChildren":[{"id":1}, {"id":2}]},
|
||||
{"id":2,"gChildren":[]}]},
|
||||
{ "id":2,
|
||||
"childs":[
|
||||
{"id":3,"gChilds":[]}]},
|
||||
{ "id":3,"childs":[]},
|
||||
{ "id":4,"childs":[]}
|
||||
"children":[
|
||||
{"id":3,"gChildren":[]}]},
|
||||
{ "id":3,"children":[]},
|
||||
{ "id":4,"children":[]}
|
||||
]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "ordering response" $ do
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
module Feature.UnicodeSpec where
|
||||
|
||||
import Control.Monad (void)
|
||||
import Network.Wai (Application)
|
||||
import Network.Wai (Application)
|
||||
|
||||
import Test.Hspec
|
||||
import Test.Hspec.Wai
|
||||
|
||||
@@ -180,26 +180,6 @@ spec =
|
||||
[json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|]
|
||||
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "fails if the payload has more than one row" $
|
||||
put "/tiobe_pls?name=eq.Go"
|
||||
[str| [ { "name": "Go", "rank": 19 }, { "name": "Swift", "rank": 12 } ]|]
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"PUT payload must contain a single row"}|]
|
||||
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "fails if not all columns are specified" $ do
|
||||
put "/tiobe_pls?name=eq.Go"
|
||||
[str| [ { "name": "Go" } ]|]
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"You must specify all columns in the payload when using PUT"}|]
|
||||
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
put "/employees?first_name=eq.Susan&last_name=eq.Heidt"
|
||||
[str| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000" } ]|]
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"You must specify all columns in the payload when using PUT"}|]
|
||||
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "rejects every other filter than pk cols eq's" $ do
|
||||
put "/tiobe_pls?rank=eq.19"
|
||||
[str| [ { "name": "Go", "rank": 19 } ]|]
|
||||
@@ -280,6 +260,14 @@ spec =
|
||||
put "/tiobe_pls?name=eq.Go" [str| [ { "name": "Go", "rank": 13 } ]|] `shouldRespondWith` 204
|
||||
get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json| [ { "name": "Go", "rank": 13 } ]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "succeeds if the payload has more than one row, but it only puts the first element" $
|
||||
request methodPut "/tiobe_pls?name=eq.Go"
|
||||
[("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")]
|
||||
[str| [ { "name": "Go", "rank": 19 }, { "name": "Swift", "rank": 12 } ] |]
|
||||
`shouldRespondWith`
|
||||
[json|{ "name": "Go", "rank": 19 }|]
|
||||
{ matchStatus = 200 , matchHeaders = [matchContentTypeSingular] }
|
||||
|
||||
it "succeeds on table with composite pk" $ do
|
||||
get "/employees?first_name=eq.Susan&last_name=eq.Heidt"
|
||||
`shouldRespondWith`
|
||||
|
||||
+2
-2
@@ -16,7 +16,8 @@ import PostgREST.App (postgrest)
|
||||
import PostgREST.Config (AppConfig (..))
|
||||
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
||||
import PostgREST.Types (pgVersion95, pgVersion96)
|
||||
import Protolude hiding (toList)
|
||||
import Protolude hiding (toList, toS)
|
||||
import Protolude.Conv (toS)
|
||||
import SpecHelper
|
||||
|
||||
import qualified Feature.AndOrParamsSpec
|
||||
@@ -55,7 +56,6 @@ main = do
|
||||
getTime <- mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||
|
||||
testDbConn <- getEnvVarWithDefault "POSTGREST_TEST_CONNECTION" "postgres://postgrest_test@localhost/postgrest_test"
|
||||
setupDb testDbConn
|
||||
|
||||
pool <- P.acquire (3, 10, toS testDbConn)
|
||||
|
||||
|
||||
+7
-7
@@ -10,7 +10,8 @@ import qualified Hasql.Transaction as HT
|
||||
import qualified Hasql.Transaction.Sessions as HT
|
||||
import Text.Heredoc
|
||||
|
||||
import Protolude hiding (get)
|
||||
import Protolude hiding (get, toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
import PostgREST.QueryBuilder (requestToCallProcQuery)
|
||||
import PostgREST.Types
|
||||
@@ -22,40 +23,39 @@ import Test.Hspec
|
||||
main :: IO ()
|
||||
main = do
|
||||
testDbConn <- getEnvVarWithDefault "POSTGREST_TEST_CONNECTION" "postgres://postgrest_test@localhost/postgrest_test"
|
||||
-- To speed things up, assume setupDb has ben ran in the previous spec.
|
||||
pool <- P.acquire (3, 10, toS testDbConn)
|
||||
|
||||
hspec $ describe "QueryCost" $
|
||||
context "call proc query" $ do
|
||||
it "should not exceed cost when calling setof composite proc" $ do
|
||||
cost <- exec pool [str| {"id": 3} |] $
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False Nothing
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False Nothing []
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 40)
|
||||
|
||||
it "should not exceed cost when calling setof composite proc with empty params" $ do
|
||||
cost <- exec pool mempty $
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] False Nothing
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] False Nothing []
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 30)
|
||||
|
||||
it "should not exceed cost when calling scalar proc" $ do
|
||||
cost <- exec pool [str| {"a": 3, "b": 4} |] $
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing []
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 10)
|
||||
|
||||
context "params=multiple-objects" $ do
|
||||
it "should not exceed cost when calling setof composite proc" $ do
|
||||
cost <- exec pool [str| [{"id": 1}, {"id": 4}] |] $
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False (Just MultipleObjects)
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False (Just MultipleObjects) []
|
||||
liftIO $ do
|
||||
cost `shouldSatisfy` (> Just 2000)
|
||||
cost `shouldSatisfy` (< Just 2100)
|
||||
|
||||
it "should not exceed cost when calling scalar proc" $ do
|
||||
cost <- exec pool [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |] $
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing
|
||||
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing []
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 10)
|
||||
|
||||
|
||||
+2
-12
@@ -7,7 +7,6 @@ import qualified Data.Map.Strict as M
|
||||
import qualified Data.Set as S
|
||||
import qualified System.IO.Error as E
|
||||
|
||||
import Control.Monad (void)
|
||||
import Data.Aeson (Value (..), decode, encode)
|
||||
import Data.CaseInsensitive (CI (..))
|
||||
import Data.List (lookup)
|
||||
@@ -25,7 +24,8 @@ import Text.Heredoc
|
||||
|
||||
import PostgREST.Config (AppConfig (..))
|
||||
import PostgREST.Types (JSPathExp (..))
|
||||
import Protolude
|
||||
import Protolude hiding (toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
matchContentTypeJson :: MatchHeader
|
||||
matchContentTypeJson = "Content-Type" <:> "application/json; charset=utf-8"
|
||||
@@ -145,16 +145,6 @@ testCfgResponseHeaders testDbConn = (testCfg testDbConn) { configReqCheck = Just
|
||||
testMultipleSchemaCfg :: Text -> AppConfig
|
||||
testMultipleSchemaCfg testDbConn = (testCfg testDbConn) { configSchemas = fromList ["v1", "v2"] }
|
||||
|
||||
setupDb :: Text -> IO ()
|
||||
setupDb dbConn = do
|
||||
loadFixture dbConn "database"
|
||||
loadFixture dbConn "roles"
|
||||
loadFixture dbConn "schema"
|
||||
loadFixture dbConn "jwt"
|
||||
loadFixture dbConn "jsonschema"
|
||||
loadFixture dbConn "privileges"
|
||||
resetDb dbConn
|
||||
|
||||
resetDb :: Text -> IO ()
|
||||
resetDb dbConn = loadFixture dbConn "data"
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ ALTER DATABASE $DB SET LC_MESSAGES = 'POSIX';
|
||||
ALTER DATABASE $DB SET LC_MONETARY = 'POSIX';
|
||||
ALTER DATABASE $DB SET LC_NUMERIC = 'POSIX';
|
||||
ALTER DATABASE $DB SET LC_TIME = 'POSIX';
|
||||
|
||||
\i $BASEPATH/fixtures/load.sql
|
||||
EOF
|
||||
|
||||
#Remove database path from the connection uri--prevents setting up the new database name with PGDATABASE
|
||||
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
command: bash -c "POSTGREST_TEST_CONNECTION=$$(test/create_test_db 'postgres://postgres:postgres@postgres' test_db) stack test"
|
||||
|
||||
postgres:
|
||||
image: postgres:11
|
||||
image: postgres:12.2
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
volumes:
|
||||
|
||||
Vendored
+29
@@ -207,6 +207,35 @@ INSERT INTO items VALUES (15);
|
||||
SELECT pg_catalog.setval('items_id_seq', 15, true);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: items2; Type: TABLE DATA; Schema: test; Owner: -
|
||||
--
|
||||
|
||||
TRUNCATE TABLE items2 CASCADE;
|
||||
INSERT INTO items2 VALUES (1);
|
||||
INSERT INTO items2 VALUES (2);
|
||||
INSERT INTO items2 VALUES (3);
|
||||
INSERT INTO items2 VALUES (4);
|
||||
INSERT INTO items2 VALUES (5);
|
||||
INSERT INTO items2 VALUES (6);
|
||||
INSERT INTO items2 VALUES (7);
|
||||
INSERT INTO items2 VALUES (8);
|
||||
INSERT INTO items2 VALUES (9);
|
||||
INSERT INTO items2 VALUES (10);
|
||||
INSERT INTO items2 VALUES (11);
|
||||
INSERT INTO items2 VALUES (12);
|
||||
INSERT INTO items2 VALUES (13);
|
||||
INSERT INTO items2 VALUES (14);
|
||||
INSERT INTO items2 VALUES (15);
|
||||
|
||||
|
||||
--
|
||||
-- Name: items_id_seq; Type: SEQUENCE SET; Schema: test; Owner: -
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('items2_id_seq', 15, true);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: json; Type: TABLE DATA; Schema: test; Owner: -
|
||||
--
|
||||
|
||||
Vendored
+1
-1
@@ -20,7 +20,7 @@ HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||||
MODIFICATIONS.
|
||||
*/
|
||||
|
||||
CREATE OR REPLACE FUNCTION _validate_json_schema_type(type text, data jsonb) RETURNS boolean AS $f$
|
||||
CREATE OR REPLACE FUNCTION public._validate_json_schema_type(type text, data jsonb) RETURNS boolean AS $f$
|
||||
BEGIN
|
||||
IF type = 'integer' THEN
|
||||
IF jsonb_typeof(data) != 'number' THEN
|
||||
|
||||
Vendored
+3
@@ -1,5 +1,8 @@
|
||||
-- From michelp/pgjwt commit c02bbd3
|
||||
BEGIN;
|
||||
|
||||
set search_path to public;
|
||||
|
||||
set client_min_messages to warning;
|
||||
DROP SCHEMA IF EXISTS jwt CASCADE;
|
||||
CREATE SCHEMA jwt;
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
-- Loads all fixtures for the PostgREST tests
|
||||
|
||||
\set ON_ERROR_STOP on
|
||||
|
||||
\ir database.sql
|
||||
\ir roles.sql
|
||||
\ir schema.sql
|
||||
\ir jwt.sql
|
||||
\ir jsonschema.sql
|
||||
\ir privileges.sql
|
||||
\ir data.sql
|
||||
Vendored
+5
-4
@@ -15,6 +15,7 @@ SET search_path = test, "تست", pg_catalog;
|
||||
|
||||
GRANT ALL ON TABLE
|
||||
items
|
||||
, items2
|
||||
, "articleStars"
|
||||
, articles
|
||||
, auto_incrementing_pk
|
||||
@@ -127,8 +128,8 @@ GRANT ALL ON TABLE
|
||||
, v1.parents
|
||||
, v2.parents
|
||||
, v2.another_table
|
||||
, v1.childs
|
||||
, v2.childs
|
||||
, v1.children
|
||||
, v2.children
|
||||
TO postgrest_test_anonymous;
|
||||
|
||||
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
||||
@@ -138,8 +139,8 @@ GRANT USAGE ON SEQUENCE
|
||||
, items_id_seq
|
||||
, callcounter_count
|
||||
, leak_id_seq
|
||||
, v1.childs_id_seq
|
||||
, v2.childs_id_seq
|
||||
, v1.children_id_seq
|
||||
, v2.children_id_seq
|
||||
TO postgrest_test_anonymous;
|
||||
|
||||
-- Privileges for non anonymous users
|
||||
|
||||
Vendored
+20
-2
@@ -109,10 +109,28 @@ CREATE TABLE items (
|
||||
id bigserial primary key
|
||||
);
|
||||
|
||||
CREATE TABLE items2 (
|
||||
id bigserial primary key
|
||||
);
|
||||
|
||||
CREATE FUNCTION search(id BIGINT) RETURNS SETOF items
|
||||
LANGUAGE plpgsql
|
||||
AS $$BEGIN
|
||||
RETURN QUERY SELECT items.id FROM items WHERE items.id=search.id;
|
||||
END$$;
|
||||
|
||||
CREATE FUNCTION always_true(test.items) RETURNS boolean
|
||||
LANGUAGE sql STABLE
|
||||
AS $$ SELECT true $$;
|
||||
|
||||
CREATE FUNCTION computed_overload(test.items) RETURNS boolean
|
||||
LANGUAGE sql STABLE
|
||||
AS $$ SELECT true $$;
|
||||
|
||||
CREATE FUNCTION computed_overload(test.items2) RETURNS boolean
|
||||
LANGUAGE sql STABLE
|
||||
AS $$ SELECT true $$;
|
||||
|
||||
CREATE FUNCTION is_first(test.items) RETURNS boolean
|
||||
LANGUAGE sql STABLE
|
||||
AS $$ SELECT $1.id = 1 $$;
|
||||
@@ -1702,7 +1720,7 @@ create table v1.parents (
|
||||
, name text
|
||||
);
|
||||
|
||||
create table v1.childs (
|
||||
create table v1.children (
|
||||
id serial primary key
|
||||
, name text
|
||||
, parent_id int
|
||||
@@ -1720,7 +1738,7 @@ create table v2.parents (
|
||||
, name text
|
||||
);
|
||||
|
||||
create table v2.childs (
|
||||
create table v2.children (
|
||||
id serial primary key
|
||||
, name text
|
||||
, parent_id int
|
||||
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
keeptmp=${KEEP_TMP:-""}
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
|
||||
USAGE: $0 COMMAND
|
||||
|
||||
Runs the given COMMAND with the POSTGREST_TEST_CONNECTION environment variable
|
||||
set to a temporary database that is ready for running the PostgREST test suite.
|
||||
|
||||
You'll need to have the Postgres binaries 'initdb', 'pg_ctl' and 'psql' on your
|
||||
PATH.
|
||||
|
||||
Example:
|
||||
|
||||
$0 stack test
|
||||
|
||||
# Or, for when inside nix-shell
|
||||
$0 cabal v2-test
|
||||
|
||||
If KEEP_TMP is set (e.g. to "1"), the temporary directory will not be deleted
|
||||
after exit. The log files in that directory might be useful for debugging
|
||||
purposes.
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Please provide a command to be run with the temporary database."
|
||||
usage
|
||||
fi
|
||||
|
||||
rootdir="$(git rev-parse --show-toplevel)"
|
||||
|
||||
# All data will be stored in a temporary directory.
|
||||
tmpdir="$(mktemp -d)"
|
||||
|
||||
if [ -n "$keeptmp" ]; then
|
||||
echo "The temporary directory at $tmpdir will be preserved after exit."
|
||||
else
|
||||
trap "rm -rf tmpdir" sigint sigterm exit
|
||||
fi
|
||||
|
||||
dblog="$tmpdir/db.log"
|
||||
setuplog="$tmpdir/setup.log"
|
||||
|
||||
log() {
|
||||
echo "$1" >> "$setuplog"
|
||||
}
|
||||
|
||||
mkdir -p "$tmpdir"/{db,socket}
|
||||
|
||||
export PGDATA="$tmpdir/db"
|
||||
export PGHOST="$tmpdir/socket"
|
||||
export PGUSER=postgrest_test_authenticator
|
||||
export PGDATABASE=postgres
|
||||
export DB_URI="postgresql://$PGDATABASE?host=$PGHOST&user=$PGUSER"
|
||||
export POSTGREST_TEST_CONNECTION="$DB_URI"
|
||||
|
||||
log "Initializing database cluster..."
|
||||
# We try to make the database cluster as independent as possible from the host
|
||||
# by specifying the timezone, locale and encoding.
|
||||
PGTZ=UTC initdb --no-locale --encoding=UTF8 --nosync -U "$PGUSER" --auth=trust \
|
||||
>> "$setuplog"
|
||||
|
||||
log "Starting the database cluster..."
|
||||
# Instead of listening on a local port, we will listen on a unix domain socket.
|
||||
pg_ctl -l "$dblog" start -o "-F -c listen_addresses=\"\" -k $PGHOST" \
|
||||
>> "$setuplog"
|
||||
|
||||
log "Waiting for the database cluster to be ready..."
|
||||
# Waiting is required for older versions of Postgres (< 10).
|
||||
until pg_isready >> "$setuplog"; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
stop() {
|
||||
log "Stopping the database cluster..."
|
||||
pg_ctl stop -m i >> "$setuplog"
|
||||
|
||||
if [ -n "$keeptmp" ]; then
|
||||
echo "Keeping the temporary directory at $tmpdir"
|
||||
else
|
||||
rm -rf "$tmpdir"
|
||||
fi
|
||||
}
|
||||
|
||||
trap stop sigint sigterm exit
|
||||
|
||||
log "Loading fixtures..."
|
||||
psql -v ON_ERROR_STOP=1 >> "$setuplog" << EOF
|
||||
create extension pgcrypto;
|
||||
alter database $PGDATABASE set request.jwt.claim.id = '-1';
|
||||
alter role $PGUSER set default_text_search_config to english;
|
||||
\i $rootdir/test/fixtures/load.sql
|
||||
EOF
|
||||
|
||||
log "Done. Running command..."
|
||||
# Run the command that was given as an argument. The `exit` trap above will
|
||||
# make sure that the database is shut down and the temporary directory is
|
||||
# deleted when the command is done. This is also why we don't `exec` the
|
||||
# command - our trap would be lost if we did that.
|
||||
"$@"
|
||||
Reference in New Issue
Block a user