Compare commits

..
45 Commits
Author SHA1 Message Date
steve-chavez d6050c8615 Bump to version v7.0.1 2020-05-18 13:05:09 -05:00
steve-chavez 7ffac522e3 circleci: remove linux distro releases
Prefer the linux static binary for x64
2020-05-18 13:05:09 -05:00
steve-chavez 6c4208d9e7 cirrusci: add binary artifact 2020-05-18 13:05:09 -05:00
steve-chavez 98bf4d861d circleci: add static bin to release 2020-05-18 13:05:09 -05:00
Remo RechkemmerandGitHub bfcd289855 Build Docker image from Nix (#1522)
* Static docker image

* added tags for latest and with version

* add postgrest repository to docker image name

* set creation date on nix docker images
2020-05-16 11:35:32 -05:00
Remo RechkemmerandGitHub 7c0fbf9b3f Add static build with Nix (fixes #1295) (#1494)
* Include modifications to nixpkgs and static-haskell-nix as patches.

* use the patched static-haskell-nix version

* Add cachix postgrest
2020-05-12 13:31:37 -05:00
steve-chavez 6fae07241f Add FreeBSD CI 2020-05-08 15:11:44 -05:00
steve-chavez a4f687fdfd appveyor: specify windows image 2020-05-08 15:11:44 -05:00
steve-chavez b1a101c253 Update BACKERS.md 2020-05-04 12:17:52 -05:00
steve-chavez 10c363b588 Remove PUT restriction for all columns/single row
Fixes https://github.com/PostgREST/postgrest/issues/1452.

The single row restriction can be lifted because the
PUT will consider only the first object of the array.
2020-05-02 11:51:32 -05:00
steve-chavez 9a52632024 Fix Content-Profile not working for POST RPC
Accept-Profile was applied instead.

Fixes https://github.com/PostgREST/postgrest/issues/1508.
2020-05-02 11:51:32 -05:00
steve-chavez f57caf0987 Readd openapi-server-proxy-uri config option
This option accidentaly got lost during a merge.

Fixes https://github.com/PostgREST/postgrest/issues/1500
2020-05-02 11:51:32 -05:00
RemoandGitHub f02904a959 Simplify the DbStructure queries (#1478)
* remove unused parts of the DbStructure queries

* explain magic numbers in DbStructure queries

* simplify primary keys query
2020-04-29 13:01:46 -05:00
RemoandGitHub 8b41b71db7 More consistent loading of the test fixtures (#1506)
Remove db setup from the Haskell test suite and include it in the test bash scripts.
2020-04-29 12:22:15 -05:00
RemoandGitHub d4cf8e7abb Hook the Nix styling scripts into CI (#1504) 2020-04-29 10:44:11 -05:00
RemoandGitHub 9134171b95 Fix the cachix push logic in CircleCI
Before pushing to cachix, check if the CACHIX_SIGNING_KEY env var is available.
2020-04-28 14:04:06 -05:00
RemoandGitHub 328c3453f8 Add tips and tricks to using nix-shell (#1505) 2020-04-28 12:45:29 -05:00
Robert VollmertandSteve Chavez bb27eb57a9 Allow base64-bytestring 1.2. 2020-04-28 11:06:05 -05:00
RemoandGitHub dbc3aa28c4 Add scripts for linting and styling with all dependencies managed by Nix (#1501)
* Integrate the style and lint dependencies with Nix

* Add documentation on the nix styling scripts.
2020-04-27 15:04:50 -05:00
RemoandGitHub ad92c207f2 Run the spec test suite from the nix build job (#1503)
* Add caching to the tests run in CI with Nix
2020-04-27 13:39:57 -05:00
RemoandSteve Chavez dea6c5eb92 Remove ghc options from stack.yaml
They are redundant after being added to postgrest.cabal in #1489
2020-04-27 12:41:55 -05:00
RobertandGitHub 3baefa1d96 Relax dependencies to build with GHC 8.10 (base, swagger2) (#1502)
* Allow swagger 2.7.
* Allow base-4.14 (GHC 8.10).
2020-04-25 13:35:01 -05:00
RemoandGitHub c0546e0e46 Basic CI integration for Nix builds (#1495)
* Skip pushing to cachix for pull requests from forks.
* Build PostgREST first in the nix CI job in order to fail fast
2020-04-25 13:08:30 -05:00
RemoandGitHub d5f1d1b1ad Pin version 0.3 of protolude in nix (#1499) 2020-04-24 12:09:56 -05:00
RemoandGitHub 1c19bbde93 Run tests against several PostgreSQL versions using Nix (#1492)
* Show test output with postgrest-test-spec* in nix-shell

* Test output in color
2020-04-24 11:05:37 -05:00
Robert VollmertandSteve Chavez 3da5a2875e Update to protolude 0.3.0
The good part is that protolude 0.3.0 builds with GHC 8.10.

The bad part is that this change is a bit painful:
- the default `toS` has changed to no longer convert to and from ByteString
- similarly, `show` no longer outputs ByteString

The changes here are pretty much minimal to keep things compiling; I didn't
see a nice way to work with the new ConvertText class, even though `toUtf8`
seems like it might help if used besides `toS` at just the right spots.
2020-04-23 14:14:21 -05:00
RemoandSteve Chavez 2e6a094d48 Disable nix pure by default in stack.yaml 2020-04-23 12:48:04 -05:00
RemoandSteve Chavez 178c5d54d5 Integrate stack in the nix-shell environment 2020-04-22 14:30:02 -05:00
RemoandGitHub a7c396e464 Fix and improvements to with_tmp_db (#1488)
* Load fixtures in one psql session

* Add an option to keet the tmp directory and logs.
2020-04-22 11:57:40 -05:00
RemoandGitHub 24db4a1e25 Basics for building PostgREST with Nix (#1489)
* Add GHC options used with stack to postgrest.cabal

* Add a Nix derivation for just the executable.
2020-04-21 11:59:00 -05:00
RobertandGitHub ae77cf9a08 circleci: Build linters with memory-saving ghc-options. (#1490)
Previously, the linters would use the cached build artefacts from
building postgrest with -fno-spec-constr via stack.yaml. Potentially
passing it to the linter build explicitly will fix recent out-of-memory
issues.
2020-04-19 14:15:12 -05:00
Robert VollmertandSteve Chavez 4a0a37588f Fix hlint 2.2.11 bracketing suggestions 2020-04-16 17:47:52 -05:00
Robert VollmertandSteve Chavez 5838214910 Fix tests for explicit errors in aeson-1.4.7, and raise lower bound 2020-04-16 17:47:52 -05:00
Robert VollmertandSteve Chavez b609d8491e Update stack.yaml to lts-15.8 (GHC 8.8.3) 2020-04-16 17:47:52 -05:00
Robert VollmertandSteve Chavez 18538707ab Relax upper bounds for lens, network-uri, time.
This allows lens-4.19, network-uri-2.7 and time-0.10. According to
the respective changelogs, none of these should cause trouble.

lens-4.19 is required to build with GHC 8.10.
2020-04-16 13:36:26 -05:00
Robert VollmertandSteve Chavez e59c72cff3 circleci: Break out a plain build and cache job 2020-04-16 13:10:21 -05:00
Robert VollmertandSteve Chavez da573a1805 circleci: Dedicated cache key for style checks 2020-04-16 13:10:21 -05:00
Robert VollmertandSteve Chavez e24a7d005a circleci: Split style and hlint out into separate job 2020-04-16 13:10:21 -05:00
Robert VollmertandSteve Chavez 79399686db Remove unused imports (with newer protolude)
The changes here are from fixing stack build with LTS 15.8 (GHC 8.8.3)
to be warning-free. Most if not all come from the newer protolude
version, which adds some things like `unwords`.

So to make sure that we don't get unused import warnings with newer
stackage, this both raises the protolude lower bound and removes
these imports.
2020-04-16 11:10:24 -05:00
RemoandGitHub f6ce93f2c8 Add a script for running the test suite in a local temporary database. (#1476) 2020-04-14 12:34:50 -05:00
RemoandGitHub 8c35c9d711 Fix CI tests for Postgres 9.4 (#1482) 2020-04-14 11:00:43 -05:00
Wolfgang WaltherandGitHub be674eb41d Fix POST, PATCH, DELETE with ?select= and empty body or return=minimal (#1471)
* add tests for POST, PATCH, DELETE when using ?select= with empty bodies or ret=min

* fix select with empty bodies and ret=min

* improved tests for default cases, fixed computed overloaded columns on empty-body?select= PATCH
2020-04-13 12:32:35 -05:00
steve-chavez 052843ac9b Update BACKERS.md 2020-04-13 12:13:35 -05:00
Wolfgang WaltherandGitHub c524531784 Fix overloaded computed columns on RPC (#1473)
* fix some typos and spelling

* fix pg_source CTE name should be prefixed with pgrst_

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