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
|
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:
|
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:
|
build-test-9.4:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/buildpack-deps:trusty
|
- image: circleci/buildpack-deps:trusty
|
||||||
environment:
|
environment:
|
||||||
- PGHOST=localhost
|
- PGHOST=localhost
|
||||||
- image: circleci/postgres:9.4.14
|
- image: circleci/postgres:9.4.26
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=circleci
|
- POSTGRES_USER=circleci
|
||||||
- POSTGRES_DB=circleci
|
- POSTGRES_DB=circleci
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -81,22 +84,11 @@ jobs:
|
|||||||
sudo apt-get install -y --only-upgrade binutils
|
sudo apt-get install -y --only-upgrade binutils
|
||||||
sudo apt-get install -y postgresql-client
|
sudo apt-get install -y postgresql-client
|
||||||
stack setup
|
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:
|
- run:
|
||||||
name: build src and tests dependencies
|
name: build src and tests dependencies
|
||||||
command: |
|
command: |
|
||||||
stack build --fast -j1 --only-dependencies
|
stack build --fast -j1 --only-dependencies
|
||||||
stack build --fast --test --no-run-tests --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:
|
- run:
|
||||||
name: build src and tests
|
name: build src and tests
|
||||||
command: |
|
command: |
|
||||||
@@ -107,22 +99,17 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
POSTGREST_TEST_CONNECTION=$(test/create_test_db "postgres://circleci@localhost" postgrest_test) stack test
|
POSTGREST_TEST_CONNECTION=$(test/create_test_db "postgres://circleci@localhost" postgrest_test) stack test
|
||||||
test/io-tests.sh
|
test/io-tests.sh
|
||||||
- run:
|
|
||||||
name: run linter
|
|
||||||
command: make lint
|
|
||||||
- run:
|
|
||||||
name: run styler
|
|
||||||
command: make style
|
|
||||||
|
|
||||||
build-test-9.6:
|
build-test-9.6:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/buildpack-deps:trusty
|
- image: circleci/buildpack-deps:trusty
|
||||||
environment:
|
environment:
|
||||||
- PGHOST=localhost
|
- PGHOST=localhost
|
||||||
- image: circleci/postgres:9.6.2
|
- image: circleci/postgres:9.6.17
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=circleci
|
- POSTGRES_USER=circleci
|
||||||
- POSTGRES_DB=circleci
|
- POSTGRES_DB=circleci
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -151,10 +138,11 @@ jobs:
|
|||||||
- image: circleci/buildpack-deps:trusty
|
- image: circleci/buildpack-deps:trusty
|
||||||
environment:
|
environment:
|
||||||
- PGHOST=localhost
|
- PGHOST=localhost
|
||||||
- image: circleci/postgres:10.5
|
- image: circleci/postgres:10.12
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=circleci
|
- POSTGRES_USER=circleci
|
||||||
- POSTGRES_DB=circleci
|
- POSTGRES_DB=circleci
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -183,10 +171,44 @@ jobs:
|
|||||||
- image: circleci/buildpack-deps:trusty
|
- image: circleci/buildpack-deps:trusty
|
||||||
environment:
|
environment:
|
||||||
- PGHOST=localhost
|
- PGHOST=localhost
|
||||||
- image: circleci/postgres:11.4
|
- image: circleci/postgres:11.7
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=circleci
|
- POSTGRES_USER=circleci
|
||||||
- POSTGRES_DB=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:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -216,10 +238,11 @@ jobs:
|
|||||||
environment:
|
environment:
|
||||||
- PGHOST=localhost
|
- PGHOST=localhost
|
||||||
- TERM=xterm
|
- TERM=xterm
|
||||||
- image: circleci/postgres:9.6.2
|
- image: circleci/postgres:9.6.17
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=circleci
|
- POSTGRES_USER=circleci
|
||||||
- POSTGRES_DB=circleci
|
- POSTGRES_DB=circleci
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -251,23 +274,8 @@ jobs:
|
|||||||
name: run memory usage tests
|
name: run memory usage tests
|
||||||
command: |
|
command: |
|
||||||
test/create_test_db "postgres://circleci@localhost" postgrest_test
|
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
|
test/memory-tests.sh
|
||||||
|
|
||||||
centos7:
|
|
||||||
<<: *build-distro-bin
|
|
||||||
|
|
||||||
ubuntu:
|
|
||||||
<<: *build-distro-bin
|
|
||||||
|
|
||||||
ubuntui386:
|
|
||||||
<<: *build-distro-bin
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.9
|
- image: circleci/golang:1.9
|
||||||
@@ -294,60 +302,132 @@ jobs:
|
|||||||
docker tag postgrest postgrest/postgrest:latest
|
docker tag postgrest postgrest/postgrest:latest
|
||||||
docker push 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:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build-test-release:
|
build-test-release:
|
||||||
jobs:
|
jobs:
|
||||||
|
- build-cache:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
|
- style:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
- build-test-9.4:
|
- build-test-9.4:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
|
requires:
|
||||||
|
- build-cache
|
||||||
- build-test-9.6:
|
- build-test-9.6:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
|
requires:
|
||||||
|
- build-cache
|
||||||
- build-test-10:
|
- build-test-10:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
|
requires:
|
||||||
|
- build-cache
|
||||||
- build-test-11:
|
- build-test-11:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
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:
|
- build-prof-test:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
- centos7:
|
- build-static-bin:
|
||||||
requires:
|
requires:
|
||||||
- build-test-9.4
|
- build-nix
|
||||||
- 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-prof-test
|
- build-prof-test
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
@@ -356,9 +436,11 @@ workflows:
|
|||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
- release:
|
- release:
|
||||||
requires:
|
requires:
|
||||||
- centos7
|
- build-static-bin
|
||||||
- ubuntu
|
filters:
|
||||||
- ubuntui386
|
tags:
|
||||||
|
only: /v[0-9]+(\.[0-9]+)*/
|
||||||
|
- build-nix:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
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)
|
- [Daniel Babiak](https://github.com/d-babiak)
|
||||||
- Evans Fernandes
|
- Evans Fernandes
|
||||||
|
- [Jan Sommer](https://github.com/nerfpops)
|
||||||
|
|
||||||
## Backers
|
## Backers
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
|||||||
- Nathan Bouscal
|
- Nathan Bouscal
|
||||||
- Daniel Rafaj
|
- Daniel Rafaj
|
||||||
- David Fenko
|
- David Fenko
|
||||||
|
- Remo Rechkemmer
|
||||||
|
|
||||||
## Former Backers
|
## Former Backers
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
### Fixed
|
### 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
|
## [7.0.0] - 2020-04-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ check: lint style test
|
|||||||
clean: prompt-clean
|
clean: prompt-clean
|
||||||
stack clean --full
|
stack clean --full
|
||||||
|
|
||||||
|
# For running these you'll need to install hlint and stylish-haskell first. Run:
|
||||||
|
# stack install hlint stylish-haskell
|
||||||
lint:
|
lint:
|
||||||
git ls-files | grep '\.l\?hs$$' | xargs stack exec -- hlint -X QuasiQuotes -X NoPatternSynonyms "$$@"
|
git ls-files | grep '\.l\?hs$$' | xargs stack exec -- hlint -X QuasiQuotes -X NoPatternSynonyms "$$@"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"POSTGREST_VER": {
|
"POSTGREST_VER": {
|
||||||
"description": "Version of PostgREST to deploy",
|
"description": "Version of PostgREST to deploy",
|
||||||
"value": "7.0.0"
|
"value": "7.0.1"
|
||||||
},
|
},
|
||||||
"DB_URI": {
|
"DB_URI": {
|
||||||
"description": "Database connection string, e.g. postgres://user:pass@xxxxxxx.rds.amazonaws.com/mydb",
|
"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.
|
## AppVeyor is only used for building a Windows binary, no tests are run here.
|
||||||
platform: x64
|
platform: x64
|
||||||
|
image: Visual Studio 2015
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- "c:\\sr"
|
- "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)
|
readIORef)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Data.Text (pack, replace, strip, stripPrefix)
|
import Data.Text (pack, replace, strip, stripPrefix)
|
||||||
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
|
import Data.Text.IO (hPutStrLn)
|
||||||
import Data.Text.IO (hPutStrLn, readFile)
|
|
||||||
import Data.Time.Clock (getCurrentTime)
|
import Data.Time.Clock (getCurrentTime)
|
||||||
import Network.Wai.Handler.Warp (defaultSettings, runSettings,
|
import Network.Wai.Handler.Warp (defaultSettings, runSettings,
|
||||||
setHost, setPort, setServerName)
|
setHost, setPort, setServerName)
|
||||||
@@ -34,7 +33,8 @@ import PostgREST.OpenAPI (isMalformedProxyUri)
|
|||||||
import PostgREST.Types (ConnectionStatus (..), DbStructure,
|
import PostgREST.Types (ConnectionStatus (..), DbStructure,
|
||||||
PgVersion (..), Schema,
|
PgVersion (..), Schema,
|
||||||
minimumPgVersion)
|
minimumPgVersion)
|
||||||
import Protolude hiding (hPutStrLn, head, replace)
|
import Protolude hiding (hPutStrLn, head, replace, toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
|
|
||||||
#ifndef mingw32_HOST_OS
|
#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
|
name: postgrest
|
||||||
version: 7.0.0
|
version: 7.0.1
|
||||||
synopsis: REST API for any Postgres database
|
synopsis: REST API for any Postgres database
|
||||||
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
||||||
for the tables and views, supporting all HTTP verbs that security
|
for the tables and views, supporting all HTTP verbs that security
|
||||||
@@ -43,12 +43,12 @@ library
|
|||||||
PostgREST.Private.Common
|
PostgREST.Private.Common
|
||||||
PostgREST.Private.QueryFragment
|
PostgREST.Private.QueryFragment
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
build-depends: base >= 4.9 && < 4.14
|
build-depends: base >= 4.9 && < 4.15
|
||||||
, HTTP >= 4000.3.7 && < 4000.4
|
, HTTP >= 4000.3.7 && < 4000.4
|
||||||
, Ranged-sets >= 0.3 && < 0.5
|
, Ranged-sets >= 0.3 && < 0.5
|
||||||
, aeson >= 0.11.3 && < 1.5
|
, aeson >= 1.4.7 && < 1.5
|
||||||
, ansi-wl-pprint >= 0.6.7 && < 0.7
|
, ansi-wl-pprint >= 0.6.7 && < 0.7
|
||||||
, base64-bytestring >= 1 && < 1.1
|
, base64-bytestring >= 1 && < 1.2
|
||||||
, bytestring >= 0.10.8 && < 0.11
|
, bytestring >= 0.10.8 && < 0.11
|
||||||
, case-insensitive >= 1.2 && < 1.3
|
, case-insensitive >= 1.2 && < 1.3
|
||||||
, cassava >= 0.4.5 && < 0.6
|
, cassava >= 0.4.5 && < 0.6
|
||||||
@@ -67,17 +67,17 @@ library
|
|||||||
, insert-ordered-containers >= 0.2.2 && < 0.3
|
, insert-ordered-containers >= 0.2.2 && < 0.3
|
||||||
, interpolatedstring-perl6 >= 1 && < 1.1
|
, interpolatedstring-perl6 >= 1 && < 1.1
|
||||||
, jose >= 0.8.1 && < 0.9
|
, jose >= 0.8.1 && < 0.9
|
||||||
, lens >= 4.14 && < 4.19
|
, lens >= 4.14 && < 4.20
|
||||||
, lens-aeson >= 1.0.1 && < 1.2
|
, 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
|
, optparse-applicative >= 0.13 && < 0.16
|
||||||
, parsec >= 3.1.11 && < 3.2
|
, parsec >= 3.1.11 && < 3.2
|
||||||
, protolude >= 0.2.2 && < 0.3
|
, protolude >= 0.3 && < 0.4
|
||||||
, regex-tdfa >= 1.2.2 && < 1.4
|
, regex-tdfa >= 1.2.2 && < 1.4
|
||||||
, scientific >= 0.3.4 && < 0.4
|
, scientific >= 0.3.4 && < 0.4
|
||||||
, swagger2 >= 2.4 && < 2.6
|
, swagger2 >= 2.4 && < 2.7
|
||||||
, text >= 1.2.2 && < 1.3
|
, text >= 1.2.2 && < 1.3
|
||||||
, time >= 1.6 && < 1.10
|
, time >= 1.6 && < 1.11
|
||||||
, unordered-containers >= 0.2.8 && < 0.3
|
, unordered-containers >= 0.2.8 && < 0.3
|
||||||
, vector >= 0.11 && < 0.13
|
, vector >= 0.11 && < 0.13
|
||||||
, wai >= 3.2.1 && < 3.3
|
, wai >= 3.2.1 && < 3.3
|
||||||
@@ -88,13 +88,20 @@ library
|
|||||||
default-extensions: OverloadedStrings
|
default-extensions: OverloadedStrings
|
||||||
QuasiQuotes
|
QuasiQuotes
|
||||||
NoImplicitPrelude
|
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
|
executable postgrest
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
hs-source-dirs: main
|
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
|
, auto-update >= 0.1.4 && < 0.2
|
||||||
, base64-bytestring >= 1 && < 1.1
|
, base64-bytestring >= 1 && < 1.2
|
||||||
, bytestring >= 0.10.8 && < 0.11
|
, bytestring >= 0.10.8 && < 0.11
|
||||||
, directory >= 1.2.6 && < 1.4
|
, directory >= 1.2.6 && < 1.4
|
||||||
, either >= 4.4.1 && < 5.1
|
, either >= 4.4.1 && < 5.1
|
||||||
@@ -103,10 +110,10 @@ executable postgrest
|
|||||||
, hasql-transaction >= 0.7.2 && < 1.1
|
, hasql-transaction >= 0.7.2 && < 1.1
|
||||||
, network < 3.2
|
, network < 3.2
|
||||||
, postgrest
|
, postgrest
|
||||||
, protolude >= 0.2.2 && < 0.3
|
, protolude >= 0.3 && < 0.4
|
||||||
, retry >= 0.7.4 && < 0.9
|
, retry >= 0.7.4 && < 0.9
|
||||||
, text >= 1.2.2 && < 1.3
|
, text >= 1.2.2 && < 1.3
|
||||||
, time >= 1.6 && < 1.10
|
, time >= 1.6 && < 1.11
|
||||||
, wai >= 3.2.1 && < 3.3
|
, wai >= 3.2.1 && < 3.3
|
||||||
, warp >= 3.2.12 && < 3.4
|
, warp >= 3.2.12 && < 3.4
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
@@ -114,6 +121,8 @@ executable postgrest
|
|||||||
QuasiQuotes
|
QuasiQuotes
|
||||||
NoImplicitPrelude
|
NoImplicitPrelude
|
||||||
ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2"
|
ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2"
|
||||||
|
-O2 -Werror -Wall -fwarn-identities
|
||||||
|
-fno-spec-constr -optP-Wno-nonportable-include-path
|
||||||
|
|
||||||
if !os(windows)
|
if !os(windows)
|
||||||
build-depends: unix
|
build-depends: unix
|
||||||
@@ -154,12 +163,12 @@ test-suite spec
|
|||||||
SpecHelper
|
SpecHelper
|
||||||
TestTypes
|
TestTypes
|
||||||
hs-source-dirs: test
|
hs-source-dirs: test
|
||||||
build-depends: base >= 4.9 && < 4.14
|
build-depends: base >= 4.9 && < 4.15
|
||||||
, aeson >= 0.11.3 && < 1.5
|
, aeson >= 1.4.7 && < 1.5
|
||||||
, aeson-qq >= 0.8.1 && < 0.9
|
, aeson-qq >= 0.8.1 && < 0.9
|
||||||
, async >= 2.1.1 && < 2.3
|
, async >= 2.1.1 && < 2.3
|
||||||
, auto-update >= 0.1.4 && < 0.2
|
, auto-update >= 0.1.4 && < 0.2
|
||||||
, base64-bytestring >= 1 && < 1.1
|
, base64-bytestring >= 1 && < 1.2
|
||||||
, bytestring >= 0.10.8 && < 0.11
|
, bytestring >= 0.10.8 && < 0.11
|
||||||
, case-insensitive >= 1.2 && < 1.3
|
, case-insensitive >= 1.2 && < 1.3
|
||||||
, cassava >= 0.4.5 && < 0.6
|
, cassava >= 0.4.5 && < 0.6
|
||||||
@@ -173,15 +182,15 @@ test-suite spec
|
|||||||
, hspec-wai >= 0.10 && < 0.11
|
, hspec-wai >= 0.10 && < 0.11
|
||||||
, hspec-wai-json >= 0.10 && < 0.11
|
, hspec-wai-json >= 0.10 && < 0.11
|
||||||
, http-types >= 0.12.3 && < 0.13
|
, http-types >= 0.12.3 && < 0.13
|
||||||
, lens >= 4.14 && < 4.19
|
, lens >= 4.14 && < 4.20
|
||||||
, lens-aeson >= 1.0.1 && < 1.2
|
, lens-aeson >= 1.0.1 && < 1.2
|
||||||
, monad-control >= 1.0.1 && < 1.1
|
, monad-control >= 1.0.1 && < 1.1
|
||||||
, postgrest
|
, postgrest
|
||||||
, process >= 1.4.2 && < 1.7
|
, process >= 1.4.2 && < 1.7
|
||||||
, protolude >= 0.2.2 && < 0.3
|
, protolude >= 0.3 && < 0.4
|
||||||
, regex-tdfa >= 1.2.2 && < 1.4
|
, regex-tdfa >= 1.2.2 && < 1.4
|
||||||
, text >= 1.2.2 && < 1.3
|
, text >= 1.2.2 && < 1.3
|
||||||
, time >= 1.6 && < 1.10
|
, time >= 1.6 && < 1.11
|
||||||
, transformers-base >= 0.4.4 && < 0.5
|
, transformers-base >= 0.4.4 && < 0.5
|
||||||
, wai >= 3.2.1 && < 3.3
|
, wai >= 3.2.1 && < 3.3
|
||||||
, wai-extra >= 3.0.19 && < 3.1
|
, wai-extra >= 3.0.19 && < 3.1
|
||||||
@@ -198,12 +207,12 @@ Test-Suite spec-querycost
|
|||||||
Hs-Source-Dirs: test
|
Hs-Source-Dirs: test
|
||||||
Main-Is: QueryCost.hs
|
Main-Is: QueryCost.hs
|
||||||
Other-Modules: SpecHelper
|
Other-Modules: SpecHelper
|
||||||
Build-Depends: base >= 4.9 && < 4.14
|
Build-Depends: base >= 4.9 && < 4.15
|
||||||
, aeson >= 0.11.3 && < 1.5
|
, aeson >= 1.4.7 && < 1.5
|
||||||
, aeson-qq >= 0.8.1 && < 0.9
|
, aeson-qq >= 0.8.1 && < 0.9
|
||||||
, async >= 2.1.1 && < 2.3
|
, async >= 2.1.1 && < 2.3
|
||||||
, auto-update >= 0.1.4 && < 0.2
|
, auto-update >= 0.1.4 && < 0.2
|
||||||
, base64-bytestring >= 1 && < 1.1
|
, base64-bytestring >= 1 && < 1.2
|
||||||
, bytestring >= 0.10.8 && < 0.11
|
, bytestring >= 0.10.8 && < 0.11
|
||||||
, case-insensitive >= 1.2 && < 1.3
|
, case-insensitive >= 1.2 && < 1.3
|
||||||
, cassava >= 0.4.5 && < 0.6
|
, cassava >= 0.4.5 && < 0.6
|
||||||
@@ -217,15 +226,15 @@ Test-Suite spec-querycost
|
|||||||
, hspec-wai >= 0.10 && < 0.11
|
, hspec-wai >= 0.10 && < 0.11
|
||||||
, hspec-wai-json >= 0.10 && < 0.11
|
, hspec-wai-json >= 0.10 && < 0.11
|
||||||
, http-types >= 0.12.3 && < 0.13
|
, http-types >= 0.12.3 && < 0.13
|
||||||
, lens >= 4.14 && < 4.19
|
, lens >= 4.14 && < 4.20
|
||||||
, lens-aeson >= 1.0.1 && < 1.2
|
, lens-aeson >= 1.0.1 && < 1.2
|
||||||
, monad-control >= 1.0.1 && < 1.1
|
, monad-control >= 1.0.1 && < 1.1
|
||||||
, postgrest
|
, postgrest
|
||||||
, process >= 1.4.2 && < 1.7
|
, process >= 1.4.2 && < 1.7
|
||||||
, protolude >= 0.2.2 && < 0.3
|
, protolude >= 0.3 && < 0.4
|
||||||
, regex-tdfa >= 1.2.2 && < 1.4
|
, regex-tdfa >= 1.2.2 && < 1.4
|
||||||
, text >= 1.2.2 && < 1.3
|
, text >= 1.2.2 && < 1.3
|
||||||
, time >= 1.6 && < 1.10
|
, time >= 1.6 && < 1.11
|
||||||
, transformers-base >= 0.4.4 && < 0.5
|
, transformers-base >= 0.4.4 && < 0.5
|
||||||
, wai >= 3.2.1 && < 3.3
|
, wai >= 3.2.1 && < 3.3
|
||||||
, wai-extra >= 3.0.19 && < 3.1
|
, 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 Control.Arrow ((***))
|
||||||
import Data.Aeson.Types (emptyArray, emptyObject)
|
import Data.Aeson.Types (emptyArray, emptyObject)
|
||||||
import Data.List (elem, last, lookup, partition)
|
import Data.List (last, lookup, partition)
|
||||||
import Data.List.NonEmpty (NonEmpty, head)
|
import Data.List.NonEmpty (head)
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
import Data.Ranged.Ranges (Range (..), emptyRange,
|
import Data.Ranged.Ranges (Range (..), emptyRange,
|
||||||
rangeIntersection)
|
rangeIntersection)
|
||||||
@@ -49,7 +49,8 @@ import PostgREST.RangeQuery (NonnegRange, allRange, rangeGeq,
|
|||||||
rangeLimit, rangeOffset, rangeRequested,
|
rangeLimit, rangeOffset, rangeRequested,
|
||||||
restrictRange)
|
restrictRange)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude hiding (head)
|
import Protolude hiding (head, toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
type RequestBody = BL.ByteString
|
type RequestBody = BL.ByteString
|
||||||
|
|
||||||
@@ -190,11 +191,10 @@ userApiRequest confSchemas rootSpec req reqBody
|
|||||||
(CTOther "application/x-www-form-urlencoded", _) ->
|
(CTOther "application/x-www-form-urlencoded", _) ->
|
||||||
let json = M.fromList . map (toS *** JSON.String . toS) . parseSimpleQuery $ toS reqBody
|
let json = M.fromList . map (toS *** JSON.String . toS) . parseSimpleQuery $ toS reqBody
|
||||||
keys = S.fromList $ M.keys json in
|
keys = S.fromList $ M.keys json in
|
||||||
Right $ ProcessedJSON (JSON.encode json) PJObject keys
|
Right $ ProcessedJSON (JSON.encode json) keys
|
||||||
(ct, _) ->
|
(ct, _) ->
|
||||||
Left $ toS $ "Content-Type not acceptable: " <> toMime ct
|
Left $ toS $ "Content-Type not acceptable: " <> toMime ct
|
||||||
rpcPrmsToJson = ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> rpcQParams)
|
rpcPrmsToJson = ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> rpcQParams) (S.fromList $ fst <$> rpcQParams)
|
||||||
PJObject (S.fromList $ fst <$> rpcQParams)
|
|
||||||
topLevelRange = fromMaybe allRange $ M.lookup "limit" ranges -- if no limit is specified, get all the request rows
|
topLevelRange = fromMaybe allRange $ M.lookup "limit" ranges -- if no limit is specified, get all the request rows
|
||||||
action =
|
action =
|
||||||
case method of
|
case method of
|
||||||
@@ -219,9 +219,9 @@ userApiRequest confSchemas rootSpec req reqBody
|
|||||||
profile
|
profile
|
||||||
| length confSchemas <= 1 -- only enable content negotiation by profile when there are multiple schemas specified in the config
|
| length confSchemas <= 1 -- only enable content negotiation by profile when there are multiple schemas specified in the config
|
||||||
= Nothing
|
= 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"
|
= 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]
|
ActionInspect False, ActionInspect True, ActionInfo]
|
||||||
= Just $ maybe defaultSchema toS $ lookupHeader "Accept-Profile"
|
= Just $ maybe defaultSchema toS $ lookupHeader "Accept-Profile"
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
@@ -333,14 +333,14 @@ payloadAttributes raw json =
|
|||||||
JSON.Object x -> S.fromList (M.keys x) == canonicalKeys
|
JSON.Object x -> S.fromList (M.keys x) == canonicalKeys
|
||||||
_ -> False) arr in
|
_ -> False) arr in
|
||||||
if areKeysUniform
|
if areKeysUniform
|
||||||
then Just $ ProcessedJSON raw (PJArray $ V.length arr) canonicalKeys
|
then Just $ ProcessedJSON raw canonicalKeys
|
||||||
else Nothing
|
else Nothing
|
||||||
Just _ -> Nothing
|
Just _ -> Nothing
|
||||||
Nothing -> Just emptyPJArray
|
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.
|
-- truncate everything else to an empty array.
|
||||||
_ -> Just emptyPJArray
|
_ -> Just emptyPJArray
|
||||||
where
|
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 Network.Wai
|
||||||
|
|
||||||
import PostgREST.ApiRequest (Action (..), ApiRequest (..),
|
import PostgREST.ApiRequest (Action (..), ApiRequest (..),
|
||||||
ContentType (..),
|
|
||||||
InvokeMethod (..), Target (..),
|
InvokeMethod (..), Target (..),
|
||||||
mutuallyAgreeable, userApiRequest)
|
mutuallyAgreeable, userApiRequest)
|
||||||
import PostgREST.Auth (containsRole, jwtClaims,
|
import PostgREST.Auth (containsRole, jwtClaims,
|
||||||
parseSecret)
|
parseSecret)
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.DbRequestBuilder (mutateRequest, readRequest)
|
import PostgREST.DbRequestBuilder (mutateRequest, readRequest,
|
||||||
|
returningCols)
|
||||||
import PostgREST.DbStructure
|
import PostgREST.DbStructure
|
||||||
import PostgREST.Error (PgError (..), SimpleError (..),
|
import PostgREST.Error (PgError (..), SimpleError (..),
|
||||||
errorResponseFor, singularityError)
|
errorResponseFor, singularityError)
|
||||||
@@ -64,7 +64,8 @@ import PostgREST.Statements (callProcStatement,
|
|||||||
createReadStatement,
|
createReadStatement,
|
||||||
createWriteStatement)
|
createWriteStatement)
|
||||||
import PostgREST.Types
|
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 :: AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
||||||
postgrest conf refDbStructure pool getTime worker =
|
postgrest conf refDbStructure pool getTime worker =
|
||||||
@@ -127,7 +128,7 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
(ActionRead headersOnly, TargetIdent (QualifiedIdentifier tSchema tName), Nothing) ->
|
(ActionRead headersOnly, TargetIdent (QualifiedIdentifier tSchema tName), Nothing) ->
|
||||||
case readSqlParts tSchema tName of
|
case readSqlParts tSchema tName of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right (q, cq, bField) -> do
|
Right (q, cq, bField, _) -> do
|
||||||
let cQuery = if estimatedCount
|
let cQuery = if estimatedCount
|
||||||
then limitedQuery cq ((+ 1) <$> maxRows) -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
then limitedQuery cq ((+ 1) <$> maxRows) -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
||||||
else cq
|
else cq
|
||||||
@@ -180,7 +181,7 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
, Just $ contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing
|
, Just $ contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing
|
||||||
, if null pkCols && isNothing (iOnConflict apiRequest)
|
, if null pkCols && isNothing (iOnConflict apiRequest)
|
||||||
then Nothing
|
then Nothing
|
||||||
else (\x -> ("Preference-Applied", show x)) <$> iPreferResolution apiRequest
|
else (\x -> ("Preference-Applied", encodeUtf8 (show x))) <$> iPreferResolution apiRequest
|
||||||
] ++ ctHeaders)) (unwrapGucHeader <$> ghdrs)
|
] ++ ctHeaders)) (unwrapGucHeader <$> ghdrs)
|
||||||
if contentType == CTSingularJSON && queryTotal /= 1
|
if contentType == CTSingularJSON && queryTotal /= 1
|
||||||
then do
|
then do
|
||||||
@@ -218,22 +219,14 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
else
|
else
|
||||||
return $ responseLBS status headers rBody
|
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
|
case mutateSqlParts tSchema tName of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right (sq, mq) -> do
|
Right (sq, mq) ->
|
||||||
let isSingle = case pjType of
|
|
||||||
PJArray len -> len == 1
|
|
||||||
PJObject -> True
|
|
||||||
colNames = colName <$> tableCols dbStructure tSchema tName
|
|
||||||
if topLevelRange /= allRange
|
if topLevelRange /= allRange
|
||||||
then return . errorResponseFor $ PutRangeNotAllowedError
|
then return . errorResponseFor $ PutRangeNotAllowedError
|
||||||
else if not isSingle
|
|
||||||
then return . errorResponseFor $ PutSingletonError
|
|
||||||
else if S.fromList colNames /= pjKeys
|
|
||||||
then return . errorResponseFor $ PutPayloadIncompleteError
|
|
||||||
else do
|
else do
|
||||||
row <- H.statement (toS pjRaw) $
|
row <- H.statement (toS $ pjRaw pJson) $
|
||||||
createWriteStatement sq mq (contentType == CTSingularJSON) False
|
createWriteStatement sq mq (contentType == CTSingularJSON) False
|
||||||
(contentType == CTTextCSV) (iPreferRepresentation apiRequest) [] pgVer
|
(contentType == CTTextCSV) (iPreferRepresentation apiRequest) [] pgVer
|
||||||
let (_, queryTotal, _, body, gucHeaders) = row
|
let (_, queryTotal, _, body, gucHeaders) = row
|
||||||
@@ -293,10 +286,10 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
let tName = fromMaybe pName $ procTableName =<< proc in
|
let tName = fromMaybe pName $ procTableName =<< proc in
|
||||||
case readSqlParts tSchema tName of
|
case readSqlParts tSchema tName of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right (q, cq, bField) -> do
|
Right (q, cq, bField, returning) -> do
|
||||||
let
|
let
|
||||||
preferParams = iPreferParameters apiRequest
|
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)
|
stm = callProcStatement returnsScalar pq q cq shouldCount (contentType == CTSingularJSON)
|
||||||
(contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects)
|
(contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects)
|
||||||
bField pgVer
|
bField pgVer
|
||||||
@@ -351,11 +344,14 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
readSqlParts s t =
|
readSqlParts s t =
|
||||||
let
|
let
|
||||||
readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest
|
readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest
|
||||||
|
returnings :: ReadRequest -> Either Response [FieldName]
|
||||||
|
returnings rr = Right (returningCols rr)
|
||||||
in
|
in
|
||||||
(,,) <$>
|
(,,,) <$>
|
||||||
(readRequestToQuery <$> readReq) <*>
|
(readRequestToQuery <$> readReq) <*>
|
||||||
(readRequestToCountQuery <$> readReq) <*>
|
(readRequestToCountQuery <$> readReq) <*>
|
||||||
(binaryField contentType rawContentTypes returnsScalar =<< readReq)
|
(binaryField contentType rawContentTypes returnsScalar =<< readReq) <*>
|
||||||
|
(returnings =<< readReq)
|
||||||
|
|
||||||
mutateSqlParts s t =
|
mutateSqlParts s t =
|
||||||
let
|
let
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import Control.Lens.Operators
|
|||||||
import Crypto.JWT
|
import Crypto.JWT
|
||||||
|
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude
|
import Protolude hiding (toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Possible situations encountered with client JWTs
|
Possible situations encountered with client JWTs
|
||||||
|
|||||||
@@ -32,17 +32,15 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
import qualified Data.Configurator as C
|
import qualified Data.Configurator as C
|
||||||
import qualified Text.PrettyPrint.ANSI.Leijen as L
|
import qualified Text.PrettyPrint.ANSI.Leijen as L
|
||||||
|
|
||||||
import Control.Exception (Handler (..))
|
|
||||||
import Control.Lens (preview)
|
import Control.Lens (preview)
|
||||||
import Control.Monad (fail)
|
import Control.Monad (fail)
|
||||||
import Crypto.JWT (StringOrURI, stringOrUri)
|
import Crypto.JWT (StringOrURI, stringOrUri)
|
||||||
import Data.List (lookup)
|
import Data.List (lookup)
|
||||||
import Data.List.NonEmpty (NonEmpty, fromList)
|
import Data.List.NonEmpty (fromList)
|
||||||
import Data.Scientific (floatingOrInteger)
|
import Data.Scientific (floatingOrInteger)
|
||||||
import Data.Text (dropEnd, dropWhileEnd,
|
import Data.Text (dropEnd, dropWhileEnd,
|
||||||
intercalate, lines, splitOn,
|
intercalate, splitOn, strip, take,
|
||||||
strip, take, unpack)
|
unpack)
|
||||||
import Data.Text.Encoding (encodeUtf8)
|
|
||||||
import Data.Text.IO (hPutStrLn)
|
import Data.Text.IO (hPutStrLn)
|
||||||
import Data.Version (versionBranch)
|
import Data.Version (versionBranch)
|
||||||
import Development.GitRev (gitHash)
|
import Development.GitRev (gitHash)
|
||||||
@@ -63,8 +61,8 @@ import PostgREST.Error (ApiRequestError (..))
|
|||||||
import PostgREST.Parsers (pRoleClaimKey)
|
import PostgREST.Parsers (pRoleClaimKey)
|
||||||
import PostgREST.Types (JSPath, JSPathExp (..))
|
import PostgREST.Types (JSPath, JSPathExp (..))
|
||||||
import Protolude hiding (concat, hPutStrLn, intercalate, null,
|
import Protolude hiding (concat, hPutStrLn, intercalate, null,
|
||||||
take, (<>))
|
take, toS, (<>))
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
|
|
||||||
-- | Config file settings for the server
|
-- | Config file settings for the server
|
||||||
@@ -155,7 +153,7 @@ readOptions = do
|
|||||||
AppConfig
|
AppConfig
|
||||||
<$> reqString "db-uri"
|
<$> reqString "db-uri"
|
||||||
<*> reqString "db-anon-role"
|
<*> reqString "db-anon-role"
|
||||||
<*> optString "server-proxy-uri"
|
<*> optString "openapi-server-proxy-uri"
|
||||||
<*> (fromList . splitOnCommas <$> reqValue "db-schema")
|
<*> (fromList . splitOnCommas <$> reqValue "db-schema")
|
||||||
<*> (fromMaybe "!4" <$> optString "server-host")
|
<*> (fromMaybe "!4" <$> optString "server-host")
|
||||||
<*> (fromMaybe 3000 <$> optInt "server-port")
|
<*> (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 (
|
module PostgREST.DbRequestBuilder (
|
||||||
readRequest
|
readRequest
|
||||||
, mutateRequest
|
, mutateRequest
|
||||||
|
, returningCols
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
@@ -40,7 +41,7 @@ readRequest :: Schema -> TableName -> Maybe Integer -> [Relation] -> ApiRequest
|
|||||||
readRequest schema rootTableName maxRows allRels apiRequest =
|
readRequest schema rootTableName maxRows allRels apiRequest =
|
||||||
mapLeft errorResponseFor $
|
mapLeft errorResponseFor $
|
||||||
treeRestrictRange maxRows =<<
|
treeRestrictRange maxRows =<<
|
||||||
augumentRequestWithJoin schema rootRels =<<
|
augmentRequestWithJoin schema rootRels =<<
|
||||||
addFiltersOrdersRanges apiRequest <*>
|
addFiltersOrdersRanges apiRequest <*>
|
||||||
(initReadRequest rootName <$> pRequestSelect sel)
|
(initReadRequest rootName <$> pRequestSelect sel)
|
||||||
where
|
where
|
||||||
@@ -89,8 +90,8 @@ treeRestrictRange maxRows request = pure $ nodeRestrictRange maxRows <$> request
|
|||||||
nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode
|
nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode
|
||||||
nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i)
|
nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i)
|
||||||
|
|
||||||
augumentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest
|
augmentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||||
augumentRequestWithJoin schema allRels request =
|
augmentRequestWithJoin schema allRels request =
|
||||||
addRels schema allRels Nothing request
|
addRels schema allRels Nothing request
|
||||||
>>= addJoinConditions Nothing
|
>>= addJoinConditions Nothing
|
||||||
|
|
||||||
|
|||||||
+113
-170
@@ -35,14 +35,13 @@ import Data.Set as S (fromList)
|
|||||||
import Data.Text (breakOn, dropAround, split,
|
import Data.Text (breakOn, dropAround, split,
|
||||||
splitOn, strip)
|
splitOn, strip)
|
||||||
import GHC.Exts (groupWith)
|
import GHC.Exts (groupWith)
|
||||||
|
import Protolude hiding (toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
import Protolude.Unsafe (unsafeHead)
|
||||||
import Text.InterpolatedString.Perl6 (q, qc)
|
import Text.InterpolatedString.Perl6 (q, qc)
|
||||||
import Unsafe (unsafeHead)
|
|
||||||
|
|
||||||
import Control.Applicative
|
|
||||||
|
|
||||||
import PostgREST.Private.Common
|
import PostgREST.Private.Common
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude
|
|
||||||
|
|
||||||
getDbStructure :: [Schema] -> PgVersion -> HT.Transaction DbStructure
|
getDbStructure :: [Schema] -> PgVersion -> HT.Transaction DbStructure
|
||||||
getDbStructure schemas pgVer = do
|
getDbStructure schemas pgVer = do
|
||||||
@@ -190,14 +189,14 @@ accessibleProcs = H.Statement (toS sql) (param HE.text) decodeProcs True
|
|||||||
procsSqlQuery :: SqlQuery
|
procsSqlQuery :: SqlQuery
|
||||||
procsSqlQuery = [q|
|
procsSqlQuery = [q|
|
||||||
SELECT
|
SELECT
|
||||||
pn.nspname as "proc_schema",
|
pn.nspname as proc_schema,
|
||||||
p.proname as "proc_name",
|
p.proname as proc_name,
|
||||||
d.description as "proc_description",
|
d.description as proc_description,
|
||||||
pg_get_function_arguments(p.oid) as "args",
|
pg_get_function_arguments(p.oid) as args,
|
||||||
tn.nspname as "rettype_schema",
|
tn.nspname as rettype_schema,
|
||||||
coalesce(comp.relname, t.typname) as "rettype_name",
|
coalesce(comp.relname, t.typname) as rettype_name,
|
||||||
p.proretset as "rettype_is_setof",
|
p.proretset as rettype_is_setof,
|
||||||
t.typtype as "rettype_typ",
|
t.typtype as rettype_typ,
|
||||||
p.provolatile
|
p.provolatile
|
||||||
FROM pg_proc p
|
FROM pg_proc p
|
||||||
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
||||||
@@ -229,11 +228,22 @@ accessibleTables =
|
|||||||
n.nspname as table_schema,
|
n.nspname as table_schema,
|
||||||
relname as table_name,
|
relname as table_name,
|
||||||
d.description as table_description,
|
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 (
|
c.relkind in ('r', 'v', 'f')
|
||||||
select 1
|
and (pg_relation_is_updatable(c.oid::regclass, false) & 8) = 8
|
||||||
from pg_trigger
|
-- The function `pg_relation_is_updateable` returns a bitmask where 8
|
||||||
where pg_trigger.tgrelid = c.oid and (pg_trigger.tgtype::integer & 69) = 69)
|
-- 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
|
) as insertable
|
||||||
from
|
from
|
||||||
pg_class c
|
pg_class c
|
||||||
@@ -243,9 +253,9 @@ accessibleTables =
|
|||||||
c.relkind in ('v', 'r', 'm', 'f')
|
c.relkind in ('v', 'r', 'm', 'f')
|
||||||
and n.nspname = $1
|
and n.nspname = $1
|
||||||
and (
|
and (
|
||||||
pg_has_role(c.relowner, 'USAGE'::text)
|
pg_has_role(c.relowner, 'USAGE')
|
||||||
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text)
|
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
|
||||||
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text)
|
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||||
)
|
)
|
||||||
order by relname |]
|
order by relname |]
|
||||||
|
|
||||||
@@ -374,13 +384,17 @@ allTables =
|
|||||||
n.nspname AS table_schema,
|
n.nspname AS table_schema,
|
||||||
c.relname AS table_name,
|
c.relname AS table_name,
|
||||||
NULL AS table_description,
|
NULL AS table_description,
|
||||||
c.relkind = 'r' OR (c.relkind IN ('v','f'))
|
(
|
||||||
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
|
c.relkind IN ('r', 'v','f')
|
||||||
OR (EXISTS
|
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
|
||||||
( SELECT 1
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
FROM pg_trigger
|
FROM pg_trigger
|
||||||
WHERE pg_trigger.tgrelid = c.oid
|
WHERE
|
||||||
AND (pg_trigger.tgtype::integer & 69) = 69) ) AS insertable
|
pg_trigger.tgrelid = c.oid
|
||||||
|
AND (pg_trigger.tgtype::integer & 69) = 69
|
||||||
|
)
|
||||||
|
) AS insertable
|
||||||
FROM pg_class c
|
FROM pg_class c
|
||||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
WHERE c.relkind IN ('v','r','m','f')
|
WHERE c.relkind IN ('v','r','m','f')
|
||||||
@@ -407,9 +421,7 @@ allColumns tabs =
|
|||||||
info.column_default AS default_value,
|
info.column_default AS default_value,
|
||||||
array_to_string(enum_info.vals, ',') AS enum
|
array_to_string(enum_info.vals, ',') AS enum
|
||||||
FROM (
|
FROM (
|
||||||
/*
|
|
||||||
-- CTE based on pg_catalog to get PRIMARY/FOREIGN key and UNIQUE columns outside api schema
|
-- CTE based on pg_catalog to get PRIMARY/FOREIGN key and UNIQUE columns outside api schema
|
||||||
*/
|
|
||||||
WITH key_columns AS (
|
WITH key_columns AS (
|
||||||
SELECT
|
SELECT
|
||||||
r.oid AS r_oid,
|
r.oid AS r_oid,
|
||||||
@@ -437,19 +449,16 @@ allColumns tabs =
|
|||||||
-- limit columns to the ones in the api schema or PK/FK columns
|
-- limit columns to the ones in the api schema or PK/FK columns
|
||||||
*/
|
*/
|
||||||
columns AS (
|
columns AS (
|
||||||
SELECT current_database()::information_schema.sql_identifier AS table_catalog,
|
SELECT
|
||||||
nc.nspname::information_schema.sql_identifier AS table_schema,
|
nc.nspname::name AS table_schema,
|
||||||
c.relname::information_schema.sql_identifier AS table_name,
|
c.relname::name AS table_name,
|
||||||
a.attname::information_schema.sql_identifier AS column_name,
|
a.attname::name AS column_name,
|
||||||
d.description AS description,
|
d.description AS description,
|
||||||
a.attnum::information_schema.cardinal_number AS ordinal_position,
|
a.attnum::integer AS ordinal_position,
|
||||||
pg_get_expr(ad.adbin, ad.adrelid)::information_schema.character_data AS column_default,
|
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
|
CASE
|
||||||
WHEN a.attnotnull OR t.typtype = 'd'::"char" AND t.typnotnull THEN 'NO'::text
|
WHEN t.typtype = 'd' THEN
|
||||||
ELSE 'YES'::text
|
|
||||||
END::information_schema.yes_or_no AS is_nullable,
|
|
||||||
CASE
|
|
||||||
WHEN t.typtype = 'd'::"char" THEN
|
|
||||||
CASE
|
CASE
|
||||||
WHEN bt.typelem <> 0::oid AND bt.typlen = (-1) THEN 'ARRAY'::text
|
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)
|
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)
|
WHEN nt.nspname = 'pg_catalog'::name THEN format_type(a.atttypid, NULL::integer)
|
||||||
ELSE format_type(a.atttypid, a.atttypmod)
|
ELSE format_type(a.atttypid, a.atttypmod)
|
||||||
END
|
END
|
||||||
END::information_schema.character_data AS data_type,
|
END::text 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_max_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_truetypid(a.*, t.*),
|
||||||
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_truetypmod(a.*, t.*)
|
||||||
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,
|
)::integer AS character_maximum_length,
|
||||||
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_numeric_precision(
|
||||||
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_truetypid(a.*, t.*),
|
||||||
information_schema._pg_interval_type(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.character_data AS interval_type,
|
information_schema._pg_truetypmod(a.*, t.*)
|
||||||
NULL::integer::information_schema.cardinal_number AS interval_precision,
|
)::integer AS numeric_precision,
|
||||||
NULL::character varying::information_schema.sql_identifier AS character_set_catalog,
|
COALESCE(bt.typname, t.typname)::name AS udt_name,
|
||||||
NULL::character varying::information_schema.sql_identifier AS character_set_schema,
|
(
|
||||||
NULL::character varying::information_schema.sql_identifier AS character_set_name,
|
c.relkind in ('r', 'v', 'f')
|
||||||
CASE
|
AND pg_column_is_updatable(c.oid::regclass, a.attnum, false)
|
||||||
WHEN nco.nspname IS NOT NULL THEN current_database()
|
)::bool is_updatable
|
||||||
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
|
|
||||||
FROM pg_attribute a
|
FROM pg_attribute a
|
||||||
LEFT JOIN key_columns kc ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid
|
LEFT JOIN key_columns kc
|
||||||
LEFT JOIN pg_catalog.pg_description AS d ON d.objoid = a.attrelid and d.objsubid = a.attnum
|
ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid
|
||||||
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
LEFT JOIN pg_catalog.pg_description AS d
|
||||||
JOIN (pg_class c
|
ON d.objoid = a.attrelid and d.objsubid = a.attnum
|
||||||
JOIN pg_namespace nc ON c.relnamespace = nc.oid) ON a.attrelid = c.oid
|
LEFT JOIN pg_attrdef ad
|
||||||
JOIN (pg_type t
|
ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
||||||
JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON a.atttypid = t.oid
|
JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid)
|
||||||
LEFT JOIN (pg_type bt
|
ON a.attrelid = c.oid
|
||||||
JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) ON t.typtype = 'd'::"char" AND t.typbasetype = bt.oid
|
JOIN (pg_type t JOIN pg_namespace nt ON t.typnamespace = nt.oid)
|
||||||
LEFT JOIN (pg_collation co
|
ON a.atttypid = t.oid
|
||||||
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 (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
|
WHERE
|
||||||
NOT pg_is_other_temp_schema(nc.oid)
|
NOT pg_is_other_temp_schema(nc.oid)
|
||||||
AND a.attnum > 0
|
AND a.attnum > 0
|
||||||
AND NOT a.attisdropped
|
AND NOT a.attisdropped
|
||||||
AND (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char", 'm'::"char"]))
|
AND c.relkind in ('r', 'v', 'f', 'm')
|
||||||
AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) /*--filter only columns that are FK/PK or in the api schema */
|
-- 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 (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL)
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
table_schema,
|
table_schema,
|
||||||
@@ -546,7 +520,6 @@ allColumns tabs =
|
|||||||
numeric_precision,
|
numeric_precision,
|
||||||
column_default,
|
column_default,
|
||||||
udt_name
|
udt_name
|
||||||
/*-- FROM information_schema.columns*/
|
|
||||||
FROM columns
|
FROM columns
|
||||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||||
) AS info
|
) AS info
|
||||||
@@ -618,69 +591,36 @@ allPrimaryKeys tabs =
|
|||||||
H.Statement sql HE.noParams (decodePks tabs) True
|
H.Statement sql HE.noParams (decodePks tabs) True
|
||||||
where
|
where
|
||||||
sql = [q|
|
sql = [q|
|
||||||
/*
|
|
||||||
-- CTE to replace information_schema.table_constraints to remove owner limit
|
-- CTE to replace information_schema.table_constraints to remove owner limit
|
||||||
*/
|
|
||||||
WITH tc AS (
|
WITH tc AS (
|
||||||
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
SELECT
|
||||||
nc.nspname::information_schema.sql_identifier AS constraint_schema,
|
c.conname::name AS constraint_name,
|
||||||
c.conname::information_schema.sql_identifier AS constraint_name,
|
nr.nspname::name AS table_schema,
|
||||||
current_database()::information_schema.sql_identifier AS table_catalog,
|
r.relname::name AS table_name
|
||||||
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
|
|
||||||
FROM pg_namespace nc,
|
FROM pg_namespace nc,
|
||||||
pg_namespace nr,
|
pg_namespace nr,
|
||||||
pg_constraint c,
|
pg_constraint c,
|
||||||
pg_class r
|
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)
|
WHERE
|
||||||
/*--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))*/
|
nc.oid = c.connamespace
|
||||||
UNION ALL
|
AND nr.oid = r.relnamespace
|
||||||
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
AND c.conrelid = r.oid
|
||||||
nr.nspname::information_schema.sql_identifier AS constraint_schema,
|
AND r.relkind = 'r'
|
||||||
(((((nr.oid::text || '_'::text) || r.oid::text) || '_'::text) || a.attnum::text) || '_not_null'::text)::information_schema.sql_identifier AS constraint_name,
|
AND NOT pg_is_other_temp_schema(nr.oid)
|
||||||
current_database()::information_schema.sql_identifier AS table_catalog,
|
AND c.contype = 'p'
|
||||||
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))*/
|
|
||||||
),
|
),
|
||||||
/*
|
|
||||||
-- CTE to replace information_schema.key_column_usage to remove owner limit
|
-- CTE to replace information_schema.key_column_usage to remove owner limit
|
||||||
*/
|
|
||||||
kc AS (
|
kc AS (
|
||||||
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
SELECT
|
||||||
ss.nc_nspname::information_schema.sql_identifier AS constraint_schema,
|
ss.conname::name AS constraint_name,
|
||||||
ss.conname::information_schema.sql_identifier AS constraint_name,
|
ss.nr_nspname::name AS table_schema,
|
||||||
current_database()::information_schema.sql_identifier AS table_catalog,
|
ss.relname::name AS table_name,
|
||||||
ss.nr_nspname::information_schema.sql_identifier AS table_schema,
|
a.attname::name AS column_name,
|
||||||
ss.relname::information_schema.sql_identifier AS table_name,
|
(ss.x).n::integer AS ordinal_position,
|
||||||
a.attname::information_schema.sql_identifier AS column_name,
|
CASE
|
||||||
(ss.x).n::information_schema.cardinal_number AS ordinal_position,
|
WHEN ss.contype = 'f' THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n])
|
||||||
CASE
|
ELSE NULL::integer
|
||||||
WHEN ss.contype = 'f'::"char" THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n])
|
END::integer AS position_in_unique_constraint
|
||||||
ELSE NULL::integer
|
|
||||||
END::information_schema.cardinal_number AS position_in_unique_constraint
|
|
||||||
FROM pg_attribute a,
|
FROM pg_attribute a,
|
||||||
( SELECT r.oid AS roid,
|
( SELECT r.oid AS roid,
|
||||||
r.relname,
|
r.relname,
|
||||||
@@ -692,28 +632,31 @@ allPrimaryKeys tabs =
|
|||||||
c.contype,
|
c.contype,
|
||||||
c.conindid,
|
c.conindid,
|
||||||
c.confkey,
|
c.confkey,
|
||||||
c.confrelid,
|
|
||||||
information_schema._pg_expandarray(c.conkey) AS x
|
information_schema._pg_expandarray(c.conkey) AS x
|
||||||
FROM pg_namespace nr,
|
FROM pg_namespace nr,
|
||||||
pg_class r,
|
pg_class r,
|
||||||
pg_namespace nc,
|
pg_namespace nc,
|
||||||
pg_constraint c
|
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
|
||||||
WHERE ss.roid = a.attrelid AND a.attnum = (ss.x).x AND NOT a.attisdropped
|
nr.oid = r.relnamespace
|
||||||
/*--AND (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/
|
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
|
SELECT
|
||||||
kc.table_schema,
|
kc.table_schema,
|
||||||
kc.table_name,
|
kc.table_name,
|
||||||
kc.column_name
|
kc.column_name
|
||||||
FROM
|
FROM
|
||||||
/*
|
|
||||||
--information_schema.table_constraints tc,
|
|
||||||
--information_schema.key_column_usage kc
|
|
||||||
*/
|
|
||||||
tc, kc
|
tc, kc
|
||||||
WHERE
|
WHERE
|
||||||
tc.constraint_type = 'PRIMARY KEY' AND
|
|
||||||
kc.table_name = tc.table_name AND
|
kc.table_name = tc.table_name AND
|
||||||
kc.table_schema = tc.table_schema AND
|
kc.table_schema = tc.table_schema AND
|
||||||
kc.constraint_name = tc.constraint_name AND
|
kc.constraint_name = tc.constraint_name AND
|
||||||
@@ -744,7 +687,7 @@ allSourceColumns cols pgVer =
|
|||||||
from pg_class c
|
from pg_class c
|
||||||
join pg_namespace n on n.oid = c.relnamespace
|
join pg_namespace n on n.oid = c.relnamespace
|
||||||
join pg_rewrite r on r.ev_class = c.oid
|
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(
|
removed_subselects as(
|
||||||
select
|
select
|
||||||
|
|||||||
+12
-20
@@ -23,12 +23,12 @@ import qualified Network.HTTP.Types.Status as HT
|
|||||||
|
|
||||||
import Data.Aeson ((.=))
|
import Data.Aeson ((.=))
|
||||||
import Network.Wai (Response, responseLBS)
|
import Network.Wai (Response, responseLBS)
|
||||||
import Text.Read (readMaybe)
|
|
||||||
|
|
||||||
import Network.HTTP.Types.Header
|
import Network.HTTP.Types.Header
|
||||||
|
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude
|
import Protolude hiding (toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
|
|
||||||
class (JSON.ToJSON a) => PgrstError a where
|
class (JSON.ToJSON a) => PgrstError a where
|
||||||
@@ -223,10 +223,8 @@ data SimpleError
|
|||||||
= GucHeadersError
|
= GucHeadersError
|
||||||
| BinaryFieldError ContentType
|
| BinaryFieldError ContentType
|
||||||
| ConnectionLostError
|
| ConnectionLostError
|
||||||
| PutSingletonError
|
|
||||||
| PutMatchingPkError
|
| PutMatchingPkError
|
||||||
| PutRangeNotAllowedError
|
| PutRangeNotAllowedError
|
||||||
| PutPayloadIncompleteError
|
|
||||||
| JwtTokenMissing
|
| JwtTokenMissing
|
||||||
| JwtTokenInvalid Text
|
| JwtTokenInvalid Text
|
||||||
| SingularityError Integer
|
| SingularityError Integer
|
||||||
@@ -234,17 +232,15 @@ data SimpleError
|
|||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance PgrstError SimpleError where
|
instance PgrstError SimpleError where
|
||||||
status GucHeadersError = HT.status500
|
status GucHeadersError = HT.status500
|
||||||
status (BinaryFieldError _) = HT.status406
|
status (BinaryFieldError _) = HT.status406
|
||||||
status ConnectionLostError = HT.status503
|
status ConnectionLostError = HT.status503
|
||||||
status PutSingletonError = HT.status400
|
status PutMatchingPkError = HT.status400
|
||||||
status PutMatchingPkError = HT.status400
|
status PutRangeNotAllowedError = HT.status400
|
||||||
status PutRangeNotAllowedError = HT.status400
|
status JwtTokenMissing = HT.status500
|
||||||
status PutPayloadIncompleteError = HT.status400
|
status (JwtTokenInvalid _) = HT.unauthorized401
|
||||||
status JwtTokenMissing = HT.status500
|
status (SingularityError _) = HT.status406
|
||||||
status (JwtTokenInvalid _) = HT.unauthorized401
|
status (ContentTypeError _) = HT.status415
|
||||||
status (SingularityError _) = HT.status406
|
|
||||||
status (ContentTypeError _) = HT.status415
|
|
||||||
|
|
||||||
headers (SingularityError _) = [toHeader CTSingularJSON]
|
headers (SingularityError _) = [toHeader CTSingularJSON]
|
||||||
headers (JwtTokenInvalid m) = [toHeader CTApplicationJSON, invalidTokenHeader m]
|
headers (JwtTokenInvalid m) = [toHeader CTApplicationJSON, invalidTokenHeader m]
|
||||||
@@ -258,12 +254,8 @@ instance JSON.ToJSON SimpleError where
|
|||||||
toJSON ConnectionLostError = JSON.object [
|
toJSON ConnectionLostError = JSON.object [
|
||||||
"message" .= ("Database connection lost, retrying the connection." :: Text)]
|
"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 [
|
toJSON PutRangeNotAllowedError = JSON.object [
|
||||||
"message" .= ("Range header and limit/offset querystring parameters are not allowed for PUT" :: Text)]
|
"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 [
|
toJSON PutMatchingPkError = JSON.object [
|
||||||
"message" .= ("Payload values do not match URL in primary key column(s)" :: Text)]
|
"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 :: Text -> Header
|
||||||
invalidTokenHeader m =
|
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 :: (Integral a) => a -> SimpleError
|
||||||
singularityError = SingularityError . toInteger
|
singularityError = SingularityError . toInteger
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import PostgREST.Config (AppConfig (..), corsPolicy)
|
|||||||
import PostgREST.Error (SimpleError (JwtTokenInvalid, JwtTokenMissing),
|
import PostgREST.Error (SimpleError (JwtTokenInvalid, JwtTokenMissing),
|
||||||
errorResponseFor)
|
errorResponseFor)
|
||||||
import PostgREST.QueryBuilder (setLocalQuery, setLocalSearchPathQuery)
|
import PostgREST.QueryBuilder (setLocalQuery, setLocalSearchPathQuery)
|
||||||
import Protolude hiding (head)
|
import Protolude hiding (head, toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
runWithClaims :: AppConfig -> JWTAttempt ->
|
runWithClaims :: AppConfig -> JWTAttempt ->
|
||||||
(ApiRequest -> H.Transaction Response) ->
|
(ApiRequest -> H.Transaction Response) ->
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import PostgREST.Types (Column (..), ForeignKey (..), PgArg (..),
|
|||||||
PrimaryKey (..), ProcDescription (..),
|
PrimaryKey (..), ProcDescription (..),
|
||||||
Proxy (..), Table (..), toMime)
|
Proxy (..), Table (..), toMime)
|
||||||
import Protolude hiding (Proxy, dropWhile, get,
|
import Protolude hiding (Proxy, dropWhile, get,
|
||||||
intercalate, (&))
|
intercalate, toLower, toS, (&))
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
makeMimeList :: [ContentType] -> MimeList
|
makeMimeList :: [ContentType] -> MimeList
|
||||||
makeMimeList cs = MimeList $ map (fromString . toS . toMime) cs
|
makeMimeList cs = MimeList $ map (fromString . toS . toMime) cs
|
||||||
@@ -117,7 +118,7 @@ makeProcParam pd =
|
|||||||
[ Inline $ (mempty :: Param)
|
[ Inline $ (mempty :: Param)
|
||||||
& name .~ "args"
|
& name .~ "args"
|
||||||
& required ?~ True
|
& required ?~ True
|
||||||
& schema .~ (ParamBody $ Inline $ makeProcSchema pd)
|
& schema .~ ParamBody (Inline $ makeProcSchema pd)
|
||||||
, Ref $ Reference "preferParams"
|
, Ref $ Reference "preferParams"
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -221,7 +222,7 @@ makePathItem (t, cs, _) = ("/" ++ unpack tn, p $ tableInsertable t)
|
|||||||
& description .~ "OK"
|
& description .~ "OK"
|
||||||
& schema ?~ Inline (mempty
|
& schema ?~ Inline (mempty
|
||||||
& type_ ?~ SwaggerArray
|
& type_ ?~ SwaggerArray
|
||||||
& items ?~ (SwaggerItemsObject $ Ref $ Reference $ tableName t)
|
& items ?~ SwaggerItemsObject (Ref $ Reference $ tableName t)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
postOp = tOp
|
postOp = tOp
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ module PostgREST.Parsers where
|
|||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
|
||||||
import Control.Monad ((>>))
|
|
||||||
import Data.Either.Combinators (mapLeft)
|
import Data.Either.Combinators (mapLeft)
|
||||||
import Data.Foldable (foldl1)
|
import Data.Foldable (foldl1)
|
||||||
import Data.Functor (($>))
|
|
||||||
import Data.List (init, last)
|
import Data.List (init, last)
|
||||||
import Data.Text (intercalate, replace, strip)
|
import Data.Text (intercalate, replace, strip)
|
||||||
import Text.Read (read)
|
import Text.Read (read)
|
||||||
@@ -24,7 +22,9 @@ import Text.ParserCombinators.Parsec hiding (many, (<|>))
|
|||||||
import PostgREST.Error (ApiRequestError (ParseRequestError))
|
import PostgREST.Error (ApiRequestError (ParseRequestError))
|
||||||
import PostgREST.RangeQuery (NonnegRange)
|
import PostgREST.RangeQuery (NonnegRange)
|
||||||
import PostgREST.Types
|
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 :: Text -> Either ApiRequestError [Tree SelectItem]
|
||||||
pRequestSelect selStr =
|
pRequestSelect selStr =
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import qualified Data.HashMap.Strict as HM
|
|||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Text (intercalate,
|
import Data.Text (intercalate,
|
||||||
isInfixOf, replace,
|
isInfixOf, replace,
|
||||||
toLower, unwords)
|
toLower)
|
||||||
import qualified Data.Text as T (map, null,
|
import qualified Data.Text as T (map, null,
|
||||||
takeWhile)
|
takeWhile)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude hiding (cast,
|
import Protolude hiding (cast,
|
||||||
intercalate, replace)
|
intercalate, replace,
|
||||||
|
toLower)
|
||||||
import Text.InterpolatedString.Perl6 (qc)
|
import Text.InterpolatedString.Perl6 (qc)
|
||||||
|
|
||||||
noLocationF :: SqlFragment
|
noLocationF :: SqlFragment
|
||||||
@@ -186,8 +187,8 @@ countF :: SqlQuery -> Bool -> (SqlFragment, SqlFragment)
|
|||||||
countF countQuery shouldCount =
|
countF countQuery shouldCount =
|
||||||
if shouldCount
|
if shouldCount
|
||||||
then (
|
then (
|
||||||
", pg_source_count AS (" <> countQuery <> ")"
|
", pgrst_source_count AS (" <> countQuery <> ")"
|
||||||
, "(SELECT pg_catalog.count(*) FROM pg_source_count)" )
|
, "(SELECT pg_catalog.count(*) FROM pgrst_source_count)" )
|
||||||
else (
|
else (
|
||||||
mempty
|
mempty
|
||||||
, "null::bigint")
|
, "null::bigint")
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module PostgREST.QueryBuilder (
|
|||||||
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
|
||||||
import Data.Text (intercalate, unwords)
|
import Data.Text (intercalate)
|
||||||
import Data.Tree (Tree (..))
|
import Data.Tree (Tree (..))
|
||||||
|
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
@@ -94,7 +94,10 @@ mutateRequestToQuery (Insert mainQi iCols onConflct putConditions returnings) =
|
|||||||
cols = intercalate ", " $ pgFmtIdent <$> S.toList iCols
|
cols = intercalate ", " $ pgFmtIdent <$> S.toList iCols
|
||||||
mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
||||||
if S.null uCols
|
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
|
else
|
||||||
unwords [
|
unwords [
|
||||||
"WITH " <> normalizedBody,
|
"WITH " <> normalizedBody,
|
||||||
@@ -105,6 +108,10 @@ mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
|||||||
]
|
]
|
||||||
where
|
where
|
||||||
cols = intercalate ", " (pgFmtIdent <> const " = _." <> pgFmtIdent <$> S.toList uCols)
|
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) =
|
mutateRequestToQuery (Delete mainQi logicForest returnings) =
|
||||||
unwords [
|
unwords [
|
||||||
"WITH " <> ignoredBody,
|
"WITH " <> ignoredBody,
|
||||||
@@ -113,15 +120,15 @@ mutateRequestToQuery (Delete mainQi logicForest returnings) =
|
|||||||
returningF mainQi returnings
|
returningF mainQi returnings
|
||||||
]
|
]
|
||||||
|
|
||||||
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> SqlQuery
|
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> [FieldName] -> SqlQuery
|
||||||
requestToCallProcQuery qi pgArgs returnsScalar preferParams =
|
requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
||||||
unwords [
|
unwords [
|
||||||
"WITH",
|
"WITH",
|
||||||
argsCTE,
|
argsCTE,
|
||||||
sourceBody ]
|
sourceBody ]
|
||||||
where
|
where
|
||||||
paramsAsSingleObject = preferParams == Just SingleObject
|
paramsAsSingleObject = preferParams == Just SingleObject
|
||||||
paramsAsMulitpleObjects = preferParams == Just MultipleObjects
|
paramsAsMultipleObjects = preferParams == Just MultipleObjects
|
||||||
|
|
||||||
(argsCTE, args)
|
(argsCTE, args)
|
||||||
| null pgArgs = (ignoredBody, "")
|
| null pgArgs = (ignoredBody, "")
|
||||||
@@ -132,7 +139,7 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams =
|
|||||||
"pgrst_args AS (",
|
"pgrst_args AS (",
|
||||||
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (\a -> " " <> pgaType a) <> ")",
|
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (\a -> " " <> pgaType a) <> ")",
|
||||||
")"]
|
")"]
|
||||||
, if paramsAsMulitpleObjects
|
, if paramsAsMultipleObjects
|
||||||
then fmtArgs (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a))
|
then fmtArgs (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a))
|
||||||
else fmtArgs (\a -> " := (SELECT " <> pgFmtIdent (pgaName a) <> " FROM pgrst_args LIMIT 1)")
|
else fmtArgs (\a -> " := (SELECT " <> pgFmtIdent (pgaName a) <> " FROM pgrst_args LIMIT 1)")
|
||||||
)
|
)
|
||||||
@@ -142,20 +149,25 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams =
|
|||||||
|
|
||||||
sourceBody :: SqlFragment
|
sourceBody :: SqlFragment
|
||||||
sourceBody
|
sourceBody
|
||||||
| paramsAsMulitpleObjects =
|
| paramsAsMultipleObjects =
|
||||||
if returnsScalar
|
if returnsScalar
|
||||||
then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args"
|
then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args"
|
||||||
else unwords [ "SELECT pgrst_lat_args.*"
|
else unwords [ "SELECT pgrst_lat_args.*"
|
||||||
, "FROM pgrst_args,"
|
, "FROM pgrst_args,"
|
||||||
, "LATERAL ( SELECT * FROM " <> callIt <> " ) pgrst_lat_args" ]
|
, "LATERAL ( SELECT " <> returned_columns <> " FROM " <> callIt <> " ) pgrst_lat_args" ]
|
||||||
| otherwise =
|
| otherwise =
|
||||||
if returnsScalar
|
if returnsScalar
|
||||||
then "SELECT " <> callIt <> " AS pgrst_scalar"
|
then "SELECT " <> callIt <> " AS pgrst_scalar"
|
||||||
else "SELECT * FROM " <> callIt
|
else "SELECT " <> returned_columns <> " FROM " <> callIt
|
||||||
|
|
||||||
callIt :: SqlFragment
|
callIt :: SqlFragment
|
||||||
callIt = fromQi qi <> "(" <> args <> ")"
|
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.
|
-- | 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.
|
-- 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.Header
|
||||||
import Network.HTTP.Types.Status
|
import Network.HTTP.Types.Status
|
||||||
|
|
||||||
import Protolude
|
import Protolude hiding (toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
type NonnegRange = Range Integer
|
type NonnegRange = Range Integer
|
||||||
|
|
||||||
@@ -91,9 +92,9 @@ rangeStatusHeader topLevelRange queryTotal tableTotal =
|
|||||||
|
|
||||||
contentRangeH :: (Integral a, Show a) => a -> a -> Maybe a -> Header
|
contentRangeH :: (Integral a, Show a) => a -> a -> Maybe a -> Header
|
||||||
contentRangeH lower upper total =
|
contentRangeH lower upper total =
|
||||||
("Content-Range", headerValue)
|
("Content-Range", toUtf8 headerValue)
|
||||||
where
|
where
|
||||||
headerValue = rangeString <> "/" <> totalString
|
headerValue = rangeString <> "/" <> totalString :: Text
|
||||||
rangeString
|
rangeString
|
||||||
| totalNotZero && fromInRange = show lower <> "-" <> show upper
|
| totalNotZero && fromInRange = show lower <> "-" <> show upper
|
||||||
| otherwise = "*"
|
| otherwise = "*"
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import Data.Aeson as JSON
|
|||||||
import qualified Data.Aeson.Lens as L
|
import qualified Data.Aeson.Lens as L
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Text (unwords)
|
|
||||||
import Data.Text.Encoding (encodeUtf8)
|
|
||||||
import qualified Hasql.Decoders as HD
|
import qualified Hasql.Decoders as HD
|
||||||
import qualified Hasql.Encoders as HE
|
import qualified Hasql.Encoders as HE
|
||||||
import qualified Hasql.Statement as H
|
import qualified Hasql.Statement as H
|
||||||
@@ -31,7 +29,8 @@ import PostgREST.Private.Common
|
|||||||
import PostgREST.Private.QueryFragment
|
import PostgREST.Private.QueryFragment
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude hiding (cast,
|
import Protolude hiding (cast,
|
||||||
replace)
|
replace, toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
import Text.InterpolatedString.Perl6 (qc)
|
import Text.InterpolatedString.Perl6 (qc)
|
||||||
|
|
||||||
{-| The generic query result format used by API responses. The location header
|
{-| 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,
|
{locF} AS header,
|
||||||
{bodyF} AS body,
|
{bodyF} AS body,
|
||||||
{responseHeadersF pgVer} AS response_headers
|
{responseHeadersF pgVer} AS response_headers
|
||||||
FROM ({selectQuery}) _postgrest_t |]
|
FROM ({selectF}) _postgrest_t |]
|
||||||
|
|
||||||
locF =
|
locF =
|
||||||
if isInsert && rep `elem` [Full, HeadersOnly]
|
if isInsert && rep `elem` [Full, HeadersOnly]
|
||||||
@@ -72,6 +71,11 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
|
|||||||
| wantSingle = asJsonSingleF
|
| wantSingle = asJsonSingleF
|
||||||
| otherwise = asJsonF
|
| 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 :: HD.Result ResultsWithCount
|
||||||
decodeStandard =
|
decodeStandard =
|
||||||
fromMaybe (Nothing, 0, [], mempty, Right []) <$> HD.rowMaybe standardRow
|
fromMaybe (Nothing, 0, [], mempty, Right []) <$> HD.rowMaybe standardRow
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import Network.HTTP.Types.Header (Header, hContentType)
|
|||||||
import Data.Tree
|
import Data.Tree
|
||||||
|
|
||||||
import PostgREST.RangeQuery (NonnegRange)
|
import PostgREST.RangeQuery (NonnegRange)
|
||||||
import Protolude
|
import Protolude hiding (toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
-- | Enumeration of currently supported response content types
|
-- | Enumeration of currently supported response content types
|
||||||
data ContentType = CTApplicationJSON | CTSingularJSON
|
data ContentType = CTApplicationJSON | CTSingularJSON
|
||||||
@@ -308,9 +309,8 @@ data PayloadJSON =
|
|||||||
ProcessedJSON {
|
ProcessedJSON {
|
||||||
-- | This is the raw ByteString that comes from the request body.
|
-- | 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
|
-- 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
|
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
|
-- | 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
|
, pjKeys :: S.Set Text
|
||||||
}|
|
}|
|
||||||
@@ -522,7 +522,7 @@ pgVersion121 :: PgVersion
|
|||||||
pgVersion121 = PgVersion 120001 "12.1"
|
pgVersion121 = PgVersion 120001 "12.1"
|
||||||
|
|
||||||
sourceCTEName :: SqlFragment
|
sourceCTEName :: SqlFragment
|
||||||
sourceCTEName = "pg_source"
|
sourceCTEName = "pgrst_source"
|
||||||
|
|
||||||
-- | full jspath, e.g. .property[0].attr.detail
|
-- | full jspath, e.g. .property[0].attr.detail
|
||||||
type JSPath = [JSPathExp]
|
type JSPath = [JSPathExp]
|
||||||
|
|||||||
+7
-16
@@ -1,20 +1,11 @@
|
|||||||
# stack is used for circleci and appveyor CI builds
|
# stack is used for circleci and appveyor CI builds
|
||||||
resolver: lts-14.3
|
resolver: lts-15.8
|
||||||
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
|
|
||||||
nix:
|
nix:
|
||||||
packages: [pcre, pkgconfig, postgresql, zlib]
|
packages: [pcre, pkgconfig, postgresql, zlib]
|
||||||
|
# disable pure by default so that the test enviroment can be passed
|
||||||
# needed by stylish haskell, this only runs on ci
|
pure: false
|
||||||
extra-deps:
|
extra-deps:
|
||||||
- HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311
|
- configurator-pg-0.2.2@sha256:8f3f7c66dd4a53852283c70806ebc101a7219f8b01c3e80c5130e9749c998dc8,2987
|
||||||
- HsYAML-aeson-0.2.0.0@sha256:04796abfc01cffded83f37a10e6edba4f0c0a15d45bef44fc5bb4313d9c87757,1791
|
- hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
||||||
- configurator-pg-0.2.0@sha256:08dcfadbe31e9e505d0bed1ab034105e1735141499734ad52d1c5ac980cde4a6,2939
|
- interpolatedstring-perl6-1.0.2@sha256:7ce49c8a69a2a1b89c001ed79db2aab656ffd0faf2a7a701a553b6deb5c8ba7f,1073
|
||||||
- hspec-wai-0.10.1@sha256:56dd9ec1d56f47ef1946f71f7cbf070e4c285f718cac1b158400ae5e7172ef47,2290
|
- protolude-0.3.0@sha256:8361b811b420585b122a7ba715aa5923834db6e8c36309bf267df2dbf66b95ef,2693
|
||||||
- hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
|
||||||
|
|||||||
+22
-29
@@ -5,33 +5,12 @@
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
- completed:
|
- completed:
|
||||||
hackage: HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311
|
hackage: configurator-pg-0.2.2@sha256:8f3f7c66dd4a53852283c70806ebc101a7219f8b01c3e80c5130e9749c998dc8,2987
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
size: 1340
|
size: 2462
|
||||||
sha256: 21f61bf9cad31674126b106071dd9b852e408796aeffc90eec1792f784107eff
|
sha256: a669366632e19f396dfe52599d54414b9c92d5664ea4391c2c797be271f9aa0e
|
||||||
original:
|
original:
|
||||||
hackage: HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311
|
hackage: configurator-pg-0.2.2@sha256:8f3f7c66dd4a53852283c70806ebc101a7219f8b01c3e80c5130e9749c998dc8,2987
|
||||||
- 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
|
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
hackage: hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
@@ -39,9 +18,23 @@ packages:
|
|||||||
sha256: fb9e89b79cde3276baa484c860c6b9eeebdbc1a5c43301293351a25bc4c08e87
|
sha256: fb9e89b79cde3276baa484c860c6b9eeebdbc1a5c43301293351a25bc4c08e87
|
||||||
original:
|
original:
|
||||||
hackage: hspec-wai-json-0.10.1@sha256:67b405c38f0a9e2771480c8d3ecd8aeb8d8776a35d3b2906cb1b76c9538617e4,1629
|
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:
|
snapshots:
|
||||||
- completed:
|
- completed:
|
||||||
size: 523878
|
size: 492015
|
||||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/3.yaml
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/15/8.yaml
|
||||||
sha256: 470c46c27746a48c7c50f829efc0cf00112787a7804ee4ac7a27754658f6d92c
|
sha256: 926bc3d70249dd0ba05277ff00943c0addb35b627cb641752669e7cf771310d0
|
||||||
original: lts-14.3
|
original: lts-15.8
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
module Feature.ConcurrentSpec where
|
module Feature.ConcurrentSpec where
|
||||||
|
|
||||||
import Control.Concurrent.Async (mapConcurrently)
|
import Control.Concurrent.Async (mapConcurrently)
|
||||||
import Control.Monad (void)
|
|
||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
|
|
||||||
import Control.Monad.Base
|
import Control.Monad.Base
|
||||||
|
|||||||
@@ -27,15 +27,30 @@ spec =
|
|||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
, matchHeaders = ["Content-Range" <:> "*/1"]
|
, 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" $
|
it "returns the deleted item and shapes the response" $
|
||||||
request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] ""
|
request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] ""
|
||||||
`shouldRespondWith` [str|[{"id":2,"name":"Two"}]|]
|
`shouldRespondWith` [str|[{"id":2,"name":"Two"}]|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
, matchHeaders = ["Content-Range" <:> "*/*"]
|
, matchHeaders = ["Content-Range" <:> "*/*"]
|
||||||
}
|
}
|
||||||
|
|
||||||
it "can rename and cast the selected columns" $
|
it "can rename and cast the selected columns" $
|
||||||
request methodDelete "/complex_items?id=eq.3&select=ciId:id::text,ciName:name" [("Prefer", "return=representation")] ""
|
request methodDelete "/complex_items?id=eq.3&select=ciId:id::text,ciName:name" [("Prefer", "return=representation")] ""
|
||||||
`shouldRespondWith` [str|[{"ciId":"3","ciName":"Three"}]|]
|
`shouldRespondWith` [str|[{"ciId":"3","ciName":"Three"}]|]
|
||||||
|
|
||||||
it "can embed (parent) entities" $
|
it "can embed (parent) entities" $
|
||||||
request methodDelete "/tasks?id=eq.8&select=id,name,project:projects(id)" [("Prefer", "return=representation")] ""
|
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}}]|]
|
`shouldRespondWith` [str|[{"id":8,"name":"Code OSX","project":{"id":4}}]|]
|
||||||
|
|||||||
@@ -324,20 +324,20 @@ spec =
|
|||||||
]|]
|
]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embeds childs recursively" $
|
it "embeds children recursively" $
|
||||||
get "/family_tree?id=eq.1&select=id,name, childs:family_tree!parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
get "/family_tree?id=eq.1&select=id,name, children:family_tree!parent(id,name,children:family_tree!parent(id,name))" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": "1", "name": "Parental Unit", "childs": [
|
"id": "1", "name": "Parental Unit", "children": [
|
||||||
{ "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] },
|
{ "id": "2", "name": "Kid One", "children": [ { "id": "4", "name": "Grandkid One" } ] },
|
||||||
{ "id": "3", "name": "Kid Two", "childs": [ { "id": "5", "name": "Grandkid Two" } ] }
|
{ "id": "3", "name": "Kid Two", "children": [ { "id": "5", "name": "Grandkid Two" } ] }
|
||||||
]
|
]
|
||||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embeds parent and then embeds childs" $
|
it "embeds parent and then embeds children" $
|
||||||
get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
get "/family_tree?id=eq.2&select=id,name,parent(id,name,children:family_tree!parent(id,name))" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": "2", "name": "Kid One", "parent": {
|
"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] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
@@ -356,7 +356,7 @@ spec =
|
|||||||
}
|
}
|
||||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embeds childs" $ do
|
it "embeds children" $ do
|
||||||
get "/organizations?select=id,name,refereeds:organizations!referee(id,name)&id=eq.1" `shouldRespondWith`
|
get "/organizations?select=id,name,refereeds:organizations!referee(id,name)&id=eq.1" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": 1, "name": "Referee Org",
|
"id": 1, "name": "Referee Org",
|
||||||
|
|||||||
+167
-35
@@ -2,7 +2,6 @@ module Feature.InsertSpec where
|
|||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
|
|
||||||
import Control.Monad (replicateM_, void)
|
|
||||||
import Data.List (lookup)
|
import Data.List (lookup)
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
@@ -48,6 +47,22 @@ spec actualPgVersion = do
|
|||||||
, matchHeaders = [matchContentTypeJson]
|
, 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
|
context "non uniform json array" $ do
|
||||||
it "rejects json array that isn't exclusivily composed of objects" $
|
it "rejects json array that isn't exclusivily composed of objects" $
|
||||||
post "/articles"
|
post "/articles"
|
||||||
@@ -189,7 +204,16 @@ spec actualPgVersion = do
|
|||||||
it "fails with 400 and error" $
|
it "fails with 400 and error" $
|
||||||
post "/simple_pk" "}{ x = 2"
|
post "/simple_pk" "}{ x = 2"
|
||||||
`shouldRespondWith`
|
`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
|
{ matchStatus = 400
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
}
|
}
|
||||||
@@ -251,6 +275,20 @@ spec actualPgVersion = do
|
|||||||
, matchHeaders = []
|
, 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
|
context "POST with ?columns parameter" $ do
|
||||||
it "ignores json keys not included in ?columns" $ do
|
it "ignores json keys not included in ?columns" $ do
|
||||||
request methodPost "/articles?columns=id,body" [("Prefer", "return=representation")]
|
request methodPost "/articles?columns=id,body" [("Prefer", "return=representation")]
|
||||||
@@ -383,6 +421,24 @@ spec actualPgVersion = do
|
|||||||
matchHeaders = []
|
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
|
context "in a nonempty table" $ do
|
||||||
it "can update a single item" $ do
|
it "can update a single item" $ do
|
||||||
g <- get "/items?id=eq.42"
|
g <- get "/items?id=eq.42"
|
||||||
@@ -464,47 +520,123 @@ spec actualPgVersion = do
|
|||||||
matchHeaders = []
|
matchHeaders = []
|
||||||
}
|
}
|
||||||
|
|
||||||
it "can provide a representation" $ do
|
context "with representation requested" $ do
|
||||||
_ <- post "/items"
|
it "can provide a representation" $ do
|
||||||
[json| { id: 1 } |]
|
_ <- post "/items"
|
||||||
request methodPatch
|
[json| { id: 1 } |]
|
||||||
"/items?id=eq.1"
|
request methodPatch
|
||||||
[("Prefer", "return=representation")]
|
"/items?id=eq.1"
|
||||||
[json| { id: 99 } |]
|
[("Prefer", "return=representation")]
|
||||||
`shouldRespondWith` [json| [{id:99}] |]
|
[json| { id: 99 } |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
`shouldRespondWith` [json| [{id:99}] |]
|
||||||
-- put value back for other tests
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
void $ request methodPatch "/items?id=eq.99" [] [json| { "id":1 } |]
|
-- 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
|
it "can return computed columns" $
|
||||||
request methodPatch "/items" [] [json| {} |]
|
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` ""
|
`shouldRespondWith` ""
|
||||||
{
|
{
|
||||||
matchStatus = 204,
|
matchStatus = 204,
|
||||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
matchHeaders = ["Content-Range" <:> "0-0/*"]
|
||||||
}
|
}
|
||||||
|
request methodPatch "/items?id=eq.1&select=id" [("Prefer", "return=minimal")] [json| { id:1 } |]
|
||||||
request methodPatch "/items" [] [json| [] |]
|
|
||||||
`shouldRespondWith` ""
|
`shouldRespondWith` ""
|
||||||
{
|
{
|
||||||
matchStatus = 204,
|
matchStatus = 204,
|
||||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
matchHeaders = ["Content-Range" <:> "0-0/*"]
|
||||||
}
|
}
|
||||||
|
|
||||||
request methodPatch "/items" [] [json| [{}] |]
|
context "when patching with an empty body" $ do
|
||||||
`shouldRespondWith` ""
|
it "makes no updates and returns 204 without return= and without ?select=" $ do
|
||||||
{
|
request methodPatch "/items" [] [json| {} |]
|
||||||
matchStatus = 204,
|
`shouldRespondWith` ""
|
||||||
matchHeaders = ["Content-Range" <:> "*/*"]
|
{
|
||||||
}
|
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" [] [json| [] |]
|
||||||
request methodPatch "/items" [("Prefer", "return=representation")] [json| {} |]
|
`shouldRespondWith` ""
|
||||||
`shouldRespondWith` "[]"
|
{
|
||||||
{
|
matchStatus = 204,
|
||||||
matchStatus = 200,
|
matchHeaders = ["Content-Range" <:> "*/*"]
|
||||||
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" $
|
context "with unicode values" $
|
||||||
it "succeeds and returns values intact" $ do
|
it "succeeds and returns values intact" $ do
|
||||||
@@ -561,7 +693,7 @@ spec actualPgVersion = do
|
|||||||
, matchHeaders = [ matchContentTypeJson , "Location" <:> "/web_content?id=eq.6" ]
|
, 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)"
|
request methodPatch "/web_content?id=eq.0&select=id,name,web_content(name)"
|
||||||
[("Prefer", "return=representation")]
|
[("Prefer", "return=representation")]
|
||||||
[json|{"name": "tardis-patched"}|]
|
[json|{"name": "tardis-patched"}|]
|
||||||
@@ -573,7 +705,7 @@ spec actualPgVersion = do
|
|||||||
matchHeaders = [matchContentTypeJson]
|
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)"
|
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")]
|
[("Prefer", "return=representation")]
|
||||||
[json|{"name": "tardis-patched-2"}|]
|
[json|{"name": "tardis-patched-2"}|]
|
||||||
@@ -594,7 +726,7 @@ spec actualPgVersion = do
|
|||||||
matchHeaders = [matchContentTypeJson]
|
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)"
|
request methodPatch "/web_content?id=eq.0&select=name,web_content(name)"
|
||||||
[("Prefer", "return=representation")]
|
[("Prefer", "return=representation")]
|
||||||
[json|{"name": "tardis-patched"}|]
|
[json|{"name": "tardis-patched"}|]
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ spec actualPgVersion =
|
|||||||
|
|
||||||
context "Inserting tables on different schemas" $ do
|
context "Inserting tables on different schemas" $ do
|
||||||
it "succeeds inserting on default schema and returning it" $
|
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`
|
`shouldRespondWith`
|
||||||
[json|[{"id":1, "name": "child v1-1", "parent_id": 1}]|]
|
[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" $
|
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}|]
|
[json|{"name": "child v1-2", "parent_id": 2}|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[{"id":2, "parent": {"id": 2, "name": "parent v1-2"}}]|]
|
[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" $
|
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}|]
|
[json|{"name": "child v2-3", "parent_id": 3}|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[{"id":1, "parent": {"id": 3, "name": "parent v2-3"}}]|]
|
[json|[{"id":1, "parent": {"id": 3, "name": "parent v2-3"}}]|]
|
||||||
@@ -106,7 +106,7 @@ spec actualPgVersion =
|
|||||||
}
|
}
|
||||||
|
|
||||||
it "fails when inserting on an unknown schema" $
|
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}|]
|
[json|{"name": "child 4", "parent_id": 4}|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|{"message":"The schema must be one of the following: v1, v2"}|]
|
[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" $
|
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`
|
`shouldRespondWith`
|
||||||
[json| [
|
[json| [
|
||||||
{"id":1,"name":"parent v1-1","childs":[{"id":1,"name":"child v1-1"}]},
|
{"id":1,"name":"parent v1-1","children":[{"id":1,"name":"child v1-1"}]},
|
||||||
{"id":2,"name":"parent v1-2","childs":[{"id":2,"name":"child v1-2"}]}] |]
|
{"id":2,"name":"parent v1-2","children":[{"id":2,"name":"child v1-2"}]}] |]
|
||||||
{
|
{
|
||||||
matchStatus = 200
|
matchStatus = 200
|
||||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v1"]
|
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v1"]
|
||||||
}
|
}
|
||||||
|
|
||||||
it "succeeds in calling the v2 schema proc and embedding" $
|
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`
|
`shouldRespondWith`
|
||||||
[json| [
|
[json| [
|
||||||
{"id":3,"name":"parent v2-3","childs":[{"id":1,"name":"child v2-3"}]},
|
{"id":3,"name":"parent v2-3","children":[{"id":1,"name":"child v2-3"}]},
|
||||||
{"id":4,"name":"parent v2-4","childs":[]}] |]
|
{"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
|
matchStatus = 200
|
||||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
||||||
@@ -148,7 +160,7 @@ spec actualPgVersion =
|
|||||||
|
|
||||||
context "Modifying tables on different schemas" $ do
|
context "Modifying tables on different schemas" $ do
|
||||||
it "succeeds in patching on the v1 schema and returning its parent" $
|
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"}|]
|
[json|{"name": "child v1-1 updated"}|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[{"name":"child v1-1 updated", "parent": {"name": "parent v1-1"}}]|]
|
[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" $
|
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"}|]
|
[json|{"name": "child v2-1 updated"}|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[{"name":"child v2-1 updated", "parent": {"name": "parent v2-3"}}]|]
|
[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
|
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}]|]
|
`shouldRespondWith` [json|[{"id": 1, "name": "child v2-1 updated", "parent_id": 3}]|]
|
||||||
{
|
{
|
||||||
matchStatus = 200
|
matchStatus = 200
|
||||||
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
, matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"]
|
||||||
}
|
}
|
||||||
request methodGet "/childs?id=eq.1" [("Accept-Profile", "v2")] ""
|
request methodGet "/children?id=eq.1" [("Accept-Profile", "v2")] ""
|
||||||
`shouldRespondWith` "[]"
|
`shouldRespondWith` "[]"
|
||||||
{
|
{
|
||||||
matchStatus = 200
|
matchStatus = 200
|
||||||
@@ -183,7 +195,7 @@ spec actualPgVersion =
|
|||||||
|
|
||||||
when (actualPgVersion >= pgVersion96) $
|
when (actualPgVersion >= pgVersion96) $
|
||||||
it "succeeds on PUT on the v2 schema" $
|
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 } ]|]
|
[json| [ { "id": 111, "name": "child v2-111", "parent_id": null } ]|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[{ "id": 111, "name": "child v2-111", "parent_id": null }]|]
|
[json|[{ "id": 111, "name": "child v2-111", "parent_id": null }]|]
|
||||||
|
|||||||
+33
-10
@@ -237,7 +237,6 @@ spec actualPgVersion = do
|
|||||||
[json| [{"myId":1}] |]
|
[json| [{"myId":1}] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
|
||||||
it "one simple column with casting (text)" $
|
it "one simple column with casting (text)" $
|
||||||
get "/complex_items?select=id::text" `shouldRespondWith`
|
get "/complex_items?select=id::text" `shouldRespondWith`
|
||||||
[json| [{"id":"1"},{"id":"2"},{"id":"3"}] |]
|
[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"}]}]|]
|
[json|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ 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
|
describe "view embedding" $ do
|
||||||
it "can detect fk relations through views to tables in the public schema" $
|
it "can detect fk relations through views to tables in the public schema" $
|
||||||
get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200
|
get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200
|
||||||
@@ -482,18 +505,18 @@ spec actualPgVersion = do
|
|||||||
"designTasks":[ ] }
|
"designTasks":[ ] }
|
||||||
]|] { matchHeaders = [matchContentTypeJson] }
|
]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "works with two aliased childs embeds plus and/or" $
|
it "works with two aliased children 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`
|
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|[
|
[json|[
|
||||||
{ "id":1,
|
{ "id":1,
|
||||||
"childs":[
|
"children":[
|
||||||
{"id":1,"gChilds":[{"id":1}, {"id":2}]},
|
{"id":1,"gChildren":[{"id":1}, {"id":2}]},
|
||||||
{"id":2,"gChilds":[]}]},
|
{"id":2,"gChildren":[]}]},
|
||||||
{ "id":2,
|
{ "id":2,
|
||||||
"childs":[
|
"children":[
|
||||||
{"id":3,"gChilds":[]}]},
|
{"id":3,"gChildren":[]}]},
|
||||||
{ "id":3,"childs":[]},
|
{ "id":3,"children":[]},
|
||||||
{ "id":4,"childs":[]}
|
{ "id":4,"children":[]}
|
||||||
]|] { matchHeaders = [matchContentTypeJson] }
|
]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
describe "ordering response" $ do
|
describe "ordering response" $ do
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
module Feature.UnicodeSpec where
|
module Feature.UnicodeSpec where
|
||||||
|
|
||||||
import Control.Monad (void)
|
import Network.Wai (Application)
|
||||||
import Network.Wai (Application)
|
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
|
|||||||
@@ -180,26 +180,6 @@ spec =
|
|||||||
[json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|]
|
[json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|]
|
||||||
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
|
{ 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
|
it "rejects every other filter than pk cols eq's" $ do
|
||||||
put "/tiobe_pls?rank=eq.19"
|
put "/tiobe_pls?rank=eq.19"
|
||||||
[str| [ { "name": "Go", "rank": 19 } ]|]
|
[str| [ { "name": "Go", "rank": 19 } ]|]
|
||||||
@@ -280,6 +260,14 @@ spec =
|
|||||||
put "/tiobe_pls?name=eq.Go" [str| [ { "name": "Go", "rank": 13 } ]|] `shouldRespondWith` 204
|
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] }
|
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
|
it "succeeds on table with composite pk" $ do
|
||||||
get "/employees?first_name=eq.Susan&last_name=eq.Heidt"
|
get "/employees?first_name=eq.Susan&last_name=eq.Heidt"
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,8 @@ import PostgREST.App (postgrest)
|
|||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
||||||
import PostgREST.Types (pgVersion95, pgVersion96)
|
import PostgREST.Types (pgVersion95, pgVersion96)
|
||||||
import Protolude hiding (toList)
|
import Protolude hiding (toList, toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
import qualified Feature.AndOrParamsSpec
|
import qualified Feature.AndOrParamsSpec
|
||||||
@@ -55,7 +56,6 @@ main = do
|
|||||||
getTime <- mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
getTime <- mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||||
|
|
||||||
testDbConn <- getEnvVarWithDefault "POSTGREST_TEST_CONNECTION" "postgres://postgrest_test@localhost/postgrest_test"
|
testDbConn <- getEnvVarWithDefault "POSTGREST_TEST_CONNECTION" "postgres://postgrest_test@localhost/postgrest_test"
|
||||||
setupDb testDbConn
|
|
||||||
|
|
||||||
pool <- P.acquire (3, 10, toS 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 qualified Hasql.Transaction.Sessions as HT
|
||||||
import Text.Heredoc
|
import Text.Heredoc
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get, toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
import PostgREST.QueryBuilder (requestToCallProcQuery)
|
import PostgREST.QueryBuilder (requestToCallProcQuery)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
@@ -22,40 +23,39 @@ import Test.Hspec
|
|||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
testDbConn <- getEnvVarWithDefault "POSTGREST_TEST_CONNECTION" "postgres://postgrest_test@localhost/postgrest_test"
|
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)
|
pool <- P.acquire (3, 10, toS testDbConn)
|
||||||
|
|
||||||
hspec $ describe "QueryCost" $
|
hspec $ describe "QueryCost" $
|
||||||
context "call proc query" $ do
|
context "call proc query" $ do
|
||||||
it "should not exceed cost when calling setof composite proc" $ do
|
it "should not exceed cost when calling setof composite proc" $ do
|
||||||
cost <- exec pool [str| {"id": 3} |] $
|
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 $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 40)
|
cost `shouldSatisfy` (< Just 40)
|
||||||
|
|
||||||
it "should not exceed cost when calling setof composite proc with empty params" $ do
|
it "should not exceed cost when calling setof composite proc with empty params" $ do
|
||||||
cost <- exec pool mempty $
|
cost <- exec pool mempty $
|
||||||
requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] False Nothing
|
requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] False Nothing []
|
||||||
liftIO $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 30)
|
cost `shouldSatisfy` (< Just 30)
|
||||||
|
|
||||||
it "should not exceed cost when calling scalar proc" $ do
|
it "should not exceed cost when calling scalar proc" $ do
|
||||||
cost <- exec pool [str| {"a": 3, "b": 4} |] $
|
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 $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 10)
|
cost `shouldSatisfy` (< Just 10)
|
||||||
|
|
||||||
context "params=multiple-objects" $ do
|
context "params=multiple-objects" $ do
|
||||||
it "should not exceed cost when calling setof composite proc" $ do
|
it "should not exceed cost when calling setof composite proc" $ do
|
||||||
cost <- exec pool [str| [{"id": 1}, {"id": 4}] |] $
|
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
|
liftIO $ do
|
||||||
cost `shouldSatisfy` (> Just 2000)
|
cost `shouldSatisfy` (> Just 2000)
|
||||||
cost `shouldSatisfy` (< Just 2100)
|
cost `shouldSatisfy` (< Just 2100)
|
||||||
|
|
||||||
it "should not exceed cost when calling scalar proc" $ do
|
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}] |] $
|
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 $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 10)
|
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 Data.Set as S
|
||||||
import qualified System.IO.Error as E
|
import qualified System.IO.Error as E
|
||||||
|
|
||||||
import Control.Monad (void)
|
|
||||||
import Data.Aeson (Value (..), decode, encode)
|
import Data.Aeson (Value (..), decode, encode)
|
||||||
import Data.CaseInsensitive (CI (..))
|
import Data.CaseInsensitive (CI (..))
|
||||||
import Data.List (lookup)
|
import Data.List (lookup)
|
||||||
@@ -25,7 +24,8 @@ import Text.Heredoc
|
|||||||
|
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.Types (JSPathExp (..))
|
import PostgREST.Types (JSPathExp (..))
|
||||||
import Protolude
|
import Protolude hiding (toS)
|
||||||
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
matchContentTypeJson :: MatchHeader
|
matchContentTypeJson :: MatchHeader
|
||||||
matchContentTypeJson = "Content-Type" <:> "application/json; charset=utf-8"
|
matchContentTypeJson = "Content-Type" <:> "application/json; charset=utf-8"
|
||||||
@@ -145,16 +145,6 @@ testCfgResponseHeaders testDbConn = (testCfg testDbConn) { configReqCheck = Just
|
|||||||
testMultipleSchemaCfg :: Text -> AppConfig
|
testMultipleSchemaCfg :: Text -> AppConfig
|
||||||
testMultipleSchemaCfg testDbConn = (testCfg testDbConn) { configSchemas = fromList ["v1", "v2"] }
|
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 :: Text -> IO ()
|
||||||
resetDb dbConn = loadFixture dbConn "data"
|
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_MONETARY = 'POSIX';
|
||||||
ALTER DATABASE $DB SET LC_NUMERIC = 'POSIX';
|
ALTER DATABASE $DB SET LC_NUMERIC = 'POSIX';
|
||||||
ALTER DATABASE $DB SET LC_TIME = 'POSIX';
|
ALTER DATABASE $DB SET LC_TIME = 'POSIX';
|
||||||
|
|
||||||
|
\i $BASEPATH/fixtures/load.sql
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
#Remove database path from the connection uri--prevents setting up the new database name with PGDATABASE
|
#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"
|
command: bash -c "POSTGREST_TEST_CONNECTION=$$(test/create_test_db 'postgres://postgres:postgres@postgres' test_db) stack test"
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:11
|
image: postgres:12.2
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Vendored
+29
@@ -207,6 +207,35 @@ INSERT INTO items VALUES (15);
|
|||||||
SELECT pg_catalog.setval('items_id_seq', 15, true);
|
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: -
|
-- 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.
|
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
|
BEGIN
|
||||||
IF type = 'integer' THEN
|
IF type = 'integer' THEN
|
||||||
IF jsonb_typeof(data) != 'number' THEN
|
IF jsonb_typeof(data) != 'number' THEN
|
||||||
|
|||||||
Vendored
+3
@@ -1,5 +1,8 @@
|
|||||||
-- From michelp/pgjwt commit c02bbd3
|
-- From michelp/pgjwt commit c02bbd3
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
|
set search_path to public;
|
||||||
|
|
||||||
set client_min_messages to warning;
|
set client_min_messages to warning;
|
||||||
DROP SCHEMA IF EXISTS jwt CASCADE;
|
DROP SCHEMA IF EXISTS jwt CASCADE;
|
||||||
CREATE SCHEMA jwt;
|
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
|
GRANT ALL ON TABLE
|
||||||
items
|
items
|
||||||
|
, items2
|
||||||
, "articleStars"
|
, "articleStars"
|
||||||
, articles
|
, articles
|
||||||
, auto_incrementing_pk
|
, auto_incrementing_pk
|
||||||
@@ -127,8 +128,8 @@ GRANT ALL ON TABLE
|
|||||||
, v1.parents
|
, v1.parents
|
||||||
, v2.parents
|
, v2.parents
|
||||||
, v2.another_table
|
, v2.another_table
|
||||||
, v1.childs
|
, v1.children
|
||||||
, v2.childs
|
, v2.children
|
||||||
TO postgrest_test_anonymous;
|
TO postgrest_test_anonymous;
|
||||||
|
|
||||||
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
||||||
@@ -138,8 +139,8 @@ GRANT USAGE ON SEQUENCE
|
|||||||
, items_id_seq
|
, items_id_seq
|
||||||
, callcounter_count
|
, callcounter_count
|
||||||
, leak_id_seq
|
, leak_id_seq
|
||||||
, v1.childs_id_seq
|
, v1.children_id_seq
|
||||||
, v2.childs_id_seq
|
, v2.children_id_seq
|
||||||
TO postgrest_test_anonymous;
|
TO postgrest_test_anonymous;
|
||||||
|
|
||||||
-- Privileges for non anonymous users
|
-- Privileges for non anonymous users
|
||||||
|
|||||||
Vendored
+20
-2
@@ -109,10 +109,28 @@ CREATE TABLE items (
|
|||||||
id bigserial primary key
|
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
|
CREATE FUNCTION always_true(test.items) RETURNS boolean
|
||||||
LANGUAGE sql STABLE
|
LANGUAGE sql STABLE
|
||||||
AS $$ SELECT true $$;
|
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
|
CREATE FUNCTION is_first(test.items) RETURNS boolean
|
||||||
LANGUAGE sql STABLE
|
LANGUAGE sql STABLE
|
||||||
AS $$ SELECT $1.id = 1 $$;
|
AS $$ SELECT $1.id = 1 $$;
|
||||||
@@ -1702,7 +1720,7 @@ create table v1.parents (
|
|||||||
, name text
|
, name text
|
||||||
);
|
);
|
||||||
|
|
||||||
create table v1.childs (
|
create table v1.children (
|
||||||
id serial primary key
|
id serial primary key
|
||||||
, name text
|
, name text
|
||||||
, parent_id int
|
, parent_id int
|
||||||
@@ -1720,7 +1738,7 @@ create table v2.parents (
|
|||||||
, name text
|
, name text
|
||||||
);
|
);
|
||||||
|
|
||||||
create table v2.childs (
|
create table v2.children (
|
||||||
id serial primary key
|
id serial primary key
|
||||||
, name text
|
, name text
|
||||||
, parent_id int
|
, 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