From ab90bd24e0fd9cf4fdd8af76990823c4c183f867 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 18 Apr 2021 15:02:19 +0200 Subject: [PATCH 01/62] nix(feat): Add `postgrest-with-git` and `postgrest-with-pgrst` tools --- default.nix | 2 +- nix/tools/devTools.nix | 4 +- nix/tools/memory.nix | 2 +- nix/tools/tests.nix | 16 ++-- nix/tools/withTools.nix | 157 ++++++++++++++++++++++++++++++++++++---- 5 files changed, 156 insertions(+), 25 deletions(-) diff --git a/default.nix b/default.nix index 3c17053597..dcf437a664 100644 --- a/default.nix +++ b/default.nix @@ -148,5 +148,5 @@ rec { }; withTools = - pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions; }; + pkgs.callPackage nix/tools/withTools.nix { inherit devCabalOptions postgresqlVersions postgrest; }; } diff --git a/nix/tools/devTools.nix b/nix/tools/devTools.nix index 815c0c9b24..2fa4cd71d6 100644 --- a/nix/tools/devTools.nix +++ b/nix/tools/devTools.nix @@ -72,8 +72,8 @@ let inRootDir = true; } '' - ${withTools}/bin/postgrest-with-all ${tests}/bin/postgrest-test-spec - ${withTools}/bin/postgrest-with-all ${tests}/bin/postgrest-test-querycost + ${withTools.withPgAll} ${tests}/bin/postgrest-test-spec + ${withTools.withPgAll} ${tests}/bin/postgrest-test-querycost ${tests}/bin/postgrest-test-doctests ${tests}/bin/postgrest-test-spec-idempotence ${tests}/bin/postgrest-test-io diff --git a/nix/tools/memory.nix b/nix/tools/memory.nix index 6af26a5266..dcf34483fd 100644 --- a/nix/tools/memory.nix +++ b/nix/tools/memory.nix @@ -17,7 +17,7 @@ let withPath = [ postgrestProfiled curl ]; } '' - ${withTools.latest} test/memory-tests.sh + ${withTools.withPg} test/memory-tests.sh ''; in diff --git a/nix/tools/tests.nix b/nix/tools/tests.nix index bb5292636d..d152f4d23d 100644 --- a/nix/tools/tests.nix +++ b/nix/tools/tests.nix @@ -24,7 +24,7 @@ let withEnv = postgrest.env; } '' - ${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec + ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec ''; testQuerycost = @@ -36,7 +36,7 @@ let withEnv = postgrest.env; } '' - ${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost + ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost ''; testDoctests = @@ -66,7 +66,7 @@ let withEnv = postgrest.env; } '' - ${withTools.latest} ${runtimeShell} -c " \ + ${withTools.withPg} ${runtimeShell} -c " \ ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec && \ ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec" ''; @@ -92,7 +92,7 @@ let } '' ${cabal-install}/bin/cabal v2-build ${devCabalOptions} - ${cabal-install}/bin/cabal v2-exec ${withTools.latest} \ + ${cabal-install}/bin/cabal v2-exec ${withTools.withPg} \ ${ioTestPython}/bin/pytest -- -v test/io-tests "''${_arg_leftovers[@]}" ''; @@ -106,7 +106,7 @@ let withPath = [ jq ]; } '' - ${withTools.latest} \ + ${withTools.withPg} \ ${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \ postgrest --dump-schema \ | ${yq}/bin/yq -y . @@ -135,14 +135,14 @@ let # collect all tests HPCTIXFILE="$tmpdir"/io.tix \ - ${withTools.latest} ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} \ + ${withTools.withPg} ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} \ ${ioTestPython}/bin/pytest -- -v test/io-tests HPCTIXFILE="$tmpdir"/spec.tix \ - ${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec + ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec HPCTIXFILE="$tmpdir"/querycost.tix \ - ${withTools.latest} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost + ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:querycost # Note: No coverage for doctests, as doctests leverage GHCi and GHCi does not support hpc diff --git a/nix/tools/withTools.nix b/nix/tools/withTools.nix index d04da2c460..4c290b8a5b 100644 --- a/nix/tools/withTools.nix +++ b/nix/tools/withTools.nix @@ -1,9 +1,14 @@ { bashCompletion , buildToolbox +, cabal-install , checkedShellScript +, curl +, devCabalOptions +, git , lib , postgresqlVersions -, writeTextFile +, postgrest +, writeText }: let withTmpDb = @@ -78,27 +83,27 @@ let ''; # Helper script for running a command against all PostgreSQL versions. - withAll = + withPgAll = let runners = builtins.map - (pg: + (version: '' cat << EOF - Running against ${pg.name}... + Running against ${version.name}... EOF - trap 'echo "Failed on ${pg.name}"' exit + trap 'echo "Failed on ${version.name}"' exit - (${withTmpDb pg} "$_arg_command" "''${_arg_leftovers[@]}") + (${withTmpDb version} "$_arg_command" "''${_arg_leftovers[@]}") trap "" exit cat << EOF - Done running against ${pg.name}. + Done running against ${version.name}. EOF '') @@ -119,15 +124,141 @@ let (lib.concatStringsSep "\n\n" runners); # Create a `postgrest-with-postgresql-` for each PostgreSQL version - withVersions = builtins.map withTmpDb postgresqlVersions; + withPgVersions = builtins.map withTmpDb postgresqlVersions; + + withPg = builtins.head withPgVersions; + + withGit = + checkedShellScript + { + name = "postgrest-with-git"; + docs = + '' + Create a new worktree of the postgrest repo in a temporary directory and + check out , then run with arguments inside the temporary folder. + ''; + args = + [ + "ARG_POSITIONAL_SINGLE([commit], [Commit-ish reference to run command with])" + "ARG_POSITIONAL_SINGLE([command], [Command to run])" + "ARG_LEFTOVERS([command arguments])" + ]; + addCommandCompletion = true; # TODO: first positional argument needs git commit completion + inRootDir = true; + } + '' + # not using withTmpDir here, because we don't want to keep the directory on error + tmpdir="$(mktemp -d)" + trap 'rm -rf "$tmpdir"' EXIT + + ${git}/bin/git worktree add -f "$tmpdir" "$_arg_commit" > /dev/null + + cd "$tmpdir" + ("$_arg_command" "''${_arg_leftovers[@]}") + + ${git}/bin/git worktree remove -f "$tmpdir" > /dev/null + ''; + + legacyConfig = + writeText "legacy.conf" + '' + # Using this config file to support older postgrest versions for `postgrest-loadtest-against` + db-uri="$(PGRST_DB_URI)" + db-schema="$(PGRST_DB_SCHEMAS)" + db-anon-role="$(PGRST_DB_ANON_ROLE)" + db-pool="$(PGRST_DB_POOL)" + server-unix-socket="$(PGRST_SERVER_UNIX_SOCKET)" + log-level="$(PGRST_LOG_LEVEL)" + ''; + + waitForPgrstPid = + checkedShellScript + { + name = "postgrest-wait-for-pgrst-pid"; + docs = "Wait for PostgREST to be running. Needs to be a separate command for timeout to work below."; + args = [ + "ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [Unix socket to check for running PostgREST instance])" + ]; + } + '' + # ARG_USE_ENV only adds defaults or docs for environment variables + # We manually implement a required check here + # See also: https://github.com/matejak/argbash/issues/80 + : "''${PGRST_SERVER_UNIX_SOCKET:?PGRST_SERVER_UNIX_SOCKET is required}" + + until [ -S "$PGRST_SERVER_UNIX_SOCKET" ] + do + sleep 0.1 + done + + # return pid of postgrest process + lsof -t -c '/^postgrest$/' "$PGRST_SERVER_UNIX_SOCKET" + ''; + + waitForPgrstReady = + checkedShellScript + { + name = "postgrest-wait-for-pgrst-ready"; + docs = "Wait for PostgREST to be ready to serve requests. Needs to be a separate command for timeout to work below."; + args = [ + "ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [Unix socket to check for running PostgREST instance])" + ]; + } + '' + # ARG_USE_ENV only adds defaults or docs for environment variables + # We manually implement a required check here + # See also: https://github.com/matejak/argbash/issues/80 + : "''${PGRST_SERVER_UNIX_SOCKET:?PGRST_SERVER_UNIX_SOCKET is required}" + + function check_status () { + ${curl}/bin/curl -s -o /dev/null -w "%{http_code}" --unix-socket "$PGRST_SERVER_UNIX_SOCKET" http://localhost/ + } + + while [[ "$(check_status)" != "200" ]]; + do sleep 0.1; + done + ''; + + withPgrst = + checkedShellScript + { + name = "postgrest-with-pgrst"; + docs = "Build and run PostgREST and run with PGRST_SERVER_UNIX_SOCKET set."; + args = + [ + "ARG_POSITIONAL_SINGLE([command], [Command to run])" + "ARG_LEFTOVERS([command arguments])" + ]; + addCommandCompletion = true; + inRootDir = true; + withEnv = postgrest.env; + withTmpDir = true; + } + '' + export PGRST_SERVER_UNIX_SOCKET="$tmpdir"/postgrest.socket + + ${cabal-install}/bin/cabal v2-build ${devCabalOptions} > "$tmpdir"/build.log 2>&1 + ${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \ + postgrest ${legacyConfig} > "$tmpdir"/run.log 2>&1 & + + # to get the pid of the postgrest process, we need to jump through some hoops + # $! will return the pid of cabal - but killing this, will not propagate to postgrest + pid=$(timeout -s TERM 1 ${waitForPgrstPid}) + cleanup() { + kill "$pid" || true + } + trap cleanup EXIT + + timeout -s TERM 5 ${waitForPgrstReady} + + ("$_arg_command" "''${_arg_leftovers[@]}") + ''; in buildToolbox { name = "postgrest-with"; - tools = [ withAll ] ++ withVersions; - extra = { - # make withTools.latest available for other nix files - latest = withTmpDb (builtins.head postgresqlVersions); - }; + tools = [ withPgAll withGit withPgrst ] ++ withPgVersions; + # make withTools available for other nix files + extra = { inherit withGit withPg withPgAll withPgrst; }; } From 32ff9dfb48125cb8e943f565c14b4c33ecf0442a Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 12 Apr 2021 16:59:13 +0200 Subject: [PATCH 02/62] nix(feat): Add `postgrest-loadtest` --- .github/workflows/loadtest.yaml | 34 +++++++ .gitignore | 1 + default.nix | 4 + nix/tools/loadtest.nix | 165 ++++++++++++++++++++++++++++++++ shell.nix | 1 + test/loadtest/fixtures.sql | 34 +++++++ test/loadtest/patch.json | 3 + test/loadtest/post.json | 3 + test/loadtest/put.json | 5 + test/loadtest/rpc.json | 3 + test/loadtest/targets.http | 30 ++++++ 11 files changed, 283 insertions(+) create mode 100644 .github/workflows/loadtest.yaml create mode 100644 nix/tools/loadtest.nix create mode 100644 test/loadtest/fixtures.sql create mode 100644 test/loadtest/patch.json create mode 100644 test/loadtest/post.json create mode 100644 test/loadtest/put.json create mode 100644 test/loadtest/rpc.json create mode 100644 test/loadtest/targets.http diff --git a/.github/workflows/loadtest.yaml b/.github/workflows/loadtest.yaml new file mode 100644 index 0000000000..18cba2df4a --- /dev/null +++ b/.github/workflows/loadtest.yaml @@ -0,0 +1,34 @@ +name: Loadtest + +on: + push: + branches: + - main + tags: + - v* + pull_request: + branches: + - main + +jobs: + Loadtest-Nix: + name: Loadtest (Nix) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + with: + fetch-depth: 0 + - name: Setup Nix Environment + uses: ./.github/actions/setup-nix + with: + tools: loadtest + - name: Run loadtest + run: | + postgrest-loadtest-against main + postgrest-loadtest-report > loadtest/loadtest.md + - name: Upload report + uses: actions/upload-artifact@v2.2.4 + with: + name: loadtest.md + path: loadtest/loadtest.md + if-no-files-found: error diff --git a/.gitignore b/.gitignore index d00cdb788a..b91c5996fb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ __pycache__ *.tix coverage .hpc +loadtest diff --git a/default.nix b/default.nix index dcf437a664..a484819a67 100644 --- a/default.nix +++ b/default.nix @@ -123,6 +123,10 @@ rec { docker = pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; }; + # Load testing tools. + loadtest = + pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; }; + # Script for running memory tests. memory = pkgs.callPackage nix/tools/memory.nix { inherit postgrestProfiled withTools; }; diff --git a/nix/tools/loadtest.nix b/nix/tools/loadtest.nix new file mode 100644 index 0000000000..adc38f2b5a --- /dev/null +++ b/nix/tools/loadtest.nix @@ -0,0 +1,165 @@ +{ buildToolbox +, checkedShellScript +, jq +, python3Packages +, vegeta +, withTools +, writers +}: +let + runner = + checkedShellScript + { + name = "postgrest-loadtest-runner"; + docs = "Run vegeta. Assume PostgREST to be running."; + args = [ + "ARG_LEFTOVERS([additional vegeta arguments])" + "ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [Unix socket to connect to running PostgREST instance])" + ]; + } + '' + # ARG_USE_ENV only adds defaults or docs for environment variables + # We manually implement a required check here + # See also: https://github.com/matejak/argbash/issues/80 + : "''${PGRST_SERVER_UNIX_SOCKET:?PGRST_SERVER_UNIX_SOCKET is required}" + + ${vegeta}/bin/vegeta -cpus 1 attack \ + -unix-socket "$PGRST_SERVER_UNIX_SOCKET" \ + -max-workers 1 \ + -workers 1 \ + -rate 0 \ + -duration 60s \ + "''${_arg_leftovers[@]}" + ''; + + loadtest = + checkedShellScript + { + name = "postgrest-loadtest"; + docs = "Run the vegeta loadtests with PostgREST."; + args = [ + "ARG_OPTIONAL_SINGLE([output], [o], [Filename to dump json output to], [./loadtest/result.bin])" + "ARG_OPTIONAL_SINGLE([testdir], [t], [Directory to load tests and fixtures from], [./test/loadtest])" + "ARG_LEFTOVERS([additional vegeta arguments])" + ]; + inRootDir = true; + } + '' + export PGRST_DB_CONFIG="false" + export PGRST_DB_POOL="1" + export PGRST_DB_TX_END="rollback-allow-override" + export PGRST_LOG_LEVEL="crit" + + mkdir -p "$(dirname "$_arg_output")" + + # shellcheck disable=SC2145 + ${withTools.withPg} --fixtures "$_arg_testdir"/fixtures.sql \ + ${withTools.withPgrst} \ + sh -c "cd \"$_arg_testdir\" && ${runner} -targets targets.http \"''${_arg_leftovers[@]}\"" \ + | tee "$_arg_output" \ + | ${vegeta}/bin/vegeta report -type=text + ''; + + loadtestAgainst = + checkedShellScript + { + name = "postgrest-loadtest-against"; + docs = + '' + Run the vegeta loadtest twice: + - once on the branch + - once in the current worktree + ''; + args = [ + "ARG_POSITIONAL_SINGLE([target], [Commit-ish reference to compare with])" + "ARG_LEFTOVERS([additional vegeta arguments])" + ]; + inRootDir = true; + } + '' + cat << EOF + + Running loadtest on "$_arg_target"... + + EOF + + # Runs the test files from the current working tree + # to make sure both tests are run with the same files. + # Save the results in the current working tree, too, + # otherwise they'd be lost in the temporary working tree + # created by withTools.withGit. + ${withTools.withGit} "$_arg_target" ${loadtest} --output "$PWD/loadtest/$_arg_target.bin" --testdir "$PWD/test/loadtest" "''${_arg_leftovers[@]}" + + cat << EOF + + Done running on "$_arg_target". + + EOF + + cat << EOF + + Running loadtest on HEAD... + + EOF + + ${loadtest} --output "$PWD/loadtest/head.bin" --testdir "$PWD/test/loadtest" "''${_arg_leftovers[@]}" + + cat << EOF + + Done running on HEAD. + + EOF + ''; + + reporter = + checkedShellScript + { + name = "postgrest-loadtest-reporter"; + docs = "Create a named json report for a single result file."; + args = [ + "ARG_POSITIONAL_SINGLE([file], [Filename of result to create report for])" + "ARG_LEFTOVERS([additional vegeta arguments])" + ]; + inRootDir = true; + } + '' + ${vegeta}/bin/vegeta report -type=json "$_arg_file" \ + | ${jq}/bin/jq --arg branch "$(basename "$_arg_file" .bin)" '. + {branch: $branch}' + ''; + + toMarkdown = + writers.writePython3 "postgrest-loadtest-to-markdown" + { + libraries = [ python3Packages.pandas python3Packages.tabulate ]; + } + '' + import sys + import pandas as pd + + pd.read_json(sys.stdin) \ + .set_index('param') \ + .drop(['branch', 'earliest', 'end', 'latest']) \ + .convert_dtypes() \ + .to_markdown(sys.stdout, floatfmt='.0f') + ''; + + + report = + checkedShellScript + { + name = "postgrest-loadtest-report"; + docs = "Create a report of all loadtest reports as markdown."; + inRootDir = true; + } + '' + find loadtest -type f -iname '*.bin' -exec ${reporter} {} \; \ + | ${jq}/bin/jq '[leaf_paths as $path | {param: $path | join("."), (.branch): getpath($path)}]' \ + | ${jq}/bin/jq --slurp 'flatten | group_by(.param) | map(add)' \ + | ${toMarkdown} + ''; + +in +buildToolbox { + name = "postgrest-loadtest"; + tools = [ loadtest loadtestAgainst report ]; +} diff --git a/shell.nix b/shell.nix index d3b343732e..6e6fb5f971 100644 --- a/shell.nix +++ b/shell.nix @@ -21,6 +21,7 @@ let [ postgrest.cabalTools postgrest.devTools + postgrest.loadtest postgrest.nixpkgsTools postgrest.style postgrest.tests diff --git a/test/loadtest/fixtures.sql b/test/loadtest/fixtures.sql new file mode 100644 index 0000000000..aa0b43ec58 --- /dev/null +++ b/test/loadtest/fixtures.sql @@ -0,0 +1,34 @@ +CREATE ROLE postgrest_test_anonymous; +GRANT postgrest_test_anonymous TO :USER; +CREATE SCHEMA test; + +-- PUT+PATCH target needs one record and column to modify +CREATE TABLE test.actors ( + PRIMARY KEY (actor), + actor INT, + name TEXT, + last_modified TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO test.actors VALUES (1, 'John Doe'); + +-- POST target needs generated PK +CREATE TABLE test.films ( + PRIMARY KEY (film), + film INT GENERATED BY DEFAULT AS IDENTITY, + title TEXT +); + +-- DELETE target remains empty +CREATE TABLE test.roles ( + actor INT REFERENCES test.actors, + film INT REFERENCES test.films, + character TEXT +); + +CREATE FUNCTION test.call_me (name TEXT) RETURNS TEXT +STABLE LANGUAGE SQL AS $$ + SELECT 'Hello ' || name || ', how are you?'; +$$; + +GRANT USAGE ON SCHEMA test TO postgrest_test_anonymous; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA test TO postgrest_test_anonymous; diff --git a/test/loadtest/patch.json b/test/loadtest/patch.json new file mode 100644 index 0000000000..035306dd79 --- /dev/null +++ b/test/loadtest/patch.json @@ -0,0 +1,3 @@ +{ + "last_modified": "now" +} diff --git a/test/loadtest/post.json b/test/loadtest/post.json new file mode 100644 index 0000000000..2ffa497c92 --- /dev/null +++ b/test/loadtest/post.json @@ -0,0 +1,3 @@ +{ + "title": "Workers Leaving The Lumière Factory In Lyon" +} diff --git a/test/loadtest/put.json b/test/loadtest/put.json new file mode 100644 index 0000000000..ccb40ba39e --- /dev/null +++ b/test/loadtest/put.json @@ -0,0 +1,5 @@ +{ + "actor": 1, + "name": "John Doe", + "last_modified": "now" +} diff --git a/test/loadtest/rpc.json b/test/loadtest/rpc.json new file mode 100644 index 0000000000..52192609d3 --- /dev/null +++ b/test/loadtest/rpc.json @@ -0,0 +1,3 @@ +{ + "name": "John" +} diff --git a/test/loadtest/targets.http b/test/loadtest/targets.http new file mode 100644 index 0000000000..dbc762dbc1 --- /dev/null +++ b/test/loadtest/targets.http @@ -0,0 +1,30 @@ +GET http://postgrest/ +Prefer: tx=commit + +HEAD http://postgrest/actors?actor=eq.1 +Prefer: tx=commit + +GET http://postgrest/actors?select=*,roles(*,films(*)) +Prefer: tx=commit + +POST http://postgrest/films?columns=title +Prefer: tx=rollback +@post.json + +PUT http://postgrest/actors?actor=eq.1&columns=name +Prefer: tx=rollback +@put.json + +PATCH http://postgrest/actors?actor=eq.1 +Prefer: tx=rollback +@patch.json + +DELETE http://postgrest/roles +Prefer: tx=rollback + +GET http://postgrest/rpc/call_me?name=John + +POST http://postgrest/rpc/call_me +@rpc.json + +OPTIONS http://postgrest/actors From 5a8d9b12464b3f79c3609650e421bd1ba8a9fb06 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 18 Apr 2021 22:16:13 +0200 Subject: [PATCH 03/62] nix(feat): Add bash completion for git references --- .../checked-shell-script.nix | 15 +++++++------- nix/tools/devTools.nix | 2 +- nix/tools/loadtest.nix | 11 +++++++++- nix/tools/withTools.nix | 20 ++++++++++++++----- shell.nix | 2 ++ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/nix/overlays/checked-shell-script/checked-shell-script.nix b/nix/overlays/checked-shell-script/checked-shell-script.nix index cf391e9cd1..7de75f5249 100644 --- a/nix/overlays/checked-shell-script/checked-shell-script.nix +++ b/nix/overlays/checked-shell-script/checked-shell-script.nix @@ -14,7 +14,7 @@ { name , docs , args ? [ ] -, addCommandCompletion ? false +, positionalCompletion ? "" , inRootDir ? false , redirectTixFiles ? true , withEnv ? null @@ -22,11 +22,10 @@ , withTmpDir ? false }: text: let + # square brackets are a pain to escape - if even possible. just don't use them... + escape = builtins.replaceStrings [ "\n" ] [ " \\n" ]; + argsTemplate = - let - # square brackets are a pain to escape - if even possible. just don't use them... - escapedDocs = builtins.replaceStrings [ "\n" ] [ " \\n" ] docs; - in writeTextFile { inherit name; destination = "/${name}.m4"; # destination is needed to have the proper basename for completion @@ -37,7 +36,7 @@ let # stripping the /nix/store/... path for nicer display BASH_ARGV0="$(basename "$0")" - # ARG_HELP([${name}], [${escapedDocs}]) + # ARG_HELP([${name}], [${escape docs}]) ${lib.strings.concatMapStrings (arg: "# " + arg) args} # ARG_POSITIONAL_DOUBLEDASH() # ARG_DEFAULTS_POS() @@ -65,8 +64,8 @@ let ${argbash}/bin/argbash --type completion --strip all ${argsTemplate}/${name}.m4 > $out '' - + lib.optionalString addCommandCompletion '' - sed 's/COMPREPLY.*compgen -o bashdefault .*$/_command/' -i $out + + lib.optionalString (positionalCompletion != "") '' + sed 's#COMPREPLY.*compgen -o bashdefault .*$#${escape positionalCompletion}#' -i $out '' ); diff --git a/nix/tools/devTools.nix b/nix/tools/devTools.nix index 2fa4cd71d6..f99f1d8ac4 100644 --- a/nix/tools/devTools.nix +++ b/nix/tools/devTools.nix @@ -29,7 +29,7 @@ let "ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_LEFTOVERS([command arguments])" ]; - addCommandCompletion = true; + positionalCompletion = "_command"; redirectTixFiles = false; # will be done by sub-command inRootDir = true; } diff --git a/nix/tools/loadtest.nix b/nix/tools/loadtest.nix index adc38f2b5a..a7e207809e 100644 --- a/nix/tools/loadtest.nix +++ b/nix/tools/loadtest.nix @@ -61,9 +61,12 @@ let ''; loadtestAgainst = + let + name = "postgrest-loadtest-against"; + in checkedShellScript { - name = "postgrest-loadtest-against"; + inherit name; docs = '' Run the vegeta loadtest twice: @@ -74,6 +77,12 @@ let "ARG_POSITIONAL_SINGLE([target], [Commit-ish reference to compare with])" "ARG_LEFTOVERS([additional vegeta arguments])" ]; + positionalCompletion = + '' + if test "$prev" == "${name}"; then + __gitcomp_nl "$(__git_refs)" + fi + ''; inRootDir = true; } '' diff --git a/nix/tools/withTools.nix b/nix/tools/withTools.nix index 4c290b8a5b..fbc0bd44c0 100644 --- a/nix/tools/withTools.nix +++ b/nix/tools/withTools.nix @@ -27,7 +27,7 @@ let "ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])" "ARG_USE_ENV([PGRST_DB_ANON_ROLE], [postgrest_test_anonymous], [Anonymous PG role])" ]; - addCommandCompletion = true; + positionalCompletion = "_command"; inRootDir = true; redirectTixFiles = false; withPath = [ postgresql ]; @@ -118,7 +118,7 @@ let "ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_LEFTOVERS([command arguments])" ]; - addCommandCompletion = true; + positionalCompletion = "_command"; inRootDir = true; } (lib.concatStringsSep "\n\n" runners); @@ -129,9 +129,12 @@ let withPg = builtins.head withPgVersions; withGit = + let + name = "postgrest-with-git"; + in checkedShellScript { - name = "postgrest-with-git"; + inherit name; docs = '' Create a new worktree of the postgrest repo in a temporary directory and @@ -143,7 +146,14 @@ let "ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_LEFTOVERS([command arguments])" ]; - addCommandCompletion = true; # TODO: first positional argument needs git commit completion + positionalCompletion = + '' + if test "$prev" == "${name}"; then + __gitcomp_nl "$(__git_refs)" + else + _command_offset 2 + fi + ''; inRootDir = true; } '' @@ -229,7 +239,7 @@ let "ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_LEFTOVERS([command arguments])" ]; - addCommandCompletion = true; + positionalCompletion = "_command"; inRootDir = true; withEnv = postgrest.env; withTmpDir = true; diff --git a/shell.nix b/shell.nix index 6e6fb5f971..95fe15b7f0 100644 --- a/shell.nix +++ b/shell.nix @@ -38,6 +38,7 @@ lib.overrideDerivation postgrest.env ( base.buildInputs ++ [ pkgs.cabal-install pkgs.cabal2nix + pkgs.git pkgs.postgresql postgrest.hsie.bin ] @@ -46,6 +47,7 @@ lib.overrideDerivation postgrest.env ( shellHook = '' source ${pkgs.bashCompletion}/etc/profile.d/bash_completion.sh + source ${pkgs.git}/share/git/contrib/completion/git-completion.bash source ${postgrest.hsie.bashCompletion} '' From 94115ff134d131883855cfa3b85103c97321a6bd Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sat, 27 Nov 2021 10:59:07 +0100 Subject: [PATCH 04/62] ci: Disable update of dockerhub description on release This is not allowed via personal access token right now and could be re-enabled in the future, once solved by docker hub. --- .github/workflows/ci.yaml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 988e30b161..d043a43ade 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -361,13 +361,15 @@ jobs: else echo "Skipping pushing to 'latest' tag for v$VERSION pre-release..." fi - - name: Update descriptions on Docker Hub - env: - DOCKER_PASS: ${{ secrets.DOCKER_PASS }} - run: | - if [[ -z "$ISPRERELEASE" ]]; then - echo "Updating description on Docker Hub..." - postgrest-release-dockerhub-description - else - echo "Skipping updating description for pre-release..." - fi +# TODO: Enable dockerhub description update again, once a solution for the permission problem is found: +# https://github.com/docker/hub-feedback/issues/1927 +# - name: Update descriptions on Docker Hub +# env: +# DOCKER_PASS: ${{ secrets.DOCKER_PASS }} +# run: | +# if [[ -z "$ISPRERELEASE" ]]; then +# echo "Updating description on Docker Hub..." +# postgrest-release-dockerhub-description +# else +# echo "Skipping updating description for pre-release..." +# fi From defdcb85efedb85f3b9aa087c0626c103284f452 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sat, 27 Nov 2021 11:08:11 +0100 Subject: [PATCH 05/62] ci: Run IO tests against all postgres versions Resolves #1820. --- .github/workflows/ci.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d043a43ade..a020281633 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,6 +64,22 @@ jobs: if: always() run: postgrest-with-postgresql-9.6 postgrest-test-spec + - name: Run the IO tests against PostgreSQL 13 + if: always() + run: postgrest-with-postgresql-13 postgrest-test-io + - name: Run the IO tests against PostgreSQL 12 + if: always() + run: postgrest-with-postgresql-12 postgrest-test-io + - name: Run the IO tests against PostgreSQL 11 + if: always() + run: postgrest-with-postgresql-11 postgrest-test-io + - name: Run the IO tests against PostgreSQL 10 + if: always() + run: postgrest-with-postgresql-10 postgrest-test-io + - name: Run the IO tests against PostgreSQL 9.6 + if: always() + run: postgrest-with-postgresql-9.6 postgrest-test-io + - name: Run query cost tests against all PostgreSQL versions if: always() run: postgrest-with-all postgrest-test-querycost From 9c6597c35167d66d01be1e78947ac927f8771639 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sat, 27 Nov 2021 11:24:37 +0100 Subject: [PATCH 06/62] ci: Use matrix strategy to test against postgres versions --- .github/workflows/ci.yaml | 71 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a020281633..286cacaf91 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,7 +39,7 @@ jobs: - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: - tools: tests withTools + tools: tests - name: Run coverage (IO tests and Spec tests against PostgreSQL 14) run: postgrest-coverage @@ -48,49 +48,45 @@ jobs: with: files: ./coverage/codecov.json - - name: Run the spec tests against PostgreSQL 13 - if: always() - run: postgrest-with-postgresql-13 postgrest-test-spec - - name: Run the spec tests against PostgreSQL 12 - if: always() - run: postgrest-with-postgresql-12 postgrest-test-spec - - name: Run the spec tests against PostgreSQL 11 - if: always() - run: postgrest-with-postgresql-11 postgrest-test-spec - - name: Run the spec tests against PostgreSQL 10 - if: always() - run: postgrest-with-postgresql-10 postgrest-test-spec - - name: Run the spec tests against PostgreSQL 9.6 + - name: Run doctests if: always() - run: postgrest-with-postgresql-9.6 postgrest-test-spec + run: nix-shell --run postgrest-test-doctests - - name: Run the IO tests against PostgreSQL 13 - if: always() - run: postgrest-with-postgresql-13 postgrest-test-io - - name: Run the IO tests against PostgreSQL 12 - if: always() - run: postgrest-with-postgresql-12 postgrest-test-io - - name: Run the IO tests against PostgreSQL 11 - if: always() - run: postgrest-with-postgresql-11 postgrest-test-io - - name: Run the IO tests against PostgreSQL 10 - if: always() - run: postgrest-with-postgresql-10 postgrest-test-io - - name: Run the IO tests against PostgreSQL 9.6 + - name: Check the spec tests for idempotence if: always() - run: postgrest-with-postgresql-9.6 postgrest-test-io + run: postgrest-test-spec-idempotence + + + Test-Pg-Nix: + strategy: + fail-fast: false + matrix: + pgVersion: [9.6, 10, 11, 12, 13, 14] + name: Test PG ${{ matrix.pgVersion }} (Nix) + runs-on: ubuntu-latest + defaults: + run: + # Hack for enabling color output, see: + # https://github.com/actions/runner/issues/241#issuecomment-842566950 + shell: script -qec "bash --noprofile --norc -eo pipefail {0}" + steps: + - uses: actions/checkout@v2.4.0 + - name: Setup Nix Environment + uses: ./.github/actions/setup-nix + with: + tools: tests withTools - - name: Run query cost tests against all PostgreSQL versions + - name: Run spec tests if: always() - run: postgrest-with-all postgrest-test-querycost + run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec - - name: Run doctests + - name: Run IO tests if: always() - run: nix-shell --run postgrest-test-doctests + run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io - - name: Check the spec tests for idempotence + - name: Run query cost tests if: always() - run: postgrest-test-spec-idempotence + run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-querycost Test-Memory-Nix: @@ -106,7 +102,7 @@ jobs: run: postgrest-test-memory - Build-Nix: + Build-Static-Nix: name: Build Linux static (Nix) runs-on: ubuntu-latest steps: @@ -232,8 +228,9 @@ jobs: needs: - Lint-Style - Test-Nix + - Test-Pg-Nix - Test-Memory-Nix - - Build-Nix + - Build-Static-Nix - Build-Stack - Get-FreeBSD-CirrusCI outputs: From bbc07d3f40cfa156cfe8cfb5db93f6da6c115748 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 28 Nov 2021 21:20:56 +0100 Subject: [PATCH 07/62] fix: Link to latest docs in pre-release openapi output Resolves #2018 --- src/PostgREST/Version.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PostgREST/Version.hs b/src/PostgREST/Version.hs index 4a9a37f482..d736c77b84 100644 --- a/src/PostgREST/Version.hs +++ b/src/PostgREST/Version.hs @@ -30,10 +30,12 @@ prettyVersion = -- | Version number used in docs. +-- Pre-release versions link to the latest docs -- Uses only the two first components of the version. Example: 'v1.1' docsVersion :: Text -docsVersion = - "v" <> (T.intercalate "." . map show . take 2 $ versionBranch version) +docsVersion + | isPreRelease = "latest" + | otherwise = "v" <> (T.intercalate "." . map show . take 2 $ versionBranch version) -- | Versions with four components (e.g., '1.1.1.1') are treated as pre-releases. From 1cb00d3c624babfe6b1cbf15c54a0b1b7ab0b5dd Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 28 Nov 2021 10:37:41 +0100 Subject: [PATCH 08/62] fix: Execute deferred constraint triggers when using `Prefer: tx=rollback` Resolves #2020 --- CHANGELOG.md | 2 ++ src/PostgREST/Middleware.hs | 6 ++++-- test/Feature/RollbackSpec.hs | 42 ++++++++++++++++++++++++++++++++++++ test/fixtures/privileges.sql | 1 + test/fixtures/schema.sql | 14 ++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d36a9c7c3c..1788c3a2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed + - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther + ## [9.0.0] - 2021-11-25 ### Added diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index fba8edddc1..fb64f38cad 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -2,6 +2,7 @@ Module : PostgREST.Middleware Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and pre-request function. -} +{-# LANGUAGE BlockArguments #-} {-# LANGUAGE RecordWildCards #-} module PostgREST.Middleware ( runPgLocals @@ -170,8 +171,9 @@ optionalRollback -> ExceptT Error SQL.Transaction Wai.Response optionalRollback AppConfig{..} ApiRequest{..} transaction = do resp <- catchError transaction $ return . errorResponseFor - when (shouldRollback || (configDbTxRollbackAll && not shouldCommit)) - (lift SQL.condemn) + when (shouldRollback || (configDbTxRollbackAll && not shouldCommit)) $ lift do + SQL.sql "SET CONSTRAINTS ALL IMMEDIATE" + SQL.condemn return $ Wai.mapResponseHeaders preferenceApplied resp where shouldCommit = diff --git a/test/Feature/RollbackSpec.hs b/test/Feature/RollbackSpec.hs index 24bdbcd077..5ed05b9ca8 100644 --- a/test/Feature/RollbackSpec.hs +++ b/test/Feature/RollbackSpec.hs @@ -70,6 +70,35 @@ shouldRespondToReads reqHeaders respHeaders = do [json|[{"id":1}]|] { matchHeaders = respHeaders } +shouldRaiseExceptions reqHeaders respHeaders = do + it "raises immediate constraints" $ do + request methodPost "/rpc/raise_constraint" + reqHeaders + "" + `shouldRespondWith` + [json|{ + "hint":null, + "details":"Key (col)=(1) already exists.", + "code":"23505", + "message":"duplicate key value violates unique constraint \"deferrable_unique_constraint_col_key\"" + }|] + { matchStatus = 409 + , matchHeaders = respHeaders } + + it "raises deferred constraints" $ do + request methodPost "/rpc/raise_constraint" + reqHeaders + [json|{"deferred": true}|] + `shouldRespondWith` + [json|{ + "hint":null, + "details":"Key (col)=(1) already exists.", + "code":"23505", + "message":"duplicate key value violates unique constraint \"deferrable_unique_constraint_col_key\"" + }|] + { matchStatus = 409 + , matchHeaders = respHeaders } + shouldPersistMutations reqHeaders respHeaders = do it "does persist post" $ do request methodPost "/items" @@ -178,28 +207,38 @@ allowed = describe "tx-allow-override = true" $ do describe "without Prefer tx" $ do preferDefault `shouldRespondToReads` withoutPreferenceApplied preferDefault `shouldNotPersistMutations` withoutPreferenceApplied + preferDefault `shouldRaiseExceptions` withoutPreferenceApplied describe "Prefer tx=commit" $ do preferCommit `shouldRespondToReads` withPreferenceCommitApplied preferCommit `shouldPersistMutations` withPreferenceCommitApplied + -- Exceptions are always without preference applied, + -- because they return before the end of the transaction. + preferCommit `shouldRaiseExceptions` withoutPreferenceApplied describe "Prefer tx=rollback" $ do preferRollback `shouldRespondToReads` withPreferenceRollbackApplied preferRollback `shouldNotPersistMutations` withPreferenceRollbackApplied + -- Exceptions are always without preference applied, + -- because they return before the end of the transaction. + preferRollback `shouldRaiseExceptions` withoutPreferenceApplied disallowed :: SpecWith ((), Application) disallowed = describe "tx-rollback-all = false, tx-allow-override = false" $ do describe "without Prefer tx" $ do preferDefault `shouldRespondToReads` withoutPreferenceApplied preferDefault `shouldPersistMutations` withoutPreferenceApplied + preferDefault `shouldRaiseExceptions` withoutPreferenceApplied describe "Prefer tx=commit" $ do preferCommit `shouldRespondToReads` withoutPreferenceApplied preferCommit `shouldPersistMutations` withoutPreferenceApplied + preferCommit `shouldRaiseExceptions` withoutPreferenceApplied describe "Prefer tx=rollback" $ do preferRollback `shouldRespondToReads` withoutPreferenceApplied preferRollback `shouldPersistMutations` withoutPreferenceApplied + preferRollback `shouldRaiseExceptions` withoutPreferenceApplied forced :: SpecWith ((), Application) @@ -207,12 +246,15 @@ forced = describe "tx-rollback-all = true, tx-allow-override = false" $ do describe "without Prefer tx" $ do preferDefault `shouldRespondToReads` withoutPreferenceApplied preferDefault `shouldNotPersistMutations` withoutPreferenceApplied + preferDefault `shouldRaiseExceptions` withoutPreferenceApplied describe "Prefer tx=commit" $ do preferCommit `shouldRespondToReads` withoutPreferenceApplied preferCommit `shouldNotPersistMutations` withoutPreferenceApplied + preferCommit `shouldRaiseExceptions` withoutPreferenceApplied describe "Prefer tx=rollback" $ do preferRollback `shouldRespondToReads` withoutPreferenceApplied preferRollback `shouldNotPersistMutations` withoutPreferenceApplied + preferRollback `shouldRaiseExceptions` withoutPreferenceApplied diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index b103eeae32..b6899e0122 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -25,6 +25,7 @@ GRANT ALL ON TABLE , complex_items , compound_pk , compound_pk_view + , deferrable_unique_constraint , empty_table , has_count_column , has_fk diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 0c0c002732..bb92f174d2 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -2448,3 +2448,17 @@ CREATE TABLE chores ( , name text , done bool ); + +CREATE TABLE deferrable_unique_constraint ( + col INT UNIQUE DEFERRABLE INITIALLY IMMEDIATE +); + +CREATE FUNCTION raise_constraint(deferred BOOL DEFAULT FALSE) RETURNS void +LANGUAGE plpgsql AS $$ +BEGIN + IF deferred THEN + SET CONSTRAINTS ALL DEFERRED; + END IF; + + INSERT INTO deferrable_unique_constraint VALUES (1), (1); +END$$; From c6943a2912893007cc1acd8e67fe0c12d7cc9f79 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Tue, 30 Nov 2021 17:52:27 +0100 Subject: [PATCH 09/62] refactor: Make asJsonSingleF "safe", by accessing only the first element of a json_agg result --- src/PostgREST/Query/SqlFragment.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PostgREST/Query/SqlFragment.hs b/src/PostgREST/Query/SqlFragment.hs index e35a8c2dcb..fb19412121 100644 --- a/src/PostgREST/Query/SqlFragment.hs +++ b/src/PostgREST/Query/SqlFragment.hs @@ -172,10 +172,10 @@ asJsonF returnsScalar | returnsScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar), '[]')::character varying" | otherwise = "coalesce(json_agg(_postgrest_t), '[]')::character varying" -asJsonSingleF :: Bool -> SqlFragment --TODO! unsafe when the query actually returns multiple rows, used only on inserting and returning single element +asJsonSingleF :: Bool -> SqlFragment asJsonSingleF returnsScalar - | returnsScalar = "coalesce(string_agg(to_json(_postgrest_t.pgrst_scalar)::text, ','), 'null')::character varying" - | otherwise = "coalesce(string_agg(to_json(_postgrest_t)::text, ','), '')::character varying" + | returnsScalar = "coalesce((json_agg(_postgrest_t.pgrst_scalar)->0)::text, 'null')" + | otherwise = "coalesce((json_agg(_postgrest_t)->0)::text, 'null')" asBinaryF :: FieldName -> SqlFragment asBinaryF fieldName = "coalesce(string_agg(_postgrest_t." <> pgFmtIdent fieldName <> ", ''), '')" From c2a99b74456782aa7a07a9d5c5aff9456b6a290c Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 3 Dec 2021 18:24:20 +0100 Subject: [PATCH 10/62] nix: Add weeder to postgrest-coverage Removes a bit of dead code from SpecHelper.hs --- nix/tools/devTools.nix | 1 + nix/tools/tests.nix | 6 ++++-- postgrest.cabal | 6 ++++-- test/SpecHelper.hs | 13 +------------ test/weeder.dhall | 4 ++++ 5 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 test/weeder.dhall diff --git a/nix/tools/devTools.nix b/nix/tools/devTools.nix index f99f1d8ac4..8b90f1fcbf 100644 --- a/nix/tools/devTools.nix +++ b/nix/tools/devTools.nix @@ -5,6 +5,7 @@ , devCabalOptions , entr , graphviz +, haskellPackages , hsie , nix , silver-searcher diff --git a/nix/tools/tests.nix b/nix/tools/tests.nix index d152f4d23d..41d1404b68 100644 --- a/nix/tools/tests.nix +++ b/nix/tools/tests.nix @@ -5,7 +5,7 @@ , ghc , glibcLocales , gnugrep -, haskell +, haskellPackages , hpc-codecov , jq , postgrest @@ -116,7 +116,7 @@ let checkedShellScript { name = "postgrest-coverage"; - docs = "Run spec and io tests while collecting hpc coverage data."; + docs = "Run spec and io tests while collecting hpc coverage data. First runs weeder to detect dead code."; args = [ "ARG_LEFTOVERS([hpc report arguments])" ]; inRootDir = true; redirectTixFiles = false; @@ -133,6 +133,8 @@ let # build once before running all the tests ${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest lib:postgrest test:spec test:querycost + ${haskellPackages.weeder}/bin/weeder --config=./test/weeder.dhall || echo Found dead code: Check file list above. + # collect all tests HPCTIXFILE="$tmpdir"/io.tix \ ${withTools.withPg} ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} \ diff --git a/postgrest.cabal b/postgrest.cabal index f182909488..871630cb00 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -123,7 +123,7 @@ library -fno-spec-constr -optP-Wno-nonportable-include-path if flag(dev) - ghc-options: -O0 + ghc-options: -O0 -fwrite-ide-info if flag(hpc) ghc-options: -fhpc -hpcdir .hpc else @@ -152,7 +152,7 @@ executable postgrest -fno-spec-constr -optP-Wno-nonportable-include-path if flag(dev) - ghc-options: -O0 + ghc-options: -O0 -fwrite-ide-info if flag(hpc) ghc-options: -fhpc -hpcdir .hpc else @@ -237,6 +237,7 @@ test-suite spec ghc-options: -O0 -Werror -Wall -fwarn-identities -fno-spec-constr -optP-Wno-nonportable-include-path -fno-warn-missing-signatures + -fwrite-ide-info test-suite querycost type: exitcode-stdio-1.0 @@ -281,6 +282,7 @@ test-suite querycost , wai-extra >= 3.0.19 && < 3.2 ghc-options: -O0 -Werror -Wall -fwarn-identities -fno-spec-constr -optP-Wno-nonportable-include-path + -fwrite-ide-info test-suite doctests type: exitcode-stdio-1.0 diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index f13467569d..f9411f8f8a 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -1,6 +1,6 @@ module SpecHelper where -import qualified Data.ByteString.Base64 as B64 (decodeLenient, encode) +import qualified Data.ByteString.Base64 as B64 (decodeLenient) import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy as BL import qualified Data.Map.Strict as M @@ -189,17 +189,10 @@ testMultipleSchemaCfg testDbConn = (testCfg testDbConn) { configDbSchemas = from testCfgLegacyGucs :: Text -> AppConfig testCfgLegacyGucs testDbConn = (testCfg testDbConn) { configDbUseLegacyGucs = False } -resetDb :: Text -> IO () -resetDb dbConn = loadFixture dbConn "data" - analyzeTable :: Text -> Text -> IO () analyzeTable dbConn tableName = void $ readProcess "psql" ["--set", "ON_ERROR_STOP=1", toS dbConn, "-a", "-c", toS $ "ANALYZE test.\"" <> tableName <> "\""] [] -loadFixture :: Text -> FilePath -> IO() -loadFixture dbConn name = - void $ readProcess "psql" ["--set", "ON_ERROR_STOP=1", toS dbConn, "-q", "-f", "test/fixtures/" ++ name ++ ".sql"] [] - rangeHdrs :: ByteRange -> [Header] rangeHdrs r = [rangeUnit, (hRange, renderByteRange r)] @@ -226,10 +219,6 @@ authHeader :: BS.ByteString -> BS.ByteString -> Header authHeader typ creds = (hAuthorization, typ <> " " <> creds) -authHeaderBasic :: BS.ByteString -> BS.ByteString -> Header -authHeaderBasic u p = - authHeader "Basic" $ toS . B64.encode . toS $ u <> ":" <> p - authHeaderJWT :: BS.ByteString -> Header authHeaderJWT = authHeader "Bearer" diff --git a/test/weeder.dhall b/test/weeder.dhall new file mode 100644 index 0000000000..683e34bddc --- /dev/null +++ b/test/weeder.dhall @@ -0,0 +1,4 @@ +{ + roots = [ "^Main.main$", "^Paths_" ], + type-class-roots = True +} From cbeb7ab5d5cc9cba9b4c8909c8e35d9f7d2ac283 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 28 Nov 2021 21:19:02 +0100 Subject: [PATCH 11/62] nix(feat): Add postgrest-git-hooks to enable pre-commit and pre-push hooks --- nix/tools/devTools.nix | 153 +++++++++++++++++++++++++++++++++++++++-- nix/tools/style.nix | 2 + 2 files changed, 149 insertions(+), 6 deletions(-) diff --git a/nix/tools/devTools.nix b/nix/tools/devTools.nix index 8b90f1fcbf..808bebeaee 100644 --- a/nix/tools/devTools.nix +++ b/nix/tools/devTools.nix @@ -4,6 +4,8 @@ , checkedShellScript , devCabalOptions , entr +, git +, gnugrep , graphviz , haskellPackages , hsie @@ -65,23 +67,161 @@ let name = "postgrest-check"; docs = '' - Run most checks that will also run on CI. + Run most checks that will also run on CI, but only against the + latest PostgreSQL version. - This currently excludes the memory tests, as those are particularly - expensive. + This currently excludes the memory and spec-idempotence tests, + as those are particularly expensive. ''; inRootDir = true; } '' - ${withTools.withPgAll} ${tests}/bin/postgrest-test-spec - ${withTools.withPgAll} ${tests}/bin/postgrest-test-querycost + ${tests}/bin/postgrest-test-spec + ${tests}/bin/postgrest-test-querycost ${tests}/bin/postgrest-test-doctests - ${tests}/bin/postgrest-test-spec-idempotence ${tests}/bin/postgrest-test-io ${style}/bin/postgrest-lint ${style}/bin/postgrest-style-check ''; + gitHooks = + let + name = "postgrest-git-hooks"; + in + checkedShellScript + { + inherit name; + docs = + '' + Enable or disable git pre-commit and pre-push hooks. + + Basic is faster and will only run: + - pre-commit: postgrest-style + - pre-push: postgrest-lint + + Full takes a lot more time and will run: + - pre-commit: postgrest-style && postgrest-lint + - pre-push: postgrest-check + + Changes made by postgrest-style will be staged automatically. + + Example usage: + postgrest-git-hooks disable + postgrest-git-hooks enable basic + postgrest-git-hooks enable full + + The "run" operation and "--hook" argument are only used internally. + ''; + args = + [ + "ARG_POSITIONAL_SINGLE([operation], [Operation])" + "ARG_TYPE_GROUP_SET([OPERATION], [OPERATION], [operation], [disable,enable,run])" + "ARG_POSITIONAL_SINGLE([mode], [Mode], [basic])" + "ARG_TYPE_GROUP_SET([MODE], [MODE], [mode], [basic,full])" + "ARG_OPTIONAL_SINGLE([hook], , [Hook], [pre-commit])" + "ARG_TYPE_GROUP_SET([HOOK], [HOOK], [hook], [pre-commit,pre-push])" + ]; + positionalCompletion = + '' + if test "$prev" == "${name}"; then + COMPREPLY=( $(compgen -W "enable disable" -- "$cur") ) + elif test "$prev" == "enable" || test "$prev" == "disable"; then + COMPREPLY=( $(compgen -W "basic full" -- "$cur") ) + fi + ''; + inRootDir = true; + } + '' + if [ run != "$_arg_operation" ]; then + # Remove all hooks first and ignore failures because the file might be missing. + # This assumes that we're only adding lines that include "postgrest-git-hooks" + # to the hook file. + sed -i -e '/postgrest-git-hooks/d' .git/hooks/pre-{commit,push} 2> /dev/null || true + + if [ disabled != "$_arg_mode" ]; then + # The nix-shell && + nix-shell || pattern makes sure we can run the hook + # in a pure nix-shell, where nix-shell itself is not available, too. + + # The $(nix-shell --run "command -v ...") pattern ensures we only need to enable + # the hooks once and still run the latest of our hook scripts, even when we + # update them in the repo. + + echo 'command -v nix-shell > /dev/null || postgrest-git-hooks --hook=pre-commit run' "$_arg_mode" \ + >> .git/hooks/pre-commit + # shellcheck disable=SC2016 + echo 'command -v nix-shell > /dev/null && $(nix-shell --quiet -Q --run "command -v postgrest-git-hooks") --hook=pre-commit run' "$_arg_mode" \ + >> .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + + echo 'command -v nix-shell > /dev/null || postgrest-git-hooks --hook=pre-push run' "$_arg_mode" \ + >> .git/hooks/pre-push + # shellcheck disable=SC2016 + echo 'command -v nix-shell > /dev/null && $(nix-shell --quiet -Q --run "command -v postgrest-git-hooks") --hook=pre-push run' "$_arg_mode" \ + >> .git/hooks/pre-push + chmod +x .git/hooks/pre-push + fi + else + # When run from a git hook, the GIT_ environment variables conflict with our withGit helper. + # The following unsets all GIT_ variables. + unset "''${!GIT_@}" + + case "$_arg_mode" in + basic) + case "$_arg_hook" in + pre-commit) + # To be able to automatically add only changes from postgrest-style to the staging area, + # we need to run postgrest-style twice. Otherwise we'd risk merge conflicts when popping + # the stash afterwards. + ${style}/bin/postgrest-style + + stash="postgrest-git-hooks-$RANDOM" + ${git}/bin/git stash push --include-untracked --keep-index -m "$stash" + if [ "$(git stash list --grep $stash)" ]; then + # Only create the stash pop trap, if we actually created a stash. + # Otherwise stash pop will cause havoc. + trap '${git}/bin/git stash pop $(git stash list --format=format:%gD --grep "$stash" -n1)' EXIT + fi + + ${style}/bin/postgrest-style + ${git}/bin/git add . + ;; + pre-push) + # Create a clean working tree without any uncomitted changes. + ${withTools.withGit} HEAD ${style}/bin/postgrest-lint + ;; + esac + ;; + full) + case "$_arg_hook" in + pre-commit) + # To be able to automatically add only changes from postgrest-style to the staging area, + # we need to run postgrest-style twice. Otherwise we'd risk merge conflicts when popping + # the stash afterwards. + ${style}/bin/postgrest-style + + stash="postgrest-git-hooks-$RANDOM" + ${git}/bin/git stash push --include-untracked --keep-index -m "$stash" + if [ "$(git stash list --grep $stash)" ]; then + # Only create the stash pop trap, if we actually created a stash. + # Otherwise stash pop will cause havoc. + trap '${git}/bin/git stash pop $(git stash list --format=format:%gD --grep "$stash" -n1)' EXIT + fi + + ${style}/bin/postgrest-style + ${git}/bin/git add . + + ${style}/bin/postgrest-lint + ;; + pre-push) + # Create a clean working tree without any uncomitted changes. + ${withTools.withGit} HEAD ${check} + ;; + esac + ;; + esac + fi + ''; + dumpMinimalImports = checkedShellScript { @@ -147,6 +287,7 @@ buildToolbox watch pushCachix check + gitHooks dumpMinimalImports hsieMinimalImports hsieGraphModules diff --git a/nix/tools/style.nix b/nix/tools/style.nix index 13adc1b9bd..7b55c93857 100644 --- a/nix/tools/style.nix +++ b/nix/tools/style.nix @@ -44,6 +44,8 @@ let '' ${style} + trap "echo postgrest-style-check failed. Run postgrest-style to fix issues automatically." ERR + ${git}/bin/git diff-index --exit-code HEAD -- '*.hs' '*.lhs' '*.nix' ''; From 199714c63c697151455f6c99dcc42760691a9b65 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 10 Dec 2021 22:53:06 +0100 Subject: [PATCH 12/62] fix: Dump db-config option without quotes as it's a boolean --- src/PostgREST/Config.hs | 2 +- test/io-tests/configs/expected/aliases.config | 2 +- test/io-tests/configs/expected/boolean-numeric.config | 2 +- test/io-tests/configs/expected/boolean-string.config | 2 +- test/io-tests/configs/expected/defaults.config | 2 +- .../expected/no-defaults-with-db-other-authenticator.config | 2 +- test/io-tests/configs/expected/no-defaults-with-db.config | 2 +- test/io-tests/configs/expected/no-defaults.config | 2 +- test/io-tests/configs/expected/types.config | 2 +- test/io-tests/configs/no-defaults.config | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 6f8c19abe9..d5d63cd92f 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -131,7 +131,7 @@ toText conf = ,("db-prepared-statements", T.toLower . show . configDbPreparedStatements) ,("db-root-spec", q . maybe mempty dumpQi . configDbRootSpec) ,("db-schemas", q . T.intercalate "," . toList . configDbSchemas) - ,("db-config", q . T.toLower . show . configDbConfig) + ,("db-config", T.toLower . show . configDbConfig) ,("db-tx-end", q . showTxEnd) ,("db-uri", q . configDbUri) ,("db-use-legacy-gucs", T.toLower . show . configDbUseLegacyGucs) diff --git a/test/io-tests/configs/expected/aliases.config b/test/io-tests/configs/expected/aliases.config index b8cd55fd94..24d89f4621 100644 --- a/test/io-tests/configs/expected/aliases.config +++ b/test/io-tests/configs/expected/aliases.config @@ -9,7 +9,7 @@ db-pre-request = "check_alias" db-prepared-statements = true db-root-spec = "open_alias" db-schemas = "provided_through_alias" -db-config = "false" +db-config = false db-tx-end = "commit" db-uri = "required" db-use-legacy-gucs = true diff --git a/test/io-tests/configs/expected/boolean-numeric.config b/test/io-tests/configs/expected/boolean-numeric.config index cc67e08904..819368bb2a 100644 --- a/test/io-tests/configs/expected/boolean-numeric.config +++ b/test/io-tests/configs/expected/boolean-numeric.config @@ -9,7 +9,7 @@ db-pre-request = "" db-prepared-statements = false db-root-spec = "" db-schemas = "required" -db-config = "false" +db-config = false db-tx-end = "commit" db-uri = "required" db-use-legacy-gucs = true diff --git a/test/io-tests/configs/expected/boolean-string.config b/test/io-tests/configs/expected/boolean-string.config index cc67e08904..819368bb2a 100644 --- a/test/io-tests/configs/expected/boolean-string.config +++ b/test/io-tests/configs/expected/boolean-string.config @@ -9,7 +9,7 @@ db-pre-request = "" db-prepared-statements = false db-root-spec = "" db-schemas = "required" -db-config = "false" +db-config = false db-tx-end = "commit" db-uri = "required" db-use-legacy-gucs = true diff --git a/test/io-tests/configs/expected/defaults.config b/test/io-tests/configs/expected/defaults.config index 792b60e070..14b97dee9e 100644 --- a/test/io-tests/configs/expected/defaults.config +++ b/test/io-tests/configs/expected/defaults.config @@ -9,7 +9,7 @@ db-pre-request = "" db-prepared-statements = true db-root-spec = "" db-schemas = "required" -db-config = "false" +db-config = false db-tx-end = "commit" db-uri = "required" db-use-legacy-gucs = true diff --git a/test/io-tests/configs/expected/no-defaults-with-db-other-authenticator.config b/test/io-tests/configs/expected/no-defaults-with-db-other-authenticator.config index f40efe775f..ff5ec9676a 100644 --- a/test/io-tests/configs/expected/no-defaults-with-db-other-authenticator.config +++ b/test/io-tests/configs/expected/no-defaults-with-db-other-authenticator.config @@ -9,7 +9,7 @@ db-pre-request = "test.other_custom_headers" db-prepared-statements = false db-root-spec = "other_root" db-schemas = "test,other_tenant1,other_tenant2" -db-config = "true" +db-config = true db-tx-end = "rollback-allow-override" db-uri = "" db-use-legacy-gucs = false diff --git a/test/io-tests/configs/expected/no-defaults-with-db.config b/test/io-tests/configs/expected/no-defaults-with-db.config index 82850fca58..4159b6b4da 100644 --- a/test/io-tests/configs/expected/no-defaults-with-db.config +++ b/test/io-tests/configs/expected/no-defaults-with-db.config @@ -9,7 +9,7 @@ db-pre-request = "test.custom_headers" db-prepared-statements = false db-root-spec = "root" db-schemas = "test,tenant1,tenant2" -db-config = "true" +db-config = true db-tx-end = "commit-allow-override" db-uri = "" db-use-legacy-gucs = false diff --git a/test/io-tests/configs/expected/no-defaults.config b/test/io-tests/configs/expected/no-defaults.config index 8723c036a9..e782643fba 100644 --- a/test/io-tests/configs/expected/no-defaults.config +++ b/test/io-tests/configs/expected/no-defaults.config @@ -9,7 +9,7 @@ db-pre-request = "please_run_fast" db-prepared-statements = false db-root-spec = "openapi_v3" db-schemas = "multi,tenant,setup" -db-config = "false" +db-config = false db-tx-end = "rollback-allow-override" db-uri = "tmp_db" db-use-legacy-gucs = false diff --git a/test/io-tests/configs/expected/types.config b/test/io-tests/configs/expected/types.config index b2b8589895..f77e97cc00 100644 --- a/test/io-tests/configs/expected/types.config +++ b/test/io-tests/configs/expected/types.config @@ -9,7 +9,7 @@ db-pre-request = "" db-prepared-statements = true db-root-spec = "" db-schemas = "required" -db-config = "true" +db-config = true db-tx-end = "commit" db-uri = "required" db-use-legacy-gucs = true diff --git a/test/io-tests/configs/no-defaults.config b/test/io-tests/configs/no-defaults.config index 01cd53a731..8284a74c20 100644 --- a/test/io-tests/configs/no-defaults.config +++ b/test/io-tests/configs/no-defaults.config @@ -9,7 +9,7 @@ db-pre-request = "please_run_fast" db-prepared-statements = false db-root-spec = "openapi_v3" db-schemas = "multi, tenant,setup" -db-config = "false" +db-config = false db-tx-end = "rollback-allow-override" db-uri = "tmp_db" db-use-legacy-gucs = false From 610ef2d1fb38680cb7d42ac88e1347144b6706a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 04:16:54 +0000 Subject: [PATCH 13/62] build(deps): bump actions/download-artifact from 2.0.10 to 2.1.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2.0.10 to 2.1.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2.0.10...v2.1.0) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 286cacaf91..f2d17b4646 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -292,7 +292,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Download all artifacts - uses: actions/download-artifact@v2.0.10 + uses: actions/download-artifact@v2.1.0 with: path: artifacts - name: Create release bundle with archives for all builds @@ -355,7 +355,7 @@ jobs: with: tools: release - name: Download Docker image - uses: actions/download-artifact@v2.0.10 + uses: actions/download-artifact@v2.1.0 with: name: postgrest-docker-x64 - name: Publish images on Docker Hub From c99ecb6374e07e7b03bd7422ffca2498870a7d90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 04:16:50 +0000 Subject: [PATCH 14/62] build(deps): bump actions/upload-artifact from 2.2.4 to 2.3.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.2.4 to 2.3.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2.2.4...v2.3.0) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 12 ++++++------ .github/workflows/loadtest.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f2d17b4646..2cfe9caf87 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -115,7 +115,7 @@ jobs: - name: Build static executable run: nix-build -A postgrestStatic - name: Save built executable as artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: postgrest-linux-static-x64 path: result/bin/postgrest @@ -124,7 +124,7 @@ jobs: - name: Build Docker image run: nix-build -A docker.image --out-link postgrest-docker.tar.gz - name: Save built Docker image as artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: postgrest-docker-x64 path: postgrest-docker.tar.gz @@ -194,7 +194,7 @@ jobs: echo "Using PostgreSQL binaries at $postgresql_bin ..." PATH="$postgresql_bin:$PATH" test/with_tmp_db stack test - name: Save built executable as artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: ${{ matrix.artifact }} path: | @@ -214,7 +214,7 @@ jobs: GITHUB_COMMIT: ${{github.event.pull_request.head.sha || github.sha}} run: .github/get_cirrusci_freebsd - name: Save executable as artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: postgrest-freebsd-x64 path: postgrest @@ -274,7 +274,7 @@ jobs: echo "Relevant extract from CHANGELOG.md:" cat CHANGES.md - name: Save CHANGES.md as artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: release-changes path: CHANGES.md @@ -319,7 +319,7 @@ jobs: artifacts/postgrest-windows-x64/postgrest.exe - name: Save release bundle - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: release-bundle path: release-bundle diff --git a/.github/workflows/loadtest.yaml b/.github/workflows/loadtest.yaml index 18cba2df4a..56e2043a38 100644 --- a/.github/workflows/loadtest.yaml +++ b/.github/workflows/loadtest.yaml @@ -27,7 +27,7 @@ jobs: postgrest-loadtest-against main postgrest-loadtest-report > loadtest/loadtest.md - name: Upload report - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: loadtest.md path: loadtest/loadtest.md From f0fcf2aff7c1f6f8aa335caa4a1053e968bf08ca Mon Sep 17 00:00:00 2001 From: Steve Chavez Date: Tue, 14 Dec 2021 19:13:38 -0500 Subject: [PATCH 15/62] fix: `is` not working with upper/mixed case values (#2081) --- CHANGELOG.md | 2 ++ src/PostgREST/Request/Parsers.hs | 14 ++++++++++---- test/Feature/QuerySpec.hs | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1788c3a2bb..ed9c6f07c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther + - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther + - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/Request/Parsers.hs b/src/PostgREST/Request/Parsers.hs index 4a8db797f9..fb2707fbcf 100644 --- a/src/PostgREST/Request/Parsers.hs +++ b/src/PostgREST/Request/Parsers.hs @@ -199,10 +199,10 @@ pOpExpr pSVal = try ( string "not" *> pDelimiter *> (OpExpr True <$> pOperation) <|> pFts "operator (eq, gt, ...)" - pTriVal = try (string "null" $> TriNull) - <|> try (string "unknown" $> TriUnknown) - <|> try (string "true" $> TriTrue) - <|> try (string "false" $> TriFalse) + pTriVal = try (ciString "null" $> TriNull) + <|> try (ciString "unknown" $> TriUnknown) + <|> try (ciString "true" $> TriTrue) + <|> try (ciString "false" $> TriFalse) "null or trilean value (unknown, true, false)" pFts = do @@ -213,6 +213,12 @@ pOpExpr pSVal = try ( string "not" *> pDelimiter *> (OpExpr True <$> pOperation) ops = M.filterWithKey (const . flip notElem ("in":"is":ftsOps)) operators ftsOps = M.keys ftsOperators + -- case insensitive char and string + ciChar :: Char -> GenParser Char state Char + ciChar c = char c <|> char (toUpper c) + ciString :: [Char] -> GenParser Char state [Char] + ciString = traverse ciChar + pSingleVal :: Parser SingleVal pSingleVal = toS <$> many anyChar diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 722deea40c..23e077f663 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -85,6 +85,23 @@ spec actualPgVersion = do [json| [{"id": 3, "name": "wash the dishes", "done": null }] |] { matchHeaders = [matchContentTypeJson] } + it "matches with trilean values in upper or mixed case" $ do + get "/chores?done=is.NULL" `shouldRespondWith` + [json| [{"id": 3, "name": "wash the dishes", "done": null }] |] + { matchHeaders = [matchContentTypeJson] } + + get "/chores?done=is.TRUE" `shouldRespondWith` + [json| [{"id": 1, "name": "take out the garbage", "done": true }] |] + { matchHeaders = [matchContentTypeJson] } + + get "/chores?done=is.FAlSe" `shouldRespondWith` + [json| [{"id": 2, "name": "do the laundry", "done": false }] |] + { matchHeaders = [matchContentTypeJson] } + + get "/chores?done=is.UnKnOwN" `shouldRespondWith` + [json| [{"id": 3, "name": "wash the dishes", "done": null }] |] + { matchHeaders = [matchContentTypeJson] } + it "fails if 'is' used and there's no null or trilean value" $ do get "/chores?done=is.nil" `shouldRespondWith` 400 get "/chores?done=is.ok" `shouldRespondWith` 400 From 98cbfc13b274dfd57f82269c29573349c5028da4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 10:54:13 -0500 Subject: [PATCH 16/62] build(deps): bump actions/upload-artifact from 2.3.0 to 2.3.1 (#2094) --- .github/workflows/ci.yaml | 12 ++++++------ .github/workflows/loadtest.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2cfe9caf87..efced73a08 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -115,7 +115,7 @@ jobs: - name: Build static executable run: nix-build -A postgrestStatic - name: Save built executable as artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: postgrest-linux-static-x64 path: result/bin/postgrest @@ -124,7 +124,7 @@ jobs: - name: Build Docker image run: nix-build -A docker.image --out-link postgrest-docker.tar.gz - name: Save built Docker image as artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: postgrest-docker-x64 path: postgrest-docker.tar.gz @@ -194,7 +194,7 @@ jobs: echo "Using PostgreSQL binaries at $postgresql_bin ..." PATH="$postgresql_bin:$PATH" test/with_tmp_db stack test - name: Save built executable as artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: ${{ matrix.artifact }} path: | @@ -214,7 +214,7 @@ jobs: GITHUB_COMMIT: ${{github.event.pull_request.head.sha || github.sha}} run: .github/get_cirrusci_freebsd - name: Save executable as artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: postgrest-freebsd-x64 path: postgrest @@ -274,7 +274,7 @@ jobs: echo "Relevant extract from CHANGELOG.md:" cat CHANGES.md - name: Save CHANGES.md as artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: release-changes path: CHANGES.md @@ -319,7 +319,7 @@ jobs: artifacts/postgrest-windows-x64/postgrest.exe - name: Save release bundle - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: release-bundle path: release-bundle diff --git a/.github/workflows/loadtest.yaml b/.github/workflows/loadtest.yaml index 56e2043a38..8bcedac39d 100644 --- a/.github/workflows/loadtest.yaml +++ b/.github/workflows/loadtest.yaml @@ -27,7 +27,7 @@ jobs: postgrest-loadtest-against main postgrest-loadtest-report > loadtest/loadtest.md - name: Upload report - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: loadtest.md path: loadtest/loadtest.md From fe5f24d6d7d3f3265e2f5b9563e41c60344f1bf4 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 24 Dec 2021 01:52:17 +0100 Subject: [PATCH 17/62] fix: Make recursive view parsing work with XMLTABLE + DEFAULT --- CHANGELOG.md | 1 + src/PostgREST/DbStructure.hs | 14 ++++++++------ test/fixtures/schema.sql | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9c6f07c5..9d37017084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez + - #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 87b48c8c43..721a10ea1c 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -792,7 +792,6 @@ pfkSourceColumns cols = replace( replace( replace( - replace( regexp_replace( replace( replace( @@ -803,6 +802,7 @@ pfkSourceColumns cols = replace( replace( replace( + replace( replace( view_definition::text, -- This conversion to json is heavily optimized for performance. @@ -814,9 +814,15 @@ pfkSourceColumns cols = -- ----------------------------------------------- -- pattern | replacement | flags -- ----------------------------------------------- + -- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof + -- we need to make this an empty array here to prevent json_array_elements from throwing an error + -- when the targetList is null. + -- We'll need to put it first, to make the node protection below work for node lists that start with + -- null: `(<> ...`, too. This is the case for coldefexprs, when the first column does not have a default value. + '<>' , '()' -- `,` is not part of the pg_node_tree format, but used in the regex. -- This removes all `,` that might be part of column names. - ',' , '' + ), ',' , '' -- The same applies for `{` and `}`, although those are used a lot in pg_node_tree. -- We remove the escaped ones, which might be part of column names again. ), E'\\{' , '' @@ -851,10 +857,6 @@ pfkSourceColumns cols = ), ')' , ']' -- pg_node_tree has ` ` between list items, but JSON uses `,` ), ' ' , ',' - -- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof - -- we need to make this an empty array here to prevent json_array_elements from throwing an error - -- when the targetList is null. - ), '<>' , '[]' )::json as view_definition from views ), diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index bb92f174d2..c13b88354e 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -2462,3 +2462,21 @@ BEGIN INSERT INTO deferrable_unique_constraint VALUES (1), (1); END$$; + +-- This view is not used in any requests but just parsed by the pfkSourceColumns query. +-- XMLTABLE is only supported from PG 10 on +DO $do$ +BEGIN + IF current_setting('server_version_num')::INT >= 100000 THEN + CREATE VIEW test.xml AS + SELECT * + FROM (SELECT ''::xml AS data) _, + XMLTABLE( + '' + PASSING data + COLUMNS id int PATH '@id', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified' + ); + END IF; +END +$do$; From e2513cb43dce6972d34d7d8e317a7c63a35c846a Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 4 Jan 2021 00:07:53 +0100 Subject: [PATCH 18/62] fix: Fix wrong CORS header Authentication -> Authorization Also refactors defaultCorsPolicy and corsPolicy and cleans up CORS tests --- CHANGELOG.md | 1 + src/PostgREST/Middleware.hs | 26 ++++----- test/Feature/CorsSpec.hs | 106 +++++++++++++++--------------------- 3 files changed, 57 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d37017084..458cc1ca7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez - #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther + - #1724, Fix wrong CORS header Authentication -> Authorization - @wolfgangwalther ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index fb64f38cad..e302388ce2 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -8,8 +8,6 @@ module PostgREST.Middleware ( runPgLocals , pgrstFormat , pgrstMiddleware - , defaultCorsPolicy - , corsPolicy , optionalRollback ) where @@ -132,21 +130,21 @@ pgrstMiddleware logLevel = LogWarn -> unsafePerformIO $ Wai.mkRequestLogger Wai.def { Wai.outputFormat = Wai.CustomOutputFormat $ pgrstFormat status400} LogInfo -> Wai.logStdout -defaultCorsPolicy :: Wai.CorsResourcePolicy -defaultCorsPolicy = Wai.CorsResourcePolicy Nothing - ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"] ["Authorization"] Nothing - (Just $ 60*60*24) False False True - -- | CORS policy to be used in by Wai Cors middleware corsPolicy :: Wai.Request -> Maybe Wai.CorsResourcePolicy corsPolicy req = case lookup "origin" headers of - Just origin -> Just defaultCorsPolicy { - Wai.corsOrigins = Just ([origin], True) - , Wai.corsRequestHeaders = "Authentication" : accHeaders - , Wai.corsExposedHeaders = Just [ - "Content-Encoding", "Content-Location", "Content-Range", "Content-Type" - , "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit" - ] + Just origin -> + Just Wai.CorsResourcePolicy + { Wai.corsOrigins = Just ([origin], True) + , Wai.corsMethods = ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"] + , Wai.corsRequestHeaders = "Authorization" : accHeaders + , Wai.corsExposedHeaders = Just + [ "Content-Encoding", "Content-Location", "Content-Range", "Content-Type" + , "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit"] + , Wai.corsMaxAge = Just $ 60*60*24 + , Wai.corsVaryOrigin = False + , Wai.corsRequireOrigin = False + , Wai.corsIgnoreFailures = True } Nothing -> Nothing where diff --git a/test/Feature/CorsSpec.hs b/test/Feature/CorsSpec.hs index cdadef6381..a411238708 100644 --- a/test/Feature/CorsSpec.hs +++ b/test/Feature/CorsSpec.hs @@ -1,74 +1,56 @@ module Feature.CorsSpec where --- {{{ Imports -import qualified Data.ByteString.Lazy as BL - -import Network.Wai (Application) -import Network.Wai.Test (SResponse (simpleBody, simpleHeaders)) +import Network.Wai (Application) import Network.HTTP.Types import Test.Hspec import Test.Hspec.Wai import Protolude -import SpecHelper --- }}} spec :: SpecWith ((), Application) spec = describe "CORS" $ do - let preflightHeaders = [ - ("Accept", "*/*"), - ("Origin", "http://example.com"), - ("Access-Control-Request-Method", "POST"), - ("Access-Control-Request-Headers", "Foo,Bar") ] - let normalCors = [ - ("Host", "localhost:3000"), - ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0"), - ("Origin", "http://localhost:8000"), - ("Accept", "text/csv, */*; q=0.01"), - ("Accept-Language", "en-US,en;q=0.5"), - ("Accept-Encoding", "gzip, deflate"), - ("Referer", "http://localhost:8000/"), - ("Connection", "keep-alive") ] - - describe "preflight request" $ do - it "replies naively and permissively to preflight request" $ do - r <- request methodOptions "/items" preflightHeaders "" - liftIO $ do - let respHeaders = simpleHeaders r - respHeaders `shouldSatisfy` matchHeader - "Access-Control-Allow-Origin" - "http://example.com" - respHeaders `shouldSatisfy` matchHeader - "Access-Control-Allow-Credentials" - "true" - respHeaders `shouldSatisfy` matchHeader - "Access-Control-Allow-Methods" - "GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD" - respHeaders `shouldSatisfy` matchHeader - "Access-Control-Allow-Headers" - "Authentication, Foo, Bar, Accept, Accept-Language, Content-Language" - respHeaders `shouldSatisfy` matchHeader - "Access-Control-Max-Age" - "86400" - - it "suppresses body in response" $ do - r <- request methodOptions "/" preflightHeaders "" - liftIO $ simpleBody r `shouldBe` "" - - describe "regular request" $ - it "exposes necesssary response headers" $ do - r <- request methodGet "/items" [("Origin", "http://example.com")] "" - liftIO $ simpleHeaders r `shouldSatisfy` matchHeader - "Access-Control-Expose-Headers" - "Content-Encoding, Content-Location, Content-Range, Content-Type, \ - \Date, Location, Server, Transfer-Encoding, Range-Unit" - - describe "postflight request" $ - it "allows INFO body through even with CORS request headers present" $ do - r <- request methodOptions "/items" normalCors "" - liftIO $ do - simpleHeaders r `shouldSatisfy` matchHeader - "Access-Control-Allow-Origin" "\\*" - simpleBody r `shouldSatisfy` BL.null + it "replies naively and permissively to preflight request" $ + request methodOptions "/" + [ ("Accept", "*/*") + , ("Origin", "http://example.com") + , ("Access-Control-Request-Method", "POST") + , ("Access-Control-Request-Headers", "Foo,Bar") ] + "" + `shouldRespondWith` + "" + { matchHeaders = [ "Access-Control-Allow-Origin" <:> "http://example.com" + , "Access-Control-Allow-Credentials" <:> "true" + , "Access-Control-Allow-Methods" <:> "GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD" + , "Access-Control-Allow-Headers" <:> "Authorization, Foo, Bar, Accept, Accept-Language, Content-Language" + , "Access-Control-Max-Age" <:> "86400" ] + } + + it "exposes necesssary response headers to regular request" $ + request methodGet "/items" + [("Origin", "http://example.com")] + "" + `shouldRespondWith` + ResponseMatcher + { matchStatus = 200 + , matchBody = MatchBody (\_ _ -> Nothing) -- match any body + , matchHeaders = [ "Access-Control-Expose-Headers" <:> + "Content-Encoding, Content-Location, Content-Range, Content-Type, \ + \Date, Location, Server, Transfer-Encoding, Range-Unit"] + } + + it "allows INFO body through even with CORS request headers present to postflight request" $ + request methodOptions "/items" + [ ("Host", "localhost:3000") + , ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0") + , ("Origin", "http://localhost:8000") + , ("Accept", "text/csv, */*; q=0.01") + , ("Accept-Language", "en-US,en;q=0.5") + , ("Accept-Encoding", "gzip, deflate") + , ("Referer", "http://localhost:8000/") + , ("Connection", "keep-alive") ] + "" + `shouldRespondWith` + "" + { matchHeaders = [ "Access-Control-Allow-Origin" <:> "*" ] } From 474167da382650023b6bcf91dca2b765ad3f2663 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 4 Jan 2021 00:18:55 +0100 Subject: [PATCH 19/62] cov: Improve jwt claims test-cases --- src/PostgREST/Middleware.hs | 14 +++++++------- test/io-tests/fixtures.yaml | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index e302388ce2..9dc3123a85 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -89,6 +89,13 @@ runPgLocals conf claims app req jsonDbS actualPgVersion = do _ -> mempty usesLegacyGucs = configDbUseLegacyGucs conf && actualPgVersion < pgVersion140 + unquoted :: JSON.Value -> Text + unquoted (JSON.String t) = t + unquoted (JSON.Number n) = + toS $ formatScientific Fixed (if isInteger n then Just 0 else Nothing) n + unquoted (JSON.Bool b) = show b + unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v + -- | Log in apache format. Only requests that have a status greater than minStatus are logged. -- | There's no way to filter logs in the apache format on wai-extra: https://hackage.haskell.org/package/wai-extra-3.0.29.2/docs/Network-Wai-Middleware-RequestLogger.html#t:OutputFormat. -- | So here we copy wai-logger apacheLogStr function: https://github.com/kazu-yamamoto/logger/blob/a4f51b909a099c51af7a3f75cf16e19a06f9e257/wai-logger/Network/Wai/Logger/Apache.hs#L45 @@ -153,13 +160,6 @@ corsPolicy req = case lookup "origin" headers of Just hdrs -> map (CI.mk . BS.strip) $ BS.split ',' hdrs Nothing -> [] -unquoted :: JSON.Value -> Text -unquoted (JSON.String t) = t -unquoted (JSON.Number n) = - toS $ formatScientific Fixed (if isInteger n then Just 0 else Nothing) n -unquoted (JSON.Bool b) = show b -unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v - -- | Set a transaction to eventually roll back if requested and set respective -- headers on the response. optionalRollback diff --git a/test/io-tests/fixtures.yaml b/test/io-tests/fixtures.yaml index 7db1771f58..d13010aea1 100644 --- a/test/io-tests/fixtures.yaml +++ b/test/io-tests/fixtures.yaml @@ -144,6 +144,7 @@ roleclaims: data: postgrest: a_role: postgrest_test_author + other: claims expected_status: 200 - key: '.customObject.manyRoles[1]' data: @@ -151,21 +152,25 @@ roleclaims: manyRoles: - other - postgrest_test_author + other: {} expected_status: 200 - key: '."https://www.example.com/roles"[0].value' data: 'https://www.example.com/roles': - value: postgrest_test_author + other: 666 expected_status: 200 - key: '.myDomain[3]' data: myDomain: - other - postgrest_test_author + other: 1.23 expected_status: 401 - key: '.myRole' data: role: postgrest_test_author + other: true expected_status: 401 invalidroleclaimkeys: From fcc6b660706aa76db73eddce64637abd22195813 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 26 Nov 2021 20:34:10 +0100 Subject: [PATCH 20/62] Remove outdated heroku manifest --- README.md | 3 --- app.json | 59 ------------------------------------------------------- 2 files changed, 62 deletions(-) delete mode 100644 app.json diff --git a/README.md b/README.md index 720b85d711..ae9df78100 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,6 @@ [![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/postgrest) - - Deploy - [![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/postgrest) [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org) [![Docker Stars](https://img.shields.io/docker/pulls/postgrest/postgrest.svg)](https://hub.docker.com/r/postgrest/postgrest/) diff --git a/app.json b/app.json deleted file mode 100644 index 24dc344995..0000000000 --- a/app.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "PostgREST", - "description": "RESTful API for any PostgreSQL database.", - "logo": "https://avatars2.githubusercontent.com/u/15115011", - "repository": "https://github.com/PostgREST/postgrest", - "env": { - "BUILDPACK_URL": { - "description": "Heroku buildpack for deploying Haskell applications", - "value": "https://github.com/PostgREST/postgrest-heroku" - }, - "POSTGREST_VER": { - "description": "Version of PostgREST to deploy", - "value": "8.0.0" - }, - "DB_URI": { - "description": "Database connection string, e.g. postgres://user:pass@xxxxxxx.rds.amazonaws.com/mydb", - "required": true - }, - "DB_SCHEMA": { - "description": "The database schema to expose to REST clients. Tables, views and stored procedures in this schema will get API endpoints", - "required": true, - "value": "public" - }, - "DB_ANON_ROLE": { - "description": "The database role to use when executing commands on behalf of unauthenticated clients", - "required": true - }, - "DB_POOL": { - "description": "Number of connections to keep open in PostgREST’s database pool", - "required": false, - "value": "10" - }, - "SERVER_PROXY_URI": { - "description": "Overrides the base URL used within the OpenAPI self-documentation hosted at the API root path", - "required": false - }, - "JWT_SECRET": { - "description": "The secret used to decode JWT tokens clients provide for authentication", - "required": false - }, - "SECRET_IS_BASE64": { - "description": "When this is set to true, the value derived from jwt-secret will be treated as a base64 encoded secret", - "required": false, - "value": "false" - }, - "JWT_AUD": { - "description": "The audience that should be validated if the JWT token contains an aud claim", - "required": false - }, - "MAX_ROWS": { - "description": "A hard limit to the number of rows PostgREST will fetch from a view, table, or stored procedure", - "required": false - }, - "PRE_REQUEST": { - "description": "A schema-qualified stored procedure name to call right after switching roles for a client request", - "required": false - } - } -} From 2c8b5c2ace9ae4608193fb2d6851c84b18303299 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jan 2022 23:02:03 +0100 Subject: [PATCH 21/62] nix: Fix nested postgrest-with-postgresql-x calls when -f is used --- nix/tools/withTools.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tools/withTools.nix b/nix/tools/withTools.nix index fbc0bd44c0..9473e76f75 100644 --- a/nix/tools/withTools.nix +++ b/nix/tools/withTools.nix @@ -36,7 +36,7 @@ let '' # avoid starting multiple layers of withTmpDb if test -v PGRST_DB_URI; then - exec "$@" + exec "$_arg_command" "''${_arg_leftovers[@]}" fi setuplog="$tmpdir/setup.log" From 9a5f8df3ad52c859a35f5928df247b638dac3bbf Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jan 2022 08:27:50 +0100 Subject: [PATCH 22/62] test: Use .encode() end .decode default values in io-tests Those default to "utf-8" anyway. --- test/io-tests/test_io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index 94ff66a9de..fc5b6cae9d 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -77,7 +77,7 @@ class PostgrestProcess: @pytest.fixture def dburi(): "Postgres database connection URI." - return os.getenv("PGRST_DB_URI").encode("utf-8") + return os.getenv("PGRST_DB_URI").encode() @pytest.fixture @@ -118,7 +118,7 @@ def cli(args, env=None, stdin=None): result = process.communicate(timeout=5)[0] if process.returncode != 0: raise PostgrestError() - return result.decode("utf-8") + return result.decode() finally: process.kill() process.wait() From 19597a9da92cf58a3727172d17bde9886bac16f9 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 26 Dec 2021 11:49:29 +0100 Subject: [PATCH 23/62] test: Refactor invalid_role_claim_key_notify_reload test to properly read all available input --- test/io-tests/test_io.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index fc5b6cae9d..e00531edd9 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -156,15 +156,23 @@ def run(configpath=None, stdin=None, env=None, port=None): command.append(configpath) process = subprocess.Popen( - command, stdin=subprocess.PIPE, stderr=subprocess.PIPE, env=env + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, ) + os.set_blocking(process.stdout.fileno(), False) + try: process.stdin.write(stdin or b"") process.stdin.close() wait_until_ready(baseurl) + process.stdout.read() + yield PostgrestProcess(process=process, session=PostgrestSession(baseurl)) finally: process.terminate() @@ -694,17 +702,22 @@ def test_invalid_role_claim_key_notify_reload(defaultenv): **defaultenv, "PGRST_DB_CONFIG": "true", "PGRST_DB_CHANNEL_ENABLED": "true", + "PGRST_LOG_LEVEL": "crit", } with run(env=env) as postgrest: postgrest.session.post("/rpc/invalid_role_claim_key_reload") - # skips the first lines from stderr, the "Attempting to connect to database", "Connection successful", etc. - # this is a hack to avoid readline() from locking up the test - for _ in range(6): - postgrest.process.stderr.readline() - assert "failed to parse role-claim-key value" in str( - postgrest.process.stderr.readline() + output = None + for _ in range(10): + output = postgrest.process.stdout.readline() + if output: + break + time.sleep(0.1) + + assert ( + "failed to parse role-claim-key value" + in output.decode() ) postgrest.session.post("/rpc/reset_invalid_role_claim_key") From 5ae86afacab44c847342c269e71f99b83942c7da Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jan 2022 10:51:46 +0100 Subject: [PATCH 24/62] test: Improve log output for failing io-tests --- test/io-tests/test_io.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index e00531edd9..b7c5a2a662 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -175,6 +175,9 @@ def run(configpath=None, stdin=None, env=None, port=None): yield PostgrestProcess(process=process, session=PostgrestSession(baseurl)) finally: + remaining_output = process.stdout.read() + if remaining_output: + print(remaining_output.decode()) process.terminate() try: process.wait(timeout=1) @@ -195,6 +198,7 @@ def wait_until_ready(url): "Wait for the given HTTP endpoint to return a status of 200." session = requests_unixsocket.Session() + response = None for _ in range(10): try: response = session.get(url, timeout=1) @@ -205,7 +209,10 @@ def wait_until_ready(url): time.sleep(0.1) - raise PostgrestTimedOut() + if response: + raise PostgrestTimedOut(f"{response.status_code}: {response.text}") + else: + raise PostgrestTimedOut() def authheader(token): @@ -481,7 +488,6 @@ def test_app_settings(defaultenv): uri = "/rpc/get_guc_value?name=app.settings.external_api_secret" response = postgrest.session.get(uri) - assert response.status_code == 200 assert response.text == '"0123456789abcdef"' @@ -494,7 +500,6 @@ def test_app_settings_reload(tmp_path, defaultenv): with run(configfile, env=defaultenv) as postgrest: response = postgrest.session.get(uri) - assert response.status_code == 200 assert response.text == '"John"' # change setting @@ -505,7 +510,6 @@ def test_app_settings_reload(tmp_path, defaultenv): time.sleep(0.1) response = postgrest.session.get(uri) - assert response.status_code == 200 assert response.text == '"Jane"' @@ -649,6 +653,7 @@ def test_max_rows_reload(defaultenv): with run(config, env=env) as postgrest: response = postgrest.session.head("/projects") + assert response.status_code == 200 assert response.headers["Content-Range"] == "0-4/*" # change max-rows config on the db @@ -660,11 +665,12 @@ def test_max_rows_reload(defaultenv): time.sleep(0.1) response = postgrest.session.head("/projects") - + assert response.status_code == 200 assert response.headers["Content-Range"] == "0-0/*" # reset max-rows config on the db - postgrest.session.post("/rpc/reset_max_rows_config") + response = postgrest.session.post("/rpc/reset_max_rows_config") + assert response.status_code == 200 def test_max_rows_notify_reload(defaultenv): @@ -678,6 +684,7 @@ def test_max_rows_notify_reload(defaultenv): with run(env=env) as postgrest: response = postgrest.session.head("/projects") + assert response.status_code == 200 assert response.headers["Content-Range"] == "0-4/*" # change max-rows config on the db and reload with notify @@ -688,11 +695,12 @@ def test_max_rows_notify_reload(defaultenv): time.sleep(0.1) response = postgrest.session.head("/projects") - + assert response.status_code == 200 assert response.headers["Content-Range"] == "0-0/*" # reset max-rows config on the db - postgrest.session.post("/rpc/reset_max_rows_config") + response = postgrest.session.post("/rpc/reset_max_rows_config") + assert response.status_code == 200 def test_invalid_role_claim_key_notify_reload(defaultenv): @@ -720,7 +728,8 @@ def test_invalid_role_claim_key_notify_reload(defaultenv): in output.decode() ) - postgrest.session.post("/rpc/reset_invalid_role_claim_key") + response = postgrest.session.post("/rpc/reset_invalid_role_claim_key") + assert response.status_code == 200 def test_db_prepared_statements_enable(defaultenv): From 42ce326f56499329d7c0c80daa97720046b7df7e Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jan 2022 10:34:19 +0100 Subject: [PATCH 25/62] test: Move io-tests SQL fixtures to a separate file --- .github/workflows/ci.yaml | 2 +- nix/tools/tests.nix | 8 +- test/fixtures/roles.sql | 55 -------------- test/fixtures/schema.sql | 53 ------------- test/io-tests/configs/sigusr2-settings.config | 2 +- test/io-tests/db_config.sql | 55 ++++++++++++++ test/io-tests/fixtures.sql | 75 +++++++++++++++++++ test/io-tests/test_io.py | 55 ++++---------- 8 files changed, 151 insertions(+), 154 deletions(-) create mode 100644 test/io-tests/db_config.sql create mode 100644 test/io-tests/fixtures.sql diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index efced73a08..c28e3eb1bd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -82,7 +82,7 @@ jobs: - name: Run IO tests if: always() - run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io + run: postgrest-with-postgresql-${{ matrix.pgVersion }} -f test/io-tests/fixtures.sql postgrest-test-io - name: Run query cost tests if: always() diff --git a/nix/tools/tests.nix b/nix/tools/tests.nix index 41d1404b68..b54604c51c 100644 --- a/nix/tools/tests.nix +++ b/nix/tools/tests.nix @@ -92,8 +92,8 @@ let } '' ${cabal-install}/bin/cabal v2-build ${devCabalOptions} - ${cabal-install}/bin/cabal v2-exec ${withTools.withPg} \ - ${ioTestPython}/bin/pytest -- -v test/io-tests "''${_arg_leftovers[@]}" + ${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io-tests/fixtures.sql \ + ${ioTestPython}/bin/pytest -v test/io-tests "''${_arg_leftovers[@]}" ''; dumpSchema = @@ -137,8 +137,8 @@ let # collect all tests HPCTIXFILE="$tmpdir"/io.tix \ - ${withTools.withPg} ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} \ - ${ioTestPython}/bin/pytest -- -v test/io-tests + ${withTools.withPg} -f test/io-tests/fixtures.sql ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- \ + ${ioTestPython}/bin/pytest -v test/io-tests HPCTIXFILE="$tmpdir"/spec.tix \ ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec diff --git a/test/fixtures/roles.sql b/test/fixtures/roles.sql index 332f40e215..93b962d834 100644 --- a/test/fixtures/roles.sql +++ b/test/fixtures/roles.sql @@ -5,58 +5,3 @@ CREATE ROLE postgrest_test_default_role; CREATE ROLE postgrest_test_author; GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :USER; - --- reloadable config options for io tests -ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_aud = 'https://example.org'; -ALTER ROLE postgrest_test_authenticator SET pgrst.openapi_server_proxy_uri = 'https://example.org/api'; -ALTER ROLE postgrest_test_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.db-config'; -ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_secret = 'REALLYREALLYREALLYREALLYVERYSAFE'; -ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_secret_is_base64 = 'true'; -ALTER ROLE postgrest_test_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_tx_end = 'commit-allow-override'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_root_spec = 'root'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_prepared_statements = 'false'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_pre_request = 'test.custom_headers'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_max_rows = '1000'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_extra_search_path = 'public, extensions'; - --- override with database specific setting -ALTER ROLE postgrest_test_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE'; -ALTER ROLE postgrest_test_authenticator IN DATABASE :DBNAME SET pgrst.db_extra_search_path = 'public, extensions, private'; - --- other database settings that should be ignored -DROP DATABASE IF EXISTS other; -CREATE DATABASE other; -ALTER ROLE postgrest_test_authenticator IN DATABASE other SET pgrst.db_max_rows = '1111'; - --- non-reloadable configs for io tests -ALTER ROLE postgrest_test_authenticator SET pgrst.server_host = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.server_port = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.server_unix_socket = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.server_unix_socket_mode = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.log_level = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_anon_role = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_uri = 'postgresql://ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_channel_enabled = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_channel = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_pool = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_pool_timeout = 'ignored'; -ALTER ROLE postgrest_test_authenticator SET pgrst.db_config = 'ignored'; - --- other authenticator reloadable config options for io tests -CREATE ROLE other_authenticator LOGIN NOINHERIT; -ALTER ROLE other_authenticator SET pgrst.jwt_aud = 'https://otherexample.org'; -ALTER ROLE other_authenticator SET pgrst.openapi_server_proxy_uri = 'https://otherexample.org/api'; -ALTER ROLE other_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.other-db-config'; -ALTER ROLE other_authenticator SET pgrst.jwt_secret = 'ODERREALLYREALLYREALLYREALLYVERYSAFE'; -ALTER ROLE other_authenticator SET pgrst.jwt_secret_is_base64 = 'true'; -ALTER ROLE other_authenticator SET pgrst.jwt_role_claim_key = '."other"."role"'; -ALTER ROLE other_authenticator SET pgrst.db_tx_end = 'rollback-allow-override'; -ALTER ROLE other_authenticator SET pgrst.db_schemas = 'test, other_tenant1, other_tenant2'; -ALTER ROLE other_authenticator SET pgrst.db_root_spec = 'other_root'; -ALTER ROLE other_authenticator SET pgrst.db_prepared_statements = 'false'; -ALTER ROLE other_authenticator SET pgrst.db_pre_request = 'test.other_custom_headers'; -ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100'; -ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other'; -ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled'; diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index c13b88354e..eb25c6cc09 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -2120,59 +2120,6 @@ returns setof v2.parents as $$ select * from v2.parents where id < $1; $$ language sql; --- Used to test if prepared statements are used -create function uses_prepared_statements() returns bool as $$ - select count(name) > 0 from pg_catalog.pg_prepared_statements -$$ language sql; - -create or replace function change_max_rows_config(val int, notify bool default false) returns void as $_$ -begin - execute format($$ - alter role postgrest_test_authenticator set pgrst.db_max_rows = %L; - $$, val); - if notify then - perform pg_notify('pgrst', 'reload config'); - end if; -end $_$ volatile security definer language plpgsql ; - -create or replace function reset_max_rows_config() returns void as $_$ -begin - alter role postgrest_test_authenticator set pgrst.db_max_rows = '1000'; -end $_$ volatile security definer language plpgsql ; - -create or replace function change_db_schema_and_full_reload(schemas text) returns void as $_$ -begin - execute format($$ - alter role postgrest_test_authenticator set pgrst.db_schemas = %L; - $$, schemas); - perform pg_notify('pgrst', 'reload config'); - perform pg_notify('pgrst', 'reload schema'); -end $_$ volatile security definer language plpgsql ; - -create or replace function v1.reset_db_schema_config() returns void as $_$ -begin - alter role postgrest_test_authenticator set pgrst.db_schemas = 'test'; - perform pg_notify('pgrst', 'reload config'); - perform pg_notify('pgrst', 'reload schema'); -end $_$ volatile security definer language plpgsql ; - -create or replace function test.invalid_role_claim_key_reload() returns void as $_$ -begin - alter role postgrest_test_authenticator set pgrst.jwt_role_claim_key = 'test'; - perform pg_notify('pgrst', 'reload config'); -end $_$ volatile security definer language plpgsql ; - -create or replace function test.reset_invalid_role_claim_key() returns void as $_$ -begin - alter role postgrest_test_authenticator set pgrst.jwt_role_claim_key = '."a"."role"'; - perform pg_notify('pgrst', 'reload config'); -end $_$ volatile security definer language plpgsql ; - -create or replace function test.reload_pgrst_config() returns void as $_$ -begin - perform pg_notify('pgrst', 'reload config'); -end $_$ language plpgsql ; - create table private.screens ( id serial primary key, name text not null default 'new screen' diff --git a/test/io-tests/configs/sigusr2-settings.config b/test/io-tests/configs/sigusr2-settings.config index 69784f06c8..c8a1cb403f 100644 --- a/test/io-tests/configs/sigusr2-settings.config +++ b/test/io-tests/configs/sigusr2-settings.config @@ -1,4 +1,4 @@ -db-schemas = "test" +db-schemas = "public" db-pool = 1 app.settings.name_var = "John" diff --git a/test/io-tests/db_config.sql b/test/io-tests/db_config.sql new file mode 100644 index 0000000000..c0fee5a826 --- /dev/null +++ b/test/io-tests/db_config.sql @@ -0,0 +1,55 @@ +CREATE ROLE db_config_authenticator LOGIN NOINHERIT; + +-- reloadable config options +ALTER ROLE db_config_authenticator SET pgrst.jwt_aud = 'https://example.org'; +ALTER ROLE db_config_authenticator SET pgrst.openapi_server_proxy_uri = 'https://example.org/api'; +ALTER ROLE db_config_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.db-config'; +ALTER ROLE db_config_authenticator SET pgrst.jwt_secret = 'REALLYREALLYREALLYREALLYVERYSAFE'; +ALTER ROLE db_config_authenticator SET pgrst.jwt_secret_is_base64 = 'true'; +ALTER ROLE db_config_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"'; +ALTER ROLE db_config_authenticator SET pgrst.db_tx_end = 'commit-allow-override'; +ALTER ROLE db_config_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2'; +ALTER ROLE db_config_authenticator SET pgrst.db_root_spec = 'root'; +ALTER ROLE db_config_authenticator SET pgrst.db_prepared_statements = 'false'; +ALTER ROLE db_config_authenticator SET pgrst.db_pre_request = 'test.custom_headers'; +ALTER ROLE db_config_authenticator SET pgrst.db_max_rows = '1000'; +ALTER ROLE db_config_authenticator SET pgrst.db_extra_search_path = 'public, extensions'; + +-- override with database specific setting +ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE'; +ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.db_extra_search_path = 'public, extensions, private'; + +-- other database settings that should be ignored +CREATE DATABASE other; +ALTER ROLE db_config_authenticator IN DATABASE other SET pgrst.db_max_rows = '1111'; + +-- non-reloadable configs +ALTER ROLE db_config_authenticator SET pgrst.server_host = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.server_port = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket_mode = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.log_level = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_anon_role = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_uri = 'postgresql://ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_channel_enabled = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_channel = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_pool = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_pool_timeout = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_config = 'ignored'; + +-- other authenticator reloadable config options +CREATE ROLE other_authenticator LOGIN NOINHERIT; +ALTER ROLE other_authenticator SET pgrst.jwt_aud = 'https://otherexample.org'; +ALTER ROLE other_authenticator SET pgrst.openapi_server_proxy_uri = 'https://otherexample.org/api'; +ALTER ROLE other_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.other-db-config'; +ALTER ROLE other_authenticator SET pgrst.jwt_secret = 'ODERREALLYREALLYREALLYREALLYVERYSAFE'; +ALTER ROLE other_authenticator SET pgrst.jwt_secret_is_base64 = 'true'; +ALTER ROLE other_authenticator SET pgrst.jwt_role_claim_key = '."other"."role"'; +ALTER ROLE other_authenticator SET pgrst.db_tx_end = 'rollback-allow-override'; +ALTER ROLE other_authenticator SET pgrst.db_schemas = 'test, other_tenant1, other_tenant2'; +ALTER ROLE other_authenticator SET pgrst.db_root_spec = 'other_root'; +ALTER ROLE other_authenticator SET pgrst.db_prepared_statements = 'false'; +ALTER ROLE other_authenticator SET pgrst.db_pre_request = 'test.other_custom_headers'; +ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100'; +ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other'; +ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled'; diff --git a/test/io-tests/fixtures.sql b/test/io-tests/fixtures.sql new file mode 100644 index 0000000000..4c8a3a3cb1 --- /dev/null +++ b/test/io-tests/fixtures.sql @@ -0,0 +1,75 @@ +\ir db_config.sql + +CREATE ROLE postgrest_test_anonymous; +CREATE ROLE postgrest_test_author; + +GRANT postgrest_test_anonymous, postgrest_test_author TO :USER; + +CREATE SCHEMA v1; +GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous; + +CREATE TABLE authors_only (); +GRANT SELECT ON authors_only TO postgrest_test_author; + +CREATE TABLE projects AS SELECT FROM generate_series(1,5); +GRANT SELECT ON projects TO postgrest_test_anonymous; + +create function get_guc_value(name text) returns text as $$ + select nullif(current_setting(name), '')::text; +$$ language sql; + +create function v1.get_guc_value(name text) returns text as $$ + select nullif(current_setting(name), '')::text; +$$ language sql; + +create function uses_prepared_statements() returns bool as $$ + select count(name) > 0 from pg_catalog.pg_prepared_statements +$$ language sql; + +create function change_max_rows_config(val int, notify bool default false) returns void as $_$ +begin + execute format($$ + alter role postgrest_test_authenticator set pgrst.db_max_rows = %L; + $$, val); + if notify then + perform pg_notify('pgrst', 'reload config'); + end if; +end $_$ volatile security definer language plpgsql ; + +create function reset_max_rows_config() returns void as $_$ +begin + alter role postgrest_test_authenticator reset pgrst.db_max_rows; +end $_$ volatile security definer language plpgsql ; + +create function change_db_schema_and_full_reload(schemas text) returns void as $_$ +begin + execute format($$ + alter role postgrest_test_authenticator set pgrst.db_schemas = %L; + $$, schemas); + perform pg_notify('pgrst', 'reload config'); + perform pg_notify('pgrst', 'reload schema'); +end $_$ volatile security definer language plpgsql ; + +create function v1.reset_db_schema_config() returns void as $_$ +begin + alter role postgrest_test_authenticator reset pgrst.db_schemas; + perform pg_notify('pgrst', 'reload config'); + perform pg_notify('pgrst', 'reload schema'); +end $_$ volatile security definer language plpgsql ; + +create function invalid_role_claim_key_reload() returns void as $_$ +begin + alter role postgrest_test_authenticator set pgrst.jwt_role_claim_key = 'test'; + perform pg_notify('pgrst', 'reload config'); +end $_$ volatile security definer language plpgsql ; + +create function reset_invalid_role_claim_key() returns void as $_$ +begin + alter role postgrest_test_authenticator reset pgrst.jwt_role_claim_key; + perform pg_notify('pgrst', 'reload config'); +end $_$ volatile security definer language plpgsql ; + +create function reload_pgrst_config() returns void as $_$ +begin + perform pg_notify('pgrst', 'reload config'); +end $_$ language plpgsql ; diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index b7c5a2a662..cc94ad2c14 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -85,7 +85,7 @@ def defaultenv(): "Default environment for PostgREST." return { "PGRST_DB_URI": os.environ["PGRST_DB_URI"], - "PGRST_DB_SCHEMAS": os.environ["PGRST_DB_SCHEMAS"], + "PGRST_DB_SCHEMAS": "public", "PGRST_DB_ANON_ROLE": os.environ["PGRST_DB_ANON_ROLE"], "PGRST_DB_CONFIG": "false", "PGRST_LOG_LEVEL": "info", @@ -288,7 +288,7 @@ def test_expected_config_from_environment(): @pytest.mark.parametrize( "role, expectedconfig", [ - ("postgrest_test_authenticator", "no-defaults-with-db.config"), + ("db_config_authenticator", "no-defaults-with-db.config"), ("other_authenticator", "no-defaults-with-db-other-authenticator.config"), ], ) @@ -314,23 +314,6 @@ def test_expected_config_from_db_settings(defaultenv, role, expectedconfig): assert dumpconfig(configpath=config, env=env) == expected -def test_read_db_setting(defaultenv): - """ - Should be able to read db settings with current_setting. - - See: https://github.com/PostgREST/postgrest/pull/1729#discussion_r572946461 - """ - env = { - **defaultenv, - "PGRST_DB_CONFIG": "true", - } - with run(env=env) as postgrest: - uri = "/rpc/get_guc_value?name=pgrst.jwt_secret" - response = postgrest.session.get(uri) - - assert response.text == '"OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE"' - - @pytest.mark.parametrize( "config", [conf for conf in CONFIGSDIR.iterdir() if conf.suffix == ".config"], @@ -590,16 +573,15 @@ def test_db_schema_reload(tmp_path, defaultenv): configfile = tmp_path / "test.config" configfile.write_text(config) - headers = {"Accept-Profile": "v1"} env = {key: value for key, value in defaultenv.items() if key != "PGRST_DB_SCHEMAS"} with run(configfile, env=env) as postgrest: - response = postgrest.session.get("/parents", headers=headers) - assert response.status_code == 404 + response = postgrest.session.get("/rpc/get_guc_value?name=search_path") + assert response.text == '"public, public"' # change setting configfile.write_text( - config.replace('db-schemas = "test"', 'db-schemas = "test, v1"') + config.replace('db-schemas = "public"', 'db-schemas = "v1"') ) # reload config @@ -610,23 +592,18 @@ def test_db_schema_reload(tmp_path, defaultenv): time.sleep(0.1) - response = postgrest.session.get("/parents", headers=headers) - assert response.status_code == 200 + response = postgrest.session.get("/rpc/get_guc_value?name=search_path") + assert response.text == '"v1, public"' def test_db_schema_notify_reload(defaultenv): "DB schema and config should be reloaded when PostgREST is sent a NOTIFY" - env = { - **defaultenv, - "PGRST_DB_CONFIG": "true", - "PGRST_DB_CHANNEL_ENABLED": "true", - "PGRST_DB_SCHEMAS": "test", - } + env = {**defaultenv, "PGRST_DB_CONFIG": "true", "PGRST_DB_CHANNEL_ENABLED": "true"} with run(env=env) as postgrest: - response = postgrest.session.get("/parents") - assert response.status_code == 404 + response = postgrest.session.get("/rpc/get_guc_value?name=search_path") + assert response.text == '"public, public"' # change db-schemas config on the db and reload config and cache with notify postgrest.session.post( @@ -635,11 +612,12 @@ def test_db_schema_notify_reload(defaultenv): time.sleep(0.5) - response = postgrest.session.get("/parents?select=*,children(*)") - assert response.status_code == 200 + response = postgrest.session.get("/rpc/get_guc_value?name=search_path") + assert response.text == '"v1, public"' # reset db-schemas config on the db - postgrest.session.post("/rpc/reset_db_schema_config") + response = postgrest.session.post("/rpc/reset_db_schema_config") + assert response.status_code == 200 def test_max_rows_reload(defaultenv): @@ -723,10 +701,7 @@ def test_invalid_role_claim_key_notify_reload(defaultenv): break time.sleep(0.1) - assert ( - "failed to parse role-claim-key value" - in output.decode() - ) + assert "failed to parse role-claim-key value" in output.decode() response = postgrest.session.post("/rpc/reset_invalid_role_claim_key") assert response.status_code == 200 From a4ffc12a1d0b0ca2a80e04912d6ab943fe9f59dc Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jan 2022 13:09:22 +0100 Subject: [PATCH 26/62] test: Improve io test perfomance Local test improves performance by 15%. --- test/io-tests/configs/app-settings.config | 3 --- test/io-tests/configs/base64-secret-from-file.config | 2 -- test/io-tests/configs/dburi-from-file.config | 1 - test/io-tests/configs/role-claim-key.config | 1 - test/io-tests/configs/secret-from-file.config | 2 -- .../configs/sigusr2-settings-external-secret.config | 2 -- test/io-tests/configs/sigusr2-settings.config | 1 - test/io-tests/configs/simple.config | 1 - test/io-tests/test_io.py | 6 ++++-- 9 files changed, 4 insertions(+), 15 deletions(-) diff --git a/test/io-tests/configs/app-settings.config b/test/io-tests/configs/app-settings.config index 3542ae82eb..65408c91d5 100644 --- a/test/io-tests/configs/app-settings.config +++ b/test/io-tests/configs/app-settings.config @@ -1,5 +1,2 @@ -db-pool = 1 -db-pool-timeout = 1 - app.settings.external_api_secret = "0123456789abcdef" db-config = false diff --git a/test/io-tests/configs/base64-secret-from-file.config b/test/io-tests/configs/base64-secret-from-file.config index 2a5eab44f2..d31f6f5d83 100644 --- a/test/io-tests/configs/base64-secret-from-file.config +++ b/test/io-tests/configs/base64-secret-from-file.config @@ -1,5 +1,3 @@ -db-pool = 1 - # Read secret from a file: /dev/stdin (alias for standard input) jwt-secret = "@/dev/stdin" jwt-secret-is-base64 = true diff --git a/test/io-tests/configs/dburi-from-file.config b/test/io-tests/configs/dburi-from-file.config index 33586354e6..a9d8441b09 100644 --- a/test/io-tests/configs/dburi-from-file.config +++ b/test/io-tests/configs/dburi-from-file.config @@ -1,4 +1,3 @@ db-uri = "@/dev/stdin" -db-pool = 1 jwt-secret = "reallyreallyreallyreallyverysafe" db-config = false diff --git a/test/io-tests/configs/role-claim-key.config b/test/io-tests/configs/role-claim-key.config index d51d5fb647..dc22cf7ae0 100644 --- a/test/io-tests/configs/role-claim-key.config +++ b/test/io-tests/configs/role-claim-key.config @@ -1,4 +1,3 @@ -db-pool = 1 jwt-role-claim-key = "$(ROLE_CLAIM_KEY)" jwt-secret = "reallyreallyreallyreallyverysafe" db-config = false diff --git a/test/io-tests/configs/secret-from-file.config b/test/io-tests/configs/secret-from-file.config index df373590e1..6be2deee4b 100644 --- a/test/io-tests/configs/secret-from-file.config +++ b/test/io-tests/configs/secret-from-file.config @@ -1,5 +1,3 @@ -db-pool = 1 - # Read secret from a file: /dev/stdin (alias for standard input) jwt-secret = "@/dev/stdin" jwt-secret-is-base64 = false diff --git a/test/io-tests/configs/sigusr2-settings-external-secret.config b/test/io-tests/configs/sigusr2-settings-external-secret.config index 2dfda4a63d..b3fe854e32 100644 --- a/test/io-tests/configs/sigusr2-settings-external-secret.config +++ b/test/io-tests/configs/sigusr2-settings-external-secret.config @@ -1,5 +1,3 @@ -db-pool = 1 - jwt-secret = "$(JWT_SECRET_FILE)" jwt-secret-is-base64 = false db-config = false diff --git a/test/io-tests/configs/sigusr2-settings.config b/test/io-tests/configs/sigusr2-settings.config index c8a1cb403f..cc14ac7091 100644 --- a/test/io-tests/configs/sigusr2-settings.config +++ b/test/io-tests/configs/sigusr2-settings.config @@ -1,5 +1,4 @@ db-schemas = "public" -db-pool = 1 app.settings.name_var = "John" jwt-secret = "invalidinvalidinvalidinvalidinvalid" diff --git a/test/io-tests/configs/simple.config b/test/io-tests/configs/simple.config index 97512d3ece..b5833825e3 100644 --- a/test/io-tests/configs/simple.config +++ b/test/io-tests/configs/simple.config @@ -1,3 +1,2 @@ -db-pool = 1 jwt-secret = "reallyreallyreallyreallyverysafe" db-config = false diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index cc94ad2c14..734697b8fe 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -138,6 +138,8 @@ def dumpconfig(configpath=None, env=None, stdin=None): def run(configpath=None, stdin=None, env=None, port=None): "Run PostgREST and yield an endpoint that is ready for connections." env = env or {} + env["PGRST_DB_POOL"] = "1" + env["PGRST_DB_POOL_TIMEOUT"] = "1" with tempfile.TemporaryDirectory() as tmpdir: if port: @@ -454,7 +456,7 @@ def test_iat_claim(defaultenv): response = postgrest.session.get("/authors_only", headers=headers) assert response.status_code == 200 - time.sleep(0.5) + time.sleep(0.1) def test_app_settings(defaultenv): @@ -610,7 +612,7 @@ def test_db_schema_notify_reload(defaultenv): "/rpc/change_db_schema_and_full_reload", data={"schemas": "v1"} ) - time.sleep(0.5) + time.sleep(0.1) response = postgrest.session.get("/rpc/get_guc_value?name=search_path") assert response.text == '"v1, public"' From 2fc3cea8af7f650d0beb060ff8772f40871d9012 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jan 2022 21:50:23 +0100 Subject: [PATCH 27/62] test: Remove unused unix-socket.config file Unix sockets are the default in io tests and we are testing ip connections explicitly. --- test/io-tests/configs/unix-socket.config | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test/io-tests/configs/unix-socket.config diff --git a/test/io-tests/configs/unix-socket.config b/test/io-tests/configs/unix-socket.config deleted file mode 100644 index 1d34f1ea0d..0000000000 --- a/test/io-tests/configs/unix-socket.config +++ /dev/null @@ -1,4 +0,0 @@ -db-pool = 1 -server-unix-socket = "$(POSTGREST_TEST_SOCKET)" -jwt-secret = "reallyreallyreallyreallyverysafe" -db-config = false From 6cc6a7b0d1af618795a682d922e395e660d8b2a9 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 26 Dec 2021 18:30:46 +0100 Subject: [PATCH 28/62] cov: Add tests for request logging --- test/io-tests/test_io.py | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index 734697b8fe..bd8def666e 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -7,6 +7,7 @@ from operator import attrgetter import os import pathlib +import re import shutil import signal import socket @@ -728,3 +729,46 @@ def test_db_prepared_statements_disable(defaultenv): with run(env=env) as postgrest: response = postgrest.session.post("/rpc/uses_prepared_statements") assert response.text == "false" + + +@pytest.mark.parametrize( + "level, has_output", + [ + ("info", [True, True, True]), + ("warn", [False, True, True]), + ("error", [False, False, True]), + ("crit", [False, False, False]), + ], +) +def test_log_level(level, has_output, defaultenv): + "log_level should filter request logging" + + env = { + **defaultenv, + "PGRST_LOG_LEVEL": level + } + + with run(env=env) as postgrest: + response = postgrest.session.get("/") + assert response.status_code == 200 + if has_output[0]: + assert re.match( + r'unknownSocket - - \[.+\] "GET / HTTP/1.1" 200 - "" "python-requests/.+"', + postgrest.process.stdout.readline().decode(), + ) + + response = postgrest.session.get("/unknown") + assert response.status_code == 404 + if has_output[1]: + assert re.match( + r'unknownSocket - - \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"', + postgrest.process.stdout.readline().decode(), + ) + + response = postgrest.session.get("/rpc/raise_bad_pt") + assert response.status_code == 500 + if has_output[2]: + assert re.match( + r'unknownSocket - - \[.+\] "GET /rpc/raise_bad_pt HTTP/1.1" 500 - "" "python-requests/.+"', + postgrest.process.stdout.readline().decode(), + ) From e7f46f93c19c13fbaf415139981ff0b5c00b7464 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 3 Jan 2021 13:08:30 +0100 Subject: [PATCH 29/62] cov: Remove unreachable static icon --- postgrest.cabal | 1 - src/PostgREST/Middleware.hs | 2 -- static/favicon.ico | Bin 4286 -> 0 bytes 3 files changed, 3 deletions(-) delete mode 100644 static/favicon.ico diff --git a/postgrest.cabal b/postgrest.cabal index 871630cb00..f9ed601621 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -112,7 +112,6 @@ library , wai-cors >= 0.2.5 && < 0.3 , wai-extra >= 3.0.19 && < 3.2 , wai-logger >= 2.3.2 - , wai-middleware-static >= 0.8.1 && < 0.10 , warp >= 3.2.12 && < 3.4 -- -fno-spec-constr may help keep compile time memory use in check, -- see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304 diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index 9dc3123a85..e7991e120c 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -28,7 +28,6 @@ import qualified Network.Wai.Logger as Wai import qualified Network.Wai.Middleware.Cors as Wai import qualified Network.Wai.Middleware.Gzip as Wai import qualified Network.Wai.Middleware.RequestLogger as Wai -import qualified Network.Wai.Middleware.Static as Wai import Control.Arrow ((***)) @@ -129,7 +128,6 @@ pgrstMiddleware :: LogLevel -> Wai.Application -> Wai.Application pgrstMiddleware logLevel = logger . Wai.cors corsPolicy - . Wai.staticPolicy (Wai.only [("favicon.ico", "static/favicon.ico")]) where logger = case logLevel of LogCrit -> id diff --git a/static/favicon.ico b/static/favicon.ico deleted file mode 100644 index b3dd701f3955a39beae15188ea758b397894a419..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmeHIu}T9$5S?I=LU0p7+XNf)4;D#d?I#@~HhzL%VBq|TQR?j=;?^(l?*KMBB*uv}ZmbnlkfQpY?LPn|wm?~NYAKhVkj>pc!xyT_xT`1 z%)q=K-qr?t-?sS=wSK#ChPBl1Hug0?v!sk#zhBl@>)5f}7_Tdy z&e#9FJ|}0FsJZPiT;CnNx>}*e<|z-$$2z6;ebm_8b7HsUV;$3kaMWQQ;v$Az%#l;_ zvVnux00+*-)z!+?lWpAnOjgW0Q8T+w=HVOj!;*O^_laUVyh=3HrtuK3wX5fWQTz*) C*&f^g From ebb7887254e1603090cfc2154085d179fe369305 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 3 Jan 2021 22:28:47 +0100 Subject: [PATCH 30/62] refactor: simplify logger middleware --- nix/overlays/haskell-packages.nix | 30 ++++++++++++++++ postgrest.cabal | 4 +-- src/PostgREST/Middleware.hs | 58 ++++++++----------------------- stack.yaml | 2 ++ stack.yaml.lock | 22 +++++++++--- test/io-tests/fixtures.sql | 6 ++++ test/io-tests/test_io.py | 5 +-- 7 files changed, 73 insertions(+), 54 deletions(-) diff --git a/nix/overlays/haskell-packages.nix b/nix/overlays/haskell-packages.nix index 97d7ba65b3..75d27d7613 100644 --- a/nix/overlays/haskell-packages.nix +++ b/nix/overlays/haskell-packages.nix @@ -20,6 +20,36 @@ let # To get the sha256: # nix-prefetch-url --unpack https://hackage.haskell.org/package/protolude-0.3.0/protolude-0.3.0.tar.gz + # To temporarily pin unreleased versions from GitHub: + # = + # prev.callCabal2nixWithOptions "" (super.fetchFromGitHub { + # owner = ""; + # repo = ""; + # rev = ""; + # sha256 = ""; + # }) "--subpath=" {}; + # + # To get the sha256: + # nix-prefetch-url --unpack https://github.com///archive/.tar.gz + + wai-extra = + prev.callHackageDirect + { + pkg = "wai-extra"; + ver = "3.1.8"; + sha256 = "1ha8sxc2ii7k7xs5nm06wfwqmf4f1p2acp4ya0jnx6yn6551qps4"; + } + { }; + + wai-logger = + prev.callHackageDirect + { + pkg = "wai-logger"; + ver = "2.3.7"; + sha256 = "1d23fdbwbahr3y1vdyn57m1qhljy22pm5cpgb20dy6mlxzdb30xd"; + } + { }; + hasql-dynamic-statements = lib.dontCheck (lib.unmarkBroken prev.hasql-dynamic-statements); diff --git a/postgrest.cabal b/postgrest.cabal index f9ed601621..05e4d06ef2 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -81,7 +81,6 @@ library , contravariant-extras >= 0.3.3 && < 0.4 , cookie >= 0.4.2 && < 0.5 , either >= 4.4.1 && < 5.1 - , fast-logger >= 2.4.5 , gitrev >= 1.2 && < 1.4 , hasql >= 1.4 && < 1.5 , hasql-dynamic-statements == 0.3.1 @@ -110,8 +109,7 @@ library , vector >= 0.11 && < 0.13 , wai >= 3.2.1 && < 3.3 , wai-cors >= 0.2.5 && < 0.3 - , wai-extra >= 3.0.19 && < 3.2 - , wai-logger >= 2.3.2 + , wai-extra >= 3.1.8 && < 3.2 , warp >= 3.2.12 && < 3.4 -- -fno-spec-constr may help keep compile time memory use in check, -- see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304 diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index e7991e120c..15380f534c 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -6,7 +6,6 @@ Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and {-# LANGUAGE RecordWildCards #-} module PostgREST.Middleware ( runPgLocals - , pgrstFormat , pgrstMiddleware , optionalRollback ) where @@ -24,21 +23,16 @@ import qualified Hasql.DynamicStatements.Snippet as SQL hiding import qualified Hasql.DynamicStatements.Statement as SQL import qualified Hasql.Transaction as SQL import qualified Network.Wai as Wai -import qualified Network.Wai.Logger as Wai import qualified Network.Wai.Middleware.Cors as Wai -import qualified Network.Wai.Middleware.Gzip as Wai import qualified Network.Wai.Middleware.RequestLogger as Wai import Control.Arrow ((***)) -import Data.Function (id) import Data.List (lookup) import Data.Scientific (FPFormat (..), formatScientific, isInteger) -import Network.HTTP.Types.Status (Status, status400, status500, - statusCode) +import Network.HTTP.Types.Status (status400, status500) import System.IO.Unsafe (unsafePerformIO) -import System.Log.FastLogger (toLogStr) import PostgREST.Config (AppConfig (..), LogLevel (..)) import PostgREST.Config.PgVersion (PgVersion (..), pgVersion140) @@ -95,45 +89,23 @@ runPgLocals conf claims app req jsonDbS actualPgVersion = do unquoted (JSON.Bool b) = show b unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v --- | Log in apache format. Only requests that have a status greater than minStatus are logged. --- | There's no way to filter logs in the apache format on wai-extra: https://hackage.haskell.org/package/wai-extra-3.0.29.2/docs/Network-Wai-Middleware-RequestLogger.html#t:OutputFormat. --- | So here we copy wai-logger apacheLogStr function: https://github.com/kazu-yamamoto/logger/blob/a4f51b909a099c51af7a3f75cf16e19a06f9e257/wai-logger/Network/Wai/Logger/Apache.hs#L45 --- | TODO: Add the ability to filter apache logs on wai-extra and remove this function. -pgrstFormat :: Status -> Wai.OutputFormatter -pgrstFormat minStatus date req status responseSize = - if status < minStatus - then mempty - else toLogStr (getSourceFromSocket req) - <> " - - [" - <> toLogStr date - <> "] \"" - <> toLogStr (Wai.requestMethod req) - <> " " - <> toLogStr (Wai.rawPathInfo req <> Wai.rawQueryString req) - <> " " - <> toLogStr (show (Wai.httpVersion req)::Text) - <> "\" " - <> toLogStr (show (statusCode status)::Text) - <> " " - <> toLogStr (maybe "-" show responseSize::Text) - <> " \"" - <> toLogStr (fromMaybe mempty $ Wai.requestHeaderReferer req) - <> "\" \"" - <> toLogStr (fromMaybe mempty $ Wai.requestHeaderUserAgent req) - <> "\"\n" - where - getSourceFromSocket = BS.pack . Wai.showSockAddr . Wai.remoteHost - -pgrstMiddleware :: LogLevel -> Wai.Application -> Wai.Application +pgrstMiddleware :: LogLevel -> Wai.Middleware pgrstMiddleware logLevel = - logger + logger logLevel . Wai.cors corsPolicy + +logger :: LogLevel -> Wai.Middleware +logger logLevel = case logLevel of + LogInfo -> requestLogger (const True) + LogWarn -> requestLogger (>= status400) + LogError -> requestLogger (>= status500) + LogCrit -> requestLogger (const False) where - logger = case logLevel of - LogCrit -> id - LogError -> unsafePerformIO $ Wai.mkRequestLogger Wai.def { Wai.outputFormat = Wai.CustomOutputFormat $ pgrstFormat status500} - LogWarn -> unsafePerformIO $ Wai.mkRequestLogger Wai.def { Wai.outputFormat = Wai.CustomOutputFormat $ pgrstFormat status400} - LogInfo -> Wai.logStdout + requestLogger filterStatus = unsafePerformIO $ Wai.mkRequestLogger Wai.defaultRequestLoggerSettings + { Wai.outputFormat = Wai.ApacheWithSettings $ + Wai.defaultApacheSettings + & Wai.setApacheRequestFilter (\_ res -> filterStatus $ Wai.responseStatus res) + } -- | CORS policy to be used in by Wai Cors middleware corsPolicy :: Wai.Request -> Maybe Wai.CorsResourcePolicy diff --git a/stack.yaml b/stack.yaml index 14777bb89c..fd03c455de 100644 --- a/stack.yaml +++ b/stack.yaml @@ -13,3 +13,5 @@ extra-deps: - hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673 - hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361 - ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686 + - wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280 + - wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671 diff --git a/stack.yaml.lock b/stack.yaml.lock index ec0e46aa2c..5148fe7494 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -7,27 +7,41 @@ packages: - completed: hackage: hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673 pantry-tree: - size: 641 sha256: b1b9a6a26ec765e5fe29f9a670a5c9ec7067ea00dee8491f0819284ff0201b6f + size: 641 original: hackage: hasql-dynamic-statements-0.3.1@sha256:c3a2c89c4a8b3711368dbd33f0ccfe46a493faa7efc2c85d3e354c56a01dfc48,2673 - completed: hackage: hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361 pantry-tree: - size: 310 sha256: 2f00d1467d0e226b966c2cd7bac433c8948e2f7bbdf8a44936029f66fc20b5f3 + size: 310 original: hackage: hasql-implicits-0.1.0.2@sha256:5d54e09cb779a209681b139fb3cc726bae75134557932156340cc0a56dd834a8,1361 - completed: hackage: ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686 pantry-tree: - size: 1089 sha256: d2b8440a738719ef8430ec38fe33b129e3940e4ccf2c016a727a1110a43656bb + size: 1089 original: hackage: ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686 +- completed: + hackage: wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280 + pantry-tree: + sha256: a544ea95288d188e893322a8e6d68f2b1f844f772dbea1f26e5c0c1a74694f56 + size: 4053 + original: + hackage: wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280 +- completed: + hackage: wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671 + pantry-tree: + sha256: 52b5abf5c4c09bcfbc06e01f761a75c32cbd3e6ba23c8843981933fcc31ed53c + size: 474 + original: + hackage: wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671 snapshots: - completed: + sha256: 87842ecbaa8ca9cee59a7e6be52369dbed82ed075cb4e0d152614a627e8fd488 size: 586069 url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/14.yaml - sha256: 87842ecbaa8ca9cee59a7e6be52369dbed82ed075cb4e0d152614a627e8fd488 original: lts-18.14 diff --git a/test/io-tests/fixtures.sql b/test/io-tests/fixtures.sql index 4c8a3a3cb1..16e81f1eb1 100644 --- a/test/io-tests/fixtures.sql +++ b/test/io-tests/fixtures.sql @@ -73,3 +73,9 @@ create function reload_pgrst_config() returns void as $_$ begin perform pg_notify('pgrst', 'reload config'); end $_$ language plpgsql ; + +create or replace function raise_bad_pt() returns void as $$ +begin + raise sqlstate 'PT40A' using message = 'Wrong'; +end; +$$ language plpgsql; diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index bd8def666e..0e2d9f7cb3 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -743,10 +743,7 @@ def test_db_prepared_statements_disable(defaultenv): def test_log_level(level, has_output, defaultenv): "log_level should filter request logging" - env = { - **defaultenv, - "PGRST_LOG_LEVEL": level - } + env = {**defaultenv, "PGRST_LOG_LEVEL": level} with run(env=env) as postgrest: response = postgrest.session.get("/") From 3e011503a1214a1ca679e53d4fa99ad12a1d303f Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Wed, 5 Jan 2022 10:38:54 +0100 Subject: [PATCH 31/62] cov: Add coverage overlay for impossible case in corsPolicy --- src/PostgREST/Middleware.hs | 2 ++ test/coverage.overlay | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index 15380f534c..aeafe53658 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -128,6 +128,8 @@ corsPolicy req = case lookup "origin" headers of headers = Wai.requestHeaders req accHeaders = case lookup "access-control-request-headers" headers of Just hdrs -> map (CI.mk . BS.strip) $ BS.split ',' hdrs + -- Impossible case, Middleware.Cors will not evaluate this when + -- the Access-Control-Request-Headers header is not set. Nothing -> [] -- | Set a transaction to eventually roll back if requested and set respective diff --git a/test/coverage.overlay b/test/coverage.overlay index e69de29bb2..8583637514 100644 --- a/test/coverage.overlay +++ b/test/coverage.overlay @@ -0,0 +1,5 @@ +module "postgrest-9.0.0.20211220-inplace/PostgREST.Middleware" { + inside "corsPolicy" { + tick "[]" on line 133; + } +} From a739bb636a32704da0498b02baaac5bdd88b423e Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 7 Jan 2022 11:50:34 +0100 Subject: [PATCH 32/62] Remove unused dependencies from postgrest.cabal --- postgrest.cabal | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/postgrest.cabal b/postgrest.cabal index 05e4d06ef2..64794b1a3d 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -69,7 +69,6 @@ library , HTTP >= 4000.3.7 && < 4000.4 , Ranged-sets >= 0.3 && < 0.5 , aeson >= 1.4.7 && < 1.6 - , ansi-wl-pprint >= 0.6.7 && < 0.7 , auto-update >= 0.1.4 && < 0.2 , base64-bytestring >= 1 && < 1.3 , bytestring >= 0.10.8 && < 0.11 @@ -77,7 +76,6 @@ library , cassava >= 0.4.5 && < 0.6 , configurator-pg >= 0.2 && < 0.3 , containers >= 0.5.7 && < 0.7 - , contravariant >= 1.4 && < 1.6 , contravariant-extras >= 0.3.3 && < 0.4 , cookie >= 0.4.2 && < 0.5 , either >= 4.4.1 && < 5.1 @@ -208,10 +206,7 @@ test-suite spec , base64-bytestring >= 1 && < 1.3 , bytestring >= 0.10.8 && < 0.11 , case-insensitive >= 1.2 && < 1.3 - , cassava >= 0.4.5 && < 0.6 , containers >= 0.5.7 && < 0.7 - , contravariant >= 1.4 && < 1.6 - , hasql >= 1.4 && < 1.5 , hasql-pool >= 0.5 && < 0.6 , hasql-transaction >= 1.0.1 && < 1.1 , heredoc >= 0.2 && < 0.3 @@ -227,7 +222,6 @@ test-suite spec , protolude >= 0.3 && < 0.4 , regex-tdfa >= 1.2.2 && < 1.4 , text >= 1.2.2 && < 1.3 - , time >= 1.6 && < 1.11 , transformers-base >= 0.4.4 && < 0.5 , wai >= 3.2.1 && < 3.3 , wai-extra >= 3.0.19 && < 3.2 @@ -247,13 +241,9 @@ test-suite querycost other-modules: SpecHelper build-depends: base >= 4.9 && < 4.16 , aeson >= 1.4.7 && < 1.6 - , aeson-qq >= 0.8.1 && < 0.9 - , async >= 2.1.1 && < 2.3 - , auto-update >= 0.1.4 && < 0.2 , base64-bytestring >= 1 && < 1.3 , bytestring >= 0.10.8 && < 0.11 , case-insensitive >= 1.2 && < 1.3 - , cassava >= 0.4.5 && < 0.6 , containers >= 0.5.7 && < 0.7 , contravariant >= 1.4 && < 1.6 , hasql >= 1.4 && < 1.5 @@ -263,19 +253,13 @@ test-suite querycost , heredoc >= 0.2 && < 0.3 , hspec >= 2.3 && < 2.9 , hspec-wai >= 0.10 && < 0.12 - , hspec-wai-json >= 0.10 && < 0.12 , http-types >= 0.12.3 && < 0.13 , lens >= 4.14 && < 5.1 , lens-aeson >= 1.0.1 && < 1.2 - , monad-control >= 1.0.1 && < 1.1 , postgrest , process >= 1.4.2 && < 1.7 , protolude >= 0.3 && < 0.4 , regex-tdfa >= 1.2.2 && < 1.4 - , text >= 1.2.2 && < 1.3 - , time >= 1.6 && < 1.11 - , transformers-base >= 0.4.4 && < 0.5 - , wai >= 3.2.1 && < 3.3 , wai-extra >= 3.0.19 && < 3.2 ghc-options: -O0 -Werror -Wall -fwarn-identities -fno-spec-constr -optP-Wno-nonportable-include-path From dc96a631aff51740b60a2f1f517e10f094e33032 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 7 Jan 2022 10:09:17 +0100 Subject: [PATCH 33/62] test: Reorganize test/ folder into one subdirectory for each test type --- .github/workflows/ci.yaml | 2 +- nix/README.md | 2 +- nix/tools/loadtest.nix | 6 +++--- nix/tools/memory.nix | 2 +- nix/tools/tests.nix | 8 ++++---- nix/tools/withTools.nix | 2 +- postgrest.cabal | 6 +++--- test/{doctests => doc}/Main.hs | 0 test/{io-tests => io}/configs/aliases.config | 0 test/{io-tests => io}/configs/app-settings.config | 0 .../configs/base64-secret-from-file.config | 0 .../{io-tests => io}/configs/boolean-numeric.config | 0 test/{io-tests => io}/configs/boolean-string.config | 0 .../{io-tests => io}/configs/dburi-from-file.config | 0 test/{io-tests => io}/configs/defaults.config | 0 .../configs/expected/aliases.config | 0 .../configs/expected/boolean-numeric.config | 0 .../configs/expected/boolean-string.config | 0 .../configs/expected/defaults.config | 0 .../no-defaults-with-db-other-authenticator.config | 0 .../configs/expected/no-defaults-with-db.config | 0 .../configs/expected/no-defaults.config | 0 test/{io-tests => io}/configs/expected/types.config | 0 test/{io-tests => io}/configs/invalid.yaml | 0 test/{io-tests => io}/configs/no-defaults-env.yaml | 0 test/{io-tests => io}/configs/no-defaults.config | 0 test/{io-tests => io}/configs/role-claim-key.config | 0 .../configs/secret-from-file.config | 0 .../configs/sigusr2-settings-external-secret.config | 0 .../configs/sigusr2-settings.config | 0 test/{io-tests => io}/configs/simple.config | 0 test/{io-tests => io}/configs/types.config | 0 test/{io-tests => io}/db_config.sql | 0 test/{io-tests => io}/fixtures.sql | 0 test/{io-tests => io}/fixtures.yaml | 0 test/{io-tests => io}/secrets/ascii.b64 | 0 test/{io-tests => io}/secrets/ascii.jwt | 0 test/{io-tests => io}/secrets/ascii.noeol | 0 test/{io-tests => io}/secrets/ascii.txt | 0 test/{io-tests => io}/secrets/binary.b64 | 0 test/{io-tests => io}/secrets/binary.eol | 0 test/{io-tests => io}/secrets/binary.jwt | 0 test/{io-tests => io}/secrets/binary.noeol | 0 test/{io-tests => io}/secrets/utf8.b64 | 0 test/{io-tests => io}/secrets/utf8.jwt | 0 test/{io-tests => io}/secrets/utf8.noeol | 0 test/{io-tests => io}/secrets/utf8.txt | 0 test/{io-tests => io}/secrets/word.b64 | 0 test/{io-tests => io}/secrets/word.jwt | 0 test/{io-tests => io}/secrets/word.noeol | 0 test/{io-tests => io}/secrets/word.txt | 0 test/{io-tests => io}/test_io.py | 0 test/{loadtest => load}/fixtures.sql | 0 test/{loadtest => load}/patch.json | 0 test/{loadtest => load}/post.json | 0 test/{loadtest => load}/put.json | 0 test/{loadtest => load}/rpc.json | 0 test/{loadtest => load}/targets.http | 0 test/{ => memory}/memory-tests.sh | 0 test/{ => spec}/Feature/AndOrParamsSpec.hs | 0 test/{ => spec}/Feature/AsymmetricJwtSpec.hs | 0 test/{ => spec}/Feature/AudienceJwtSecretSpec.hs | 0 test/{ => spec}/Feature/AuthSpec.hs | 0 test/{ => spec}/Feature/BinaryJwtSecretSpec.hs | 0 test/{ => spec}/Feature/ConcurrentSpec.hs | 0 test/{ => spec}/Feature/CorsSpec.hs | 0 test/{ => spec}/Feature/DeleteSpec.hs | 0 test/{ => spec}/Feature/DisabledOpenApiSpec.hs | 0 test/{ => spec}/Feature/EmbedDisambiguationSpec.hs | 0 test/{ => spec}/Feature/EmbedInnerJoinSpec.hs | 0 test/{ => spec}/Feature/ExtraSearchPathSpec.hs | 0 test/{ => spec}/Feature/HtmlRawOutputSpec.hs | 0 test/{ => spec}/Feature/IgnorePrivOpenApiSpec.hs | 0 test/{ => spec}/Feature/InsertSpec.hs | 0 test/{ => spec}/Feature/JsonOperatorSpec.hs | 0 test/{ => spec}/Feature/LegacyGucsSpec.hs | 0 test/{ => spec}/Feature/MultipleSchemaSpec.hs | 0 test/{ => spec}/Feature/NoJwtSpec.hs | 0 test/{ => spec}/Feature/NonexistentSchemaSpec.hs | 0 test/{ => spec}/Feature/OpenApiSpec.hs | 0 test/{ => spec}/Feature/OptionsSpec.hs | 0 test/{ => spec}/Feature/ProxySpec.hs | 0 test/{ => spec}/Feature/QueryLimitedSpec.hs | 0 test/{ => spec}/Feature/QuerySpec.hs | 0 test/{ => spec}/Feature/RangeSpec.hs | 0 test/{ => spec}/Feature/RawOutputTypesSpec.hs | 0 test/{ => spec}/Feature/RollbackSpec.hs | 0 test/{ => spec}/Feature/RootSpec.hs | 0 test/{ => spec}/Feature/RpcPreRequestGucsSpec.hs | 0 test/{ => spec}/Feature/RpcSpec.hs | 6 +++--- test/{ => spec}/Feature/SingularSpec.hs | 0 test/{ => spec}/Feature/UnicodeSpec.hs | 0 test/{ => spec}/Feature/UpdateSpec.hs | 0 test/{ => spec}/Feature/UpsertSpec.hs | 0 test/{ => spec}/Main.hs | 0 test/{ => spec}/QueryCost.hs | 0 test/{ => spec}/SpecHelper.hs | 2 +- test/{ => spec}/TestTypes.hs | 0 test/{ => spec}/fixtures/data.sql | 0 test/{ => spec}/fixtures/database.sql | 0 test/{ => spec}/fixtures/draft04.json | 0 test/{C.png => spec/fixtures/image.png} | Bin test/{ => spec}/fixtures/jsonschema.sql | 0 test/{ => spec}/fixtures/jwt.sql | 0 test/{ => spec}/fixtures/load.sql | 0 test/{ => spec}/fixtures/openapi.json | 0 test/{ => spec}/fixtures/privileges.sql | 0 test/{ => spec}/fixtures/roles.sql | 0 test/{ => spec}/fixtures/schema.sql | 0 test/with_tmp_db | 2 +- 110 files changed, 19 insertions(+), 19 deletions(-) rename test/{doctests => doc}/Main.hs (100%) rename test/{io-tests => io}/configs/aliases.config (100%) rename test/{io-tests => io}/configs/app-settings.config (100%) rename test/{io-tests => io}/configs/base64-secret-from-file.config (100%) rename test/{io-tests => io}/configs/boolean-numeric.config (100%) rename test/{io-tests => io}/configs/boolean-string.config (100%) rename test/{io-tests => io}/configs/dburi-from-file.config (100%) rename test/{io-tests => io}/configs/defaults.config (100%) rename test/{io-tests => io}/configs/expected/aliases.config (100%) rename test/{io-tests => io}/configs/expected/boolean-numeric.config (100%) rename test/{io-tests => io}/configs/expected/boolean-string.config (100%) rename test/{io-tests => io}/configs/expected/defaults.config (100%) rename test/{io-tests => io}/configs/expected/no-defaults-with-db-other-authenticator.config (100%) rename test/{io-tests => io}/configs/expected/no-defaults-with-db.config (100%) rename test/{io-tests => io}/configs/expected/no-defaults.config (100%) rename test/{io-tests => io}/configs/expected/types.config (100%) rename test/{io-tests => io}/configs/invalid.yaml (100%) rename test/{io-tests => io}/configs/no-defaults-env.yaml (100%) rename test/{io-tests => io}/configs/no-defaults.config (100%) rename test/{io-tests => io}/configs/role-claim-key.config (100%) rename test/{io-tests => io}/configs/secret-from-file.config (100%) rename test/{io-tests => io}/configs/sigusr2-settings-external-secret.config (100%) rename test/{io-tests => io}/configs/sigusr2-settings.config (100%) rename test/{io-tests => io}/configs/simple.config (100%) rename test/{io-tests => io}/configs/types.config (100%) rename test/{io-tests => io}/db_config.sql (100%) rename test/{io-tests => io}/fixtures.sql (100%) rename test/{io-tests => io}/fixtures.yaml (100%) rename test/{io-tests => io}/secrets/ascii.b64 (100%) rename test/{io-tests => io}/secrets/ascii.jwt (100%) rename test/{io-tests => io}/secrets/ascii.noeol (100%) rename test/{io-tests => io}/secrets/ascii.txt (100%) rename test/{io-tests => io}/secrets/binary.b64 (100%) rename test/{io-tests => io}/secrets/binary.eol (100%) rename test/{io-tests => io}/secrets/binary.jwt (100%) rename test/{io-tests => io}/secrets/binary.noeol (100%) rename test/{io-tests => io}/secrets/utf8.b64 (100%) rename test/{io-tests => io}/secrets/utf8.jwt (100%) rename test/{io-tests => io}/secrets/utf8.noeol (100%) rename test/{io-tests => io}/secrets/utf8.txt (100%) rename test/{io-tests => io}/secrets/word.b64 (100%) rename test/{io-tests => io}/secrets/word.jwt (100%) rename test/{io-tests => io}/secrets/word.noeol (100%) rename test/{io-tests => io}/secrets/word.txt (100%) rename test/{io-tests => io}/test_io.py (100%) rename test/{loadtest => load}/fixtures.sql (100%) rename test/{loadtest => load}/patch.json (100%) rename test/{loadtest => load}/post.json (100%) rename test/{loadtest => load}/put.json (100%) rename test/{loadtest => load}/rpc.json (100%) rename test/{loadtest => load}/targets.http (100%) rename test/{ => memory}/memory-tests.sh (100%) rename test/{ => spec}/Feature/AndOrParamsSpec.hs (100%) rename test/{ => spec}/Feature/AsymmetricJwtSpec.hs (100%) rename test/{ => spec}/Feature/AudienceJwtSecretSpec.hs (100%) rename test/{ => spec}/Feature/AuthSpec.hs (100%) rename test/{ => spec}/Feature/BinaryJwtSecretSpec.hs (100%) rename test/{ => spec}/Feature/ConcurrentSpec.hs (100%) rename test/{ => spec}/Feature/CorsSpec.hs (100%) rename test/{ => spec}/Feature/DeleteSpec.hs (100%) rename test/{ => spec}/Feature/DisabledOpenApiSpec.hs (100%) rename test/{ => spec}/Feature/EmbedDisambiguationSpec.hs (100%) rename test/{ => spec}/Feature/EmbedInnerJoinSpec.hs (100%) rename test/{ => spec}/Feature/ExtraSearchPathSpec.hs (100%) rename test/{ => spec}/Feature/HtmlRawOutputSpec.hs (100%) rename test/{ => spec}/Feature/IgnorePrivOpenApiSpec.hs (100%) rename test/{ => spec}/Feature/InsertSpec.hs (100%) rename test/{ => spec}/Feature/JsonOperatorSpec.hs (100%) rename test/{ => spec}/Feature/LegacyGucsSpec.hs (100%) rename test/{ => spec}/Feature/MultipleSchemaSpec.hs (100%) rename test/{ => spec}/Feature/NoJwtSpec.hs (100%) rename test/{ => spec}/Feature/NonexistentSchemaSpec.hs (100%) rename test/{ => spec}/Feature/OpenApiSpec.hs (100%) rename test/{ => spec}/Feature/OptionsSpec.hs (100%) rename test/{ => spec}/Feature/ProxySpec.hs (100%) rename test/{ => spec}/Feature/QueryLimitedSpec.hs (100%) rename test/{ => spec}/Feature/QuerySpec.hs (100%) rename test/{ => spec}/Feature/RangeSpec.hs (100%) rename test/{ => spec}/Feature/RawOutputTypesSpec.hs (100%) rename test/{ => spec}/Feature/RollbackSpec.hs (100%) rename test/{ => spec}/Feature/RootSpec.hs (100%) rename test/{ => spec}/Feature/RpcPreRequestGucsSpec.hs (100%) rename test/{ => spec}/Feature/RpcSpec.hs (99%) rename test/{ => spec}/Feature/SingularSpec.hs (100%) rename test/{ => spec}/Feature/UnicodeSpec.hs (100%) rename test/{ => spec}/Feature/UpdateSpec.hs (100%) rename test/{ => spec}/Feature/UpsertSpec.hs (100%) rename test/{ => spec}/Main.hs (100%) rename test/{ => spec}/QueryCost.hs (100%) rename test/{ => spec}/SpecHelper.hs (99%) rename test/{ => spec}/TestTypes.hs (100%) rename test/{ => spec}/fixtures/data.sql (100%) rename test/{ => spec}/fixtures/database.sql (100%) rename test/{ => spec}/fixtures/draft04.json (100%) rename test/{C.png => spec/fixtures/image.png} (100%) rename test/{ => spec}/fixtures/jsonschema.sql (100%) rename test/{ => spec}/fixtures/jwt.sql (100%) rename test/{ => spec}/fixtures/load.sql (100%) rename test/{ => spec}/fixtures/openapi.json (100%) rename test/{ => spec}/fixtures/privileges.sql (100%) rename test/{ => spec}/fixtures/roles.sql (100%) rename test/{ => spec}/fixtures/schema.sql (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c28e3eb1bd..9eb5cd8bb9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -82,7 +82,7 @@ jobs: - name: Run IO tests if: always() - run: postgrest-with-postgresql-${{ matrix.pgVersion }} -f test/io-tests/fixtures.sql postgrest-test-io + run: postgrest-with-postgresql-${{ matrix.pgVersion }} -f test/io/fixtures.sql postgrest-test-io - name: Run query cost tests if: always() diff --git a/nix/README.md b/nix/README.md index 614ea04d44..0c44a7ad3b 100644 --- a/nix/README.md +++ b/nix/README.md @@ -149,7 +149,7 @@ the PostgREST repo. Paths are resolved relative to the repo root: $ cd src # Even though the current directory is ./src, the config path must still start # from the repo root: -$ postgrest-run test/io-tests/configs/simple.conf +$ postgrest-run test/io/configs/simple.conf ``` ## Testing diff --git a/nix/tools/loadtest.nix b/nix/tools/loadtest.nix index a7e207809e..c2c282b426 100644 --- a/nix/tools/loadtest.nix +++ b/nix/tools/loadtest.nix @@ -39,7 +39,7 @@ let docs = "Run the vegeta loadtests with PostgREST."; args = [ "ARG_OPTIONAL_SINGLE([output], [o], [Filename to dump json output to], [./loadtest/result.bin])" - "ARG_OPTIONAL_SINGLE([testdir], [t], [Directory to load tests and fixtures from], [./test/loadtest])" + "ARG_OPTIONAL_SINGLE([testdir], [t], [Directory to load tests and fixtures from], [./test/load])" "ARG_LEFTOVERS([additional vegeta arguments])" ]; inRootDir = true; @@ -97,7 +97,7 @@ let # Save the results in the current working tree, too, # otherwise they'd be lost in the temporary working tree # created by withTools.withGit. - ${withTools.withGit} "$_arg_target" ${loadtest} --output "$PWD/loadtest/$_arg_target.bin" --testdir "$PWD/test/loadtest" "''${_arg_leftovers[@]}" + ${withTools.withGit} "$_arg_target" ${loadtest} --output "$PWD/loadtest/$_arg_target.bin" --testdir "$PWD/test/load" "''${_arg_leftovers[@]}" cat << EOF @@ -111,7 +111,7 @@ let EOF - ${loadtest} --output "$PWD/loadtest/head.bin" --testdir "$PWD/test/loadtest" "''${_arg_leftovers[@]}" + ${loadtest} --output "$PWD/loadtest/head.bin" --testdir "$PWD/test/load" "''${_arg_leftovers[@]}" cat << EOF diff --git a/nix/tools/memory.nix b/nix/tools/memory.nix index dcf34483fd..bdfa3c5da3 100644 --- a/nix/tools/memory.nix +++ b/nix/tools/memory.nix @@ -17,7 +17,7 @@ let withPath = [ postgrestProfiled curl ]; } '' - ${withTools.withPg} test/memory-tests.sh + ${withTools.withPg} test/memory/memory-tests.sh ''; in diff --git a/nix/tools/tests.nix b/nix/tools/tests.nix index b54604c51c..1a79402da5 100644 --- a/nix/tools/tests.nix +++ b/nix/tools/tests.nix @@ -92,8 +92,8 @@ let } '' ${cabal-install}/bin/cabal v2-build ${devCabalOptions} - ${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io-tests/fixtures.sql \ - ${ioTestPython}/bin/pytest -v test/io-tests "''${_arg_leftovers[@]}" + ${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures.sql \ + ${ioTestPython}/bin/pytest -v test/io "''${_arg_leftovers[@]}" ''; dumpSchema = @@ -137,8 +137,8 @@ let # collect all tests HPCTIXFILE="$tmpdir"/io.tix \ - ${withTools.withPg} -f test/io-tests/fixtures.sql ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- \ - ${ioTestPython}/bin/pytest -v test/io-tests + ${withTools.withPg} -f test/io/fixtures.sql ${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- \ + ${ioTestPython}/bin/pytest -v test/io HPCTIXFILE="$tmpdir"/spec.tix \ ${withTools.withPg} ${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec diff --git a/nix/tools/withTools.nix b/nix/tools/withTools.nix index 9473e76f75..4e281a3778 100644 --- a/nix/tools/withTools.nix +++ b/nix/tools/withTools.nix @@ -19,7 +19,7 @@ let docs = "Run the given command in a temporary database with ${name}"; args = [ - "ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/fixtures/load.sql])" + "ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/spec/fixtures/load.sql])" "ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_LEFTOVERS([command arguments])" "ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])" diff --git a/postgrest.cabal b/postgrest.cabal index 64794b1a3d..b29b630f70 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -159,7 +159,7 @@ test-suite spec default-extensions: OverloadedStrings QuasiQuotes NoImplicitPrelude - hs-source-dirs: test + hs-source-dirs: test/spec main-is: Main.hs other-modules: Feature.AndOrParamsSpec Feature.AsymmetricJwtSpec @@ -236,7 +236,7 @@ test-suite querycost default-extensions: OverloadedStrings QuasiQuotes NoImplicitPrelude - hs-source-dirs: test + hs-source-dirs: test/spec main-is: QueryCost.hs other-modules: SpecHelper build-depends: base >= 4.9 && < 4.16 @@ -270,7 +270,7 @@ test-suite doctests default-language: Haskell2010 default-extensions: OverloadedStrings NoImplicitPrelude - hs-source-dirs: test/doctests + hs-source-dirs: test/doc main-is: Main.hs build-depends: base >= 4.9 && < 4.16 , doctest >= 0.8 diff --git a/test/doctests/Main.hs b/test/doc/Main.hs similarity index 100% rename from test/doctests/Main.hs rename to test/doc/Main.hs diff --git a/test/io-tests/configs/aliases.config b/test/io/configs/aliases.config similarity index 100% rename from test/io-tests/configs/aliases.config rename to test/io/configs/aliases.config diff --git a/test/io-tests/configs/app-settings.config b/test/io/configs/app-settings.config similarity index 100% rename from test/io-tests/configs/app-settings.config rename to test/io/configs/app-settings.config diff --git a/test/io-tests/configs/base64-secret-from-file.config b/test/io/configs/base64-secret-from-file.config similarity index 100% rename from test/io-tests/configs/base64-secret-from-file.config rename to test/io/configs/base64-secret-from-file.config diff --git a/test/io-tests/configs/boolean-numeric.config b/test/io/configs/boolean-numeric.config similarity index 100% rename from test/io-tests/configs/boolean-numeric.config rename to test/io/configs/boolean-numeric.config diff --git a/test/io-tests/configs/boolean-string.config b/test/io/configs/boolean-string.config similarity index 100% rename from test/io-tests/configs/boolean-string.config rename to test/io/configs/boolean-string.config diff --git a/test/io-tests/configs/dburi-from-file.config b/test/io/configs/dburi-from-file.config similarity index 100% rename from test/io-tests/configs/dburi-from-file.config rename to test/io/configs/dburi-from-file.config diff --git a/test/io-tests/configs/defaults.config b/test/io/configs/defaults.config similarity index 100% rename from test/io-tests/configs/defaults.config rename to test/io/configs/defaults.config diff --git a/test/io-tests/configs/expected/aliases.config b/test/io/configs/expected/aliases.config similarity index 100% rename from test/io-tests/configs/expected/aliases.config rename to test/io/configs/expected/aliases.config diff --git a/test/io-tests/configs/expected/boolean-numeric.config b/test/io/configs/expected/boolean-numeric.config similarity index 100% rename from test/io-tests/configs/expected/boolean-numeric.config rename to test/io/configs/expected/boolean-numeric.config diff --git a/test/io-tests/configs/expected/boolean-string.config b/test/io/configs/expected/boolean-string.config similarity index 100% rename from test/io-tests/configs/expected/boolean-string.config rename to test/io/configs/expected/boolean-string.config diff --git a/test/io-tests/configs/expected/defaults.config b/test/io/configs/expected/defaults.config similarity index 100% rename from test/io-tests/configs/expected/defaults.config rename to test/io/configs/expected/defaults.config diff --git a/test/io-tests/configs/expected/no-defaults-with-db-other-authenticator.config b/test/io/configs/expected/no-defaults-with-db-other-authenticator.config similarity index 100% rename from test/io-tests/configs/expected/no-defaults-with-db-other-authenticator.config rename to test/io/configs/expected/no-defaults-with-db-other-authenticator.config diff --git a/test/io-tests/configs/expected/no-defaults-with-db.config b/test/io/configs/expected/no-defaults-with-db.config similarity index 100% rename from test/io-tests/configs/expected/no-defaults-with-db.config rename to test/io/configs/expected/no-defaults-with-db.config diff --git a/test/io-tests/configs/expected/no-defaults.config b/test/io/configs/expected/no-defaults.config similarity index 100% rename from test/io-tests/configs/expected/no-defaults.config rename to test/io/configs/expected/no-defaults.config diff --git a/test/io-tests/configs/expected/types.config b/test/io/configs/expected/types.config similarity index 100% rename from test/io-tests/configs/expected/types.config rename to test/io/configs/expected/types.config diff --git a/test/io-tests/configs/invalid.yaml b/test/io/configs/invalid.yaml similarity index 100% rename from test/io-tests/configs/invalid.yaml rename to test/io/configs/invalid.yaml diff --git a/test/io-tests/configs/no-defaults-env.yaml b/test/io/configs/no-defaults-env.yaml similarity index 100% rename from test/io-tests/configs/no-defaults-env.yaml rename to test/io/configs/no-defaults-env.yaml diff --git a/test/io-tests/configs/no-defaults.config b/test/io/configs/no-defaults.config similarity index 100% rename from test/io-tests/configs/no-defaults.config rename to test/io/configs/no-defaults.config diff --git a/test/io-tests/configs/role-claim-key.config b/test/io/configs/role-claim-key.config similarity index 100% rename from test/io-tests/configs/role-claim-key.config rename to test/io/configs/role-claim-key.config diff --git a/test/io-tests/configs/secret-from-file.config b/test/io/configs/secret-from-file.config similarity index 100% rename from test/io-tests/configs/secret-from-file.config rename to test/io/configs/secret-from-file.config diff --git a/test/io-tests/configs/sigusr2-settings-external-secret.config b/test/io/configs/sigusr2-settings-external-secret.config similarity index 100% rename from test/io-tests/configs/sigusr2-settings-external-secret.config rename to test/io/configs/sigusr2-settings-external-secret.config diff --git a/test/io-tests/configs/sigusr2-settings.config b/test/io/configs/sigusr2-settings.config similarity index 100% rename from test/io-tests/configs/sigusr2-settings.config rename to test/io/configs/sigusr2-settings.config diff --git a/test/io-tests/configs/simple.config b/test/io/configs/simple.config similarity index 100% rename from test/io-tests/configs/simple.config rename to test/io/configs/simple.config diff --git a/test/io-tests/configs/types.config b/test/io/configs/types.config similarity index 100% rename from test/io-tests/configs/types.config rename to test/io/configs/types.config diff --git a/test/io-tests/db_config.sql b/test/io/db_config.sql similarity index 100% rename from test/io-tests/db_config.sql rename to test/io/db_config.sql diff --git a/test/io-tests/fixtures.sql b/test/io/fixtures.sql similarity index 100% rename from test/io-tests/fixtures.sql rename to test/io/fixtures.sql diff --git a/test/io-tests/fixtures.yaml b/test/io/fixtures.yaml similarity index 100% rename from test/io-tests/fixtures.yaml rename to test/io/fixtures.yaml diff --git a/test/io-tests/secrets/ascii.b64 b/test/io/secrets/ascii.b64 similarity index 100% rename from test/io-tests/secrets/ascii.b64 rename to test/io/secrets/ascii.b64 diff --git a/test/io-tests/secrets/ascii.jwt b/test/io/secrets/ascii.jwt similarity index 100% rename from test/io-tests/secrets/ascii.jwt rename to test/io/secrets/ascii.jwt diff --git a/test/io-tests/secrets/ascii.noeol b/test/io/secrets/ascii.noeol similarity index 100% rename from test/io-tests/secrets/ascii.noeol rename to test/io/secrets/ascii.noeol diff --git a/test/io-tests/secrets/ascii.txt b/test/io/secrets/ascii.txt similarity index 100% rename from test/io-tests/secrets/ascii.txt rename to test/io/secrets/ascii.txt diff --git a/test/io-tests/secrets/binary.b64 b/test/io/secrets/binary.b64 similarity index 100% rename from test/io-tests/secrets/binary.b64 rename to test/io/secrets/binary.b64 diff --git a/test/io-tests/secrets/binary.eol b/test/io/secrets/binary.eol similarity index 100% rename from test/io-tests/secrets/binary.eol rename to test/io/secrets/binary.eol diff --git a/test/io-tests/secrets/binary.jwt b/test/io/secrets/binary.jwt similarity index 100% rename from test/io-tests/secrets/binary.jwt rename to test/io/secrets/binary.jwt diff --git a/test/io-tests/secrets/binary.noeol b/test/io/secrets/binary.noeol similarity index 100% rename from test/io-tests/secrets/binary.noeol rename to test/io/secrets/binary.noeol diff --git a/test/io-tests/secrets/utf8.b64 b/test/io/secrets/utf8.b64 similarity index 100% rename from test/io-tests/secrets/utf8.b64 rename to test/io/secrets/utf8.b64 diff --git a/test/io-tests/secrets/utf8.jwt b/test/io/secrets/utf8.jwt similarity index 100% rename from test/io-tests/secrets/utf8.jwt rename to test/io/secrets/utf8.jwt diff --git a/test/io-tests/secrets/utf8.noeol b/test/io/secrets/utf8.noeol similarity index 100% rename from test/io-tests/secrets/utf8.noeol rename to test/io/secrets/utf8.noeol diff --git a/test/io-tests/secrets/utf8.txt b/test/io/secrets/utf8.txt similarity index 100% rename from test/io-tests/secrets/utf8.txt rename to test/io/secrets/utf8.txt diff --git a/test/io-tests/secrets/word.b64 b/test/io/secrets/word.b64 similarity index 100% rename from test/io-tests/secrets/word.b64 rename to test/io/secrets/word.b64 diff --git a/test/io-tests/secrets/word.jwt b/test/io/secrets/word.jwt similarity index 100% rename from test/io-tests/secrets/word.jwt rename to test/io/secrets/word.jwt diff --git a/test/io-tests/secrets/word.noeol b/test/io/secrets/word.noeol similarity index 100% rename from test/io-tests/secrets/word.noeol rename to test/io/secrets/word.noeol diff --git a/test/io-tests/secrets/word.txt b/test/io/secrets/word.txt similarity index 100% rename from test/io-tests/secrets/word.txt rename to test/io/secrets/word.txt diff --git a/test/io-tests/test_io.py b/test/io/test_io.py similarity index 100% rename from test/io-tests/test_io.py rename to test/io/test_io.py diff --git a/test/loadtest/fixtures.sql b/test/load/fixtures.sql similarity index 100% rename from test/loadtest/fixtures.sql rename to test/load/fixtures.sql diff --git a/test/loadtest/patch.json b/test/load/patch.json similarity index 100% rename from test/loadtest/patch.json rename to test/load/patch.json diff --git a/test/loadtest/post.json b/test/load/post.json similarity index 100% rename from test/loadtest/post.json rename to test/load/post.json diff --git a/test/loadtest/put.json b/test/load/put.json similarity index 100% rename from test/loadtest/put.json rename to test/load/put.json diff --git a/test/loadtest/rpc.json b/test/load/rpc.json similarity index 100% rename from test/loadtest/rpc.json rename to test/load/rpc.json diff --git a/test/loadtest/targets.http b/test/load/targets.http similarity index 100% rename from test/loadtest/targets.http rename to test/load/targets.http diff --git a/test/memory-tests.sh b/test/memory/memory-tests.sh similarity index 100% rename from test/memory-tests.sh rename to test/memory/memory-tests.sh diff --git a/test/Feature/AndOrParamsSpec.hs b/test/spec/Feature/AndOrParamsSpec.hs similarity index 100% rename from test/Feature/AndOrParamsSpec.hs rename to test/spec/Feature/AndOrParamsSpec.hs diff --git a/test/Feature/AsymmetricJwtSpec.hs b/test/spec/Feature/AsymmetricJwtSpec.hs similarity index 100% rename from test/Feature/AsymmetricJwtSpec.hs rename to test/spec/Feature/AsymmetricJwtSpec.hs diff --git a/test/Feature/AudienceJwtSecretSpec.hs b/test/spec/Feature/AudienceJwtSecretSpec.hs similarity index 100% rename from test/Feature/AudienceJwtSecretSpec.hs rename to test/spec/Feature/AudienceJwtSecretSpec.hs diff --git a/test/Feature/AuthSpec.hs b/test/spec/Feature/AuthSpec.hs similarity index 100% rename from test/Feature/AuthSpec.hs rename to test/spec/Feature/AuthSpec.hs diff --git a/test/Feature/BinaryJwtSecretSpec.hs b/test/spec/Feature/BinaryJwtSecretSpec.hs similarity index 100% rename from test/Feature/BinaryJwtSecretSpec.hs rename to test/spec/Feature/BinaryJwtSecretSpec.hs diff --git a/test/Feature/ConcurrentSpec.hs b/test/spec/Feature/ConcurrentSpec.hs similarity index 100% rename from test/Feature/ConcurrentSpec.hs rename to test/spec/Feature/ConcurrentSpec.hs diff --git a/test/Feature/CorsSpec.hs b/test/spec/Feature/CorsSpec.hs similarity index 100% rename from test/Feature/CorsSpec.hs rename to test/spec/Feature/CorsSpec.hs diff --git a/test/Feature/DeleteSpec.hs b/test/spec/Feature/DeleteSpec.hs similarity index 100% rename from test/Feature/DeleteSpec.hs rename to test/spec/Feature/DeleteSpec.hs diff --git a/test/Feature/DisabledOpenApiSpec.hs b/test/spec/Feature/DisabledOpenApiSpec.hs similarity index 100% rename from test/Feature/DisabledOpenApiSpec.hs rename to test/spec/Feature/DisabledOpenApiSpec.hs diff --git a/test/Feature/EmbedDisambiguationSpec.hs b/test/spec/Feature/EmbedDisambiguationSpec.hs similarity index 100% rename from test/Feature/EmbedDisambiguationSpec.hs rename to test/spec/Feature/EmbedDisambiguationSpec.hs diff --git a/test/Feature/EmbedInnerJoinSpec.hs b/test/spec/Feature/EmbedInnerJoinSpec.hs similarity index 100% rename from test/Feature/EmbedInnerJoinSpec.hs rename to test/spec/Feature/EmbedInnerJoinSpec.hs diff --git a/test/Feature/ExtraSearchPathSpec.hs b/test/spec/Feature/ExtraSearchPathSpec.hs similarity index 100% rename from test/Feature/ExtraSearchPathSpec.hs rename to test/spec/Feature/ExtraSearchPathSpec.hs diff --git a/test/Feature/HtmlRawOutputSpec.hs b/test/spec/Feature/HtmlRawOutputSpec.hs similarity index 100% rename from test/Feature/HtmlRawOutputSpec.hs rename to test/spec/Feature/HtmlRawOutputSpec.hs diff --git a/test/Feature/IgnorePrivOpenApiSpec.hs b/test/spec/Feature/IgnorePrivOpenApiSpec.hs similarity index 100% rename from test/Feature/IgnorePrivOpenApiSpec.hs rename to test/spec/Feature/IgnorePrivOpenApiSpec.hs diff --git a/test/Feature/InsertSpec.hs b/test/spec/Feature/InsertSpec.hs similarity index 100% rename from test/Feature/InsertSpec.hs rename to test/spec/Feature/InsertSpec.hs diff --git a/test/Feature/JsonOperatorSpec.hs b/test/spec/Feature/JsonOperatorSpec.hs similarity index 100% rename from test/Feature/JsonOperatorSpec.hs rename to test/spec/Feature/JsonOperatorSpec.hs diff --git a/test/Feature/LegacyGucsSpec.hs b/test/spec/Feature/LegacyGucsSpec.hs similarity index 100% rename from test/Feature/LegacyGucsSpec.hs rename to test/spec/Feature/LegacyGucsSpec.hs diff --git a/test/Feature/MultipleSchemaSpec.hs b/test/spec/Feature/MultipleSchemaSpec.hs similarity index 100% rename from test/Feature/MultipleSchemaSpec.hs rename to test/spec/Feature/MultipleSchemaSpec.hs diff --git a/test/Feature/NoJwtSpec.hs b/test/spec/Feature/NoJwtSpec.hs similarity index 100% rename from test/Feature/NoJwtSpec.hs rename to test/spec/Feature/NoJwtSpec.hs diff --git a/test/Feature/NonexistentSchemaSpec.hs b/test/spec/Feature/NonexistentSchemaSpec.hs similarity index 100% rename from test/Feature/NonexistentSchemaSpec.hs rename to test/spec/Feature/NonexistentSchemaSpec.hs diff --git a/test/Feature/OpenApiSpec.hs b/test/spec/Feature/OpenApiSpec.hs similarity index 100% rename from test/Feature/OpenApiSpec.hs rename to test/spec/Feature/OpenApiSpec.hs diff --git a/test/Feature/OptionsSpec.hs b/test/spec/Feature/OptionsSpec.hs similarity index 100% rename from test/Feature/OptionsSpec.hs rename to test/spec/Feature/OptionsSpec.hs diff --git a/test/Feature/ProxySpec.hs b/test/spec/Feature/ProxySpec.hs similarity index 100% rename from test/Feature/ProxySpec.hs rename to test/spec/Feature/ProxySpec.hs diff --git a/test/Feature/QueryLimitedSpec.hs b/test/spec/Feature/QueryLimitedSpec.hs similarity index 100% rename from test/Feature/QueryLimitedSpec.hs rename to test/spec/Feature/QueryLimitedSpec.hs diff --git a/test/Feature/QuerySpec.hs b/test/spec/Feature/QuerySpec.hs similarity index 100% rename from test/Feature/QuerySpec.hs rename to test/spec/Feature/QuerySpec.hs diff --git a/test/Feature/RangeSpec.hs b/test/spec/Feature/RangeSpec.hs similarity index 100% rename from test/Feature/RangeSpec.hs rename to test/spec/Feature/RangeSpec.hs diff --git a/test/Feature/RawOutputTypesSpec.hs b/test/spec/Feature/RawOutputTypesSpec.hs similarity index 100% rename from test/Feature/RawOutputTypesSpec.hs rename to test/spec/Feature/RawOutputTypesSpec.hs diff --git a/test/Feature/RollbackSpec.hs b/test/spec/Feature/RollbackSpec.hs similarity index 100% rename from test/Feature/RollbackSpec.hs rename to test/spec/Feature/RollbackSpec.hs diff --git a/test/Feature/RootSpec.hs b/test/spec/Feature/RootSpec.hs similarity index 100% rename from test/Feature/RootSpec.hs rename to test/spec/Feature/RootSpec.hs diff --git a/test/Feature/RpcPreRequestGucsSpec.hs b/test/spec/Feature/RpcPreRequestGucsSpec.hs similarity index 100% rename from test/Feature/RpcPreRequestGucsSpec.hs rename to test/spec/Feature/RpcPreRequestGucsSpec.hs diff --git a/test/Feature/RpcSpec.hs b/test/spec/Feature/RpcSpec.hs similarity index 99% rename from test/Feature/RpcSpec.hs rename to test/spec/Feature/RpcSpec.hs index b0fc0f7aed..9c861b7f58 100644 --- a/test/Feature/RpcSpec.hs +++ b/test/spec/Feature/RpcSpec.hs @@ -1150,7 +1150,7 @@ spec actualPgVersion = [str|unnamed text arg|] it "can insert bytea directly" $ do - let file = unsafePerformIO $ BL.readFile "test/C.png" + let file = unsafePerformIO $ BL.readFile "test/spec/fixtures/image.png" r <- request methodPost "/rpc/unnamed_bytea_param" [("Content-Type", "application/octet-stream"), ("Accept", "application/octet-stream")] file @@ -1184,7 +1184,7 @@ spec actualPgVersion = } it "will err when no function with single unnamed bytea parameter exists and application/octet-stream is specified" $ - let file = unsafePerformIO $ BL.readFile "test/C.png" in + let file = unsafePerformIO $ BL.readFile "test/spec/fixtures/image.png" in request methodPost "/rpc/unnamed_int_param" [("Content-Type", "application/octet-stream")] file @@ -1224,7 +1224,7 @@ spec actualPgVersion = [str|unnamed text arg|] `shouldRespondWith` [str|unnamed text arg|] - let file = unsafePerformIO $ BL.readFile "test/C.png" + let file = unsafePerformIO $ BL.readFile "test/spec/fixtures/image.png" r <- request methodPost "/rpc/overloaded_unnamed_param" [("Content-Type", "application/octet-stream"), ("Accept", "application/octet-stream")] file diff --git a/test/Feature/SingularSpec.hs b/test/spec/Feature/SingularSpec.hs similarity index 100% rename from test/Feature/SingularSpec.hs rename to test/spec/Feature/SingularSpec.hs diff --git a/test/Feature/UnicodeSpec.hs b/test/spec/Feature/UnicodeSpec.hs similarity index 100% rename from test/Feature/UnicodeSpec.hs rename to test/spec/Feature/UnicodeSpec.hs diff --git a/test/Feature/UpdateSpec.hs b/test/spec/Feature/UpdateSpec.hs similarity index 100% rename from test/Feature/UpdateSpec.hs rename to test/spec/Feature/UpdateSpec.hs diff --git a/test/Feature/UpsertSpec.hs b/test/spec/Feature/UpsertSpec.hs similarity index 100% rename from test/Feature/UpsertSpec.hs rename to test/spec/Feature/UpsertSpec.hs diff --git a/test/Main.hs b/test/spec/Main.hs similarity index 100% rename from test/Main.hs rename to test/spec/Main.hs diff --git a/test/QueryCost.hs b/test/spec/QueryCost.hs similarity index 100% rename from test/QueryCost.hs rename to test/spec/QueryCost.hs diff --git a/test/SpecHelper.hs b/test/spec/SpecHelper.hs similarity index 99% rename from test/SpecHelper.hs rename to test/spec/SpecHelper.hs index f9411f8f8a..068832eece 100644 --- a/test/SpecHelper.hs +++ b/test/spec/SpecHelper.hs @@ -55,7 +55,7 @@ validateOpenApiResponse headers = do respHeaders `shouldSatisfy` \hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs let Just body = decode (simpleBody r) - Just schema <- liftIO $ decode <$> BL.readFile "test/fixtures/openapi.json" + Just schema <- liftIO $ decode <$> BL.readFile "test/spec/fixtures/openapi.json" let args :: M.Map Text Value args = M.fromList [ ( "schema", schema ) diff --git a/test/TestTypes.hs b/test/spec/TestTypes.hs similarity index 100% rename from test/TestTypes.hs rename to test/spec/TestTypes.hs diff --git a/test/fixtures/data.sql b/test/spec/fixtures/data.sql similarity index 100% rename from test/fixtures/data.sql rename to test/spec/fixtures/data.sql diff --git a/test/fixtures/database.sql b/test/spec/fixtures/database.sql similarity index 100% rename from test/fixtures/database.sql rename to test/spec/fixtures/database.sql diff --git a/test/fixtures/draft04.json b/test/spec/fixtures/draft04.json similarity index 100% rename from test/fixtures/draft04.json rename to test/spec/fixtures/draft04.json diff --git a/test/C.png b/test/spec/fixtures/image.png similarity index 100% rename from test/C.png rename to test/spec/fixtures/image.png diff --git a/test/fixtures/jsonschema.sql b/test/spec/fixtures/jsonschema.sql similarity index 100% rename from test/fixtures/jsonschema.sql rename to test/spec/fixtures/jsonschema.sql diff --git a/test/fixtures/jwt.sql b/test/spec/fixtures/jwt.sql similarity index 100% rename from test/fixtures/jwt.sql rename to test/spec/fixtures/jwt.sql diff --git a/test/fixtures/load.sql b/test/spec/fixtures/load.sql similarity index 100% rename from test/fixtures/load.sql rename to test/spec/fixtures/load.sql diff --git a/test/fixtures/openapi.json b/test/spec/fixtures/openapi.json similarity index 100% rename from test/fixtures/openapi.json rename to test/spec/fixtures/openapi.json diff --git a/test/fixtures/privileges.sql b/test/spec/fixtures/privileges.sql similarity index 100% rename from test/fixtures/privileges.sql rename to test/spec/fixtures/privileges.sql diff --git a/test/fixtures/roles.sql b/test/spec/fixtures/roles.sql similarity index 100% rename from test/fixtures/roles.sql rename to test/spec/fixtures/roles.sql diff --git a/test/fixtures/schema.sql b/test/spec/fixtures/schema.sql similarity index 100% rename from test/fixtures/schema.sql rename to test/spec/fixtures/schema.sql diff --git a/test/with_tmp_db b/test/with_tmp_db index 478d5a19ea..5808f22266 100755 --- a/test/with_tmp_db +++ b/test/with_tmp_db @@ -86,7 +86,7 @@ stop() { trap stop EXIT log "Loading fixtures..." -psql -v ON_ERROR_STOP=1 -f test/fixtures/load.sql >> "$setuplog" +psql -v ON_ERROR_STOP=1 -f test/spec/fixtures/load.sql >> "$setuplog" log "Done. Running command..." # Run the command that was given as an argument. The `exit` trap above will From 1e4d44b0e6ff1cb2fd86633de598c6e1b5c6ac3d Mon Sep 17 00:00:00 2001 From: Steve Chavez Date: Fri, 7 Jan 2022 13:06:03 -0500 Subject: [PATCH 34/62] cov: empty coverage overlay (#2119) The overlay depended on the version number, which caused errors when upgrading. Also update cabal description. --- postgrest.cabal | 2 +- test/coverage.overlay | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/postgrest.cabal b/postgrest.cabal index b29b630f70..88518a19c3 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -2,7 +2,7 @@ name: postgrest version: 9.0.0 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 + for tables, views, and functions, supporting all HTTP verbs that security permits. license: MIT license-file: LICENSE diff --git a/test/coverage.overlay b/test/coverage.overlay index 8583637514..e69de29bb2 100644 --- a/test/coverage.overlay +++ b/test/coverage.overlay @@ -1,5 +0,0 @@ -module "postgrest-9.0.0.20211220-inplace/PostgREST.Middleware" { - inside "corsPolicy" { - tick "[]" on line 133; - } -} From fd6f8e4ec224395acace02c2b32a6bb7fb3cee0a Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Wed, 5 Jan 2022 17:11:28 +0100 Subject: [PATCH 35/62] refactor: Move each Wai Middleware to a separate file --- postgrest.cabal | 2 + src/PostgREST/App.hs | 7 +++- src/PostgREST/Cors.hs | 42 ++++++++++++++++++++ src/PostgREST/Logger.hs | 28 ++++++++++++++ src/PostgREST/Middleware.hs | 77 ++++++------------------------------- 5 files changed, 89 insertions(+), 67 deletions(-) create mode 100644 src/PostgREST/Cors.hs create mode 100644 src/PostgREST/Logger.hs diff --git a/postgrest.cabal b/postgrest.cabal index 88518a19c3..31a9587020 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -44,6 +44,7 @@ library PostgREST.Config.PgVersion PostgREST.Config.Proxy PostgREST.ContentType + PostgREST.Cors PostgREST.DbStructure PostgREST.DbStructure.Identifiers PostgREST.DbStructure.Proc @@ -51,6 +52,7 @@ library PostgREST.DbStructure.Table PostgREST.Error PostgREST.GucHeader + PostgREST.Logger PostgREST.Middleware PostgREST.OpenAPI PostgREST.Query.QueryBuilder diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index d70ac55efc..a2d84fe220 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -42,8 +42,10 @@ import qualified Network.Wai.Handler.Warp as Warp import qualified PostgREST.AppState as AppState import qualified PostgREST.Auth as Auth +import qualified PostgREST.Cors as Cors import qualified PostgREST.DbStructure as DbStructure import qualified PostgREST.Error as Error +import qualified PostgREST.Logger as Logger import qualified PostgREST.Middleware as Middleware import qualified PostgREST.OpenAPI as OpenAPI import qualified PostgREST.Query.QueryBuilder as QueryBuilder @@ -137,8 +139,9 @@ serverSettings AppConfig{..} = -- | PostgREST application postgrest :: LogLevel -> AppState.AppState -> IO () -> Wai.Application -postgrest logLev appState connWorker = - Middleware.pgrstMiddleware logLev $ +postgrest logLevel appState connWorker = + Logger.middleware logLevel . + Cors.middleware $ \req respond -> do time <- AppState.getTime appState conf <- AppState.getConfig appState diff --git a/src/PostgREST/Cors.hs b/src/PostgREST/Cors.hs new file mode 100644 index 0000000000..df38f1d907 --- /dev/null +++ b/src/PostgREST/Cors.hs @@ -0,0 +1,42 @@ +{-| +Module : PostgREST.Cors +Description : Wai Middleware to set cors policy. +-} +module PostgREST.Cors (middleware) where + +import qualified Data.ByteString.Char8 as BS +import qualified Data.CaseInsensitive as CI +import qualified Network.Wai as Wai +import qualified Network.Wai.Middleware.Cors as Wai + +import Data.List (lookup) + +import Protolude + +middleware :: Wai.Middleware +middleware = Wai.cors corsPolicy + +-- | CORS policy to be used in by Wai Cors middleware +corsPolicy :: Wai.Request -> Maybe Wai.CorsResourcePolicy +corsPolicy req = case lookup "origin" headers of + Just origin -> + Just Wai.CorsResourcePolicy + { Wai.corsOrigins = Just ([origin], True) + , Wai.corsMethods = ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"] + , Wai.corsRequestHeaders = "Authorization" : accHeaders + , Wai.corsExposedHeaders = Just + [ "Content-Encoding", "Content-Location", "Content-Range", "Content-Type" + , "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit"] + , Wai.corsMaxAge = Just $ 60*60*24 + , Wai.corsVaryOrigin = False + , Wai.corsRequireOrigin = False + , Wai.corsIgnoreFailures = True + } + Nothing -> Nothing + where + headers = Wai.requestHeaders req + accHeaders = case lookup "access-control-request-headers" headers of + Just hdrs -> map (CI.mk . BS.strip) $ BS.split ',' hdrs + -- Impossible case, Middleware.Cors will not evaluate this when + -- the Access-Control-Request-Headers header is not set. + Nothing -> [] diff --git a/src/PostgREST/Logger.hs b/src/PostgREST/Logger.hs new file mode 100644 index 0000000000..ba2645d3a4 --- /dev/null +++ b/src/PostgREST/Logger.hs @@ -0,0 +1,28 @@ +{-| +Module : PostgREST.Logger +Description : Wai Middleware to log requests to stdout. +-} +module PostgREST.Logger (middleware) where + +import qualified Network.Wai as Wai +import qualified Network.Wai.Middleware.RequestLogger as Wai + +import Network.HTTP.Types.Status (status400, status500) +import System.IO.Unsafe (unsafePerformIO) + +import PostgREST.Config (LogLevel (..)) + +import Protolude + +middleware :: LogLevel -> Wai.Middleware +middleware logLevel = case logLevel of + LogInfo -> requestLogger (const True) + LogWarn -> requestLogger (>= status400) + LogError -> requestLogger (>= status500) + LogCrit -> requestLogger (const False) + where + requestLogger filterStatus = unsafePerformIO $ Wai.mkRequestLogger Wai.defaultRequestLoggerSettings + { Wai.outputFormat = Wai.ApacheWithSettings $ + Wai.defaultApacheSettings + & Wai.setApacheRequestFilter (\_ res -> filterStatus $ Wai.responseStatus res) + } diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index aeafe53658..ef48dc381c 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -6,35 +6,25 @@ Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and {-# LANGUAGE RecordWildCards #-} module PostgREST.Middleware ( runPgLocals - , pgrstMiddleware , optionalRollback ) where -import qualified Data.Aeson as JSON -import qualified Data.ByteString.Char8 as BS -import qualified Data.ByteString.Lazy.Char8 as LBS -import qualified Data.CaseInsensitive as CI -import qualified Data.HashMap.Strict as M -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import qualified Hasql.Decoders as HD -import qualified Hasql.DynamicStatements.Snippet as SQL hiding - (sql) -import qualified Hasql.DynamicStatements.Statement as SQL -import qualified Hasql.Transaction as SQL -import qualified Network.Wai as Wai -import qualified Network.Wai.Middleware.Cors as Wai -import qualified Network.Wai.Middleware.RequestLogger as Wai +import qualified Data.Aeson as JSON +import qualified Data.ByteString.Lazy.Char8 as LBS +import qualified Data.HashMap.Strict as M +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import qualified Hasql.Decoders as HD +import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql) +import qualified Hasql.DynamicStatements.Statement as SQL +import qualified Hasql.Transaction as SQL +import qualified Network.Wai as Wai import Control.Arrow ((***)) -import Data.List (lookup) -import Data.Scientific (FPFormat (..), formatScientific, - isInteger) -import Network.HTTP.Types.Status (status400, status500) -import System.IO.Unsafe (unsafePerformIO) +import Data.Scientific (FPFormat (..), formatScientific, isInteger) -import PostgREST.Config (AppConfig (..), LogLevel (..)) +import PostgREST.Config (AppConfig (..)) import PostgREST.Config.PgVersion (PgVersion (..), pgVersion140) import PostgREST.Error (Error, errorResponseFor) import PostgREST.GucHeader (addHeadersIfNotIncluded) @@ -89,49 +79,6 @@ runPgLocals conf claims app req jsonDbS actualPgVersion = do unquoted (JSON.Bool b) = show b unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v -pgrstMiddleware :: LogLevel -> Wai.Middleware -pgrstMiddleware logLevel = - logger logLevel - . Wai.cors corsPolicy - -logger :: LogLevel -> Wai.Middleware -logger logLevel = case logLevel of - LogInfo -> requestLogger (const True) - LogWarn -> requestLogger (>= status400) - LogError -> requestLogger (>= status500) - LogCrit -> requestLogger (const False) - where - requestLogger filterStatus = unsafePerformIO $ Wai.mkRequestLogger Wai.defaultRequestLoggerSettings - { Wai.outputFormat = Wai.ApacheWithSettings $ - Wai.defaultApacheSettings - & Wai.setApacheRequestFilter (\_ res -> filterStatus $ Wai.responseStatus res) - } - --- | CORS policy to be used in by Wai Cors middleware -corsPolicy :: Wai.Request -> Maybe Wai.CorsResourcePolicy -corsPolicy req = case lookup "origin" headers of - Just origin -> - Just Wai.CorsResourcePolicy - { Wai.corsOrigins = Just ([origin], True) - , Wai.corsMethods = ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"] - , Wai.corsRequestHeaders = "Authorization" : accHeaders - , Wai.corsExposedHeaders = Just - [ "Content-Encoding", "Content-Location", "Content-Range", "Content-Type" - , "Date", "Location", "Server", "Transfer-Encoding", "Range-Unit"] - , Wai.corsMaxAge = Just $ 60*60*24 - , Wai.corsVaryOrigin = False - , Wai.corsRequireOrigin = False - , Wai.corsIgnoreFailures = True - } - Nothing -> Nothing - where - headers = Wai.requestHeaders req - accHeaders = case lookup "access-control-request-headers" headers of - Just hdrs -> map (CI.mk . BS.strip) $ BS.split ',' hdrs - -- Impossible case, Middleware.Cors will not evaluate this when - -- the Access-Control-Request-Headers header is not set. - Nothing -> [] - -- | Set a transaction to eventually roll back if requested and set respective -- headers on the response. optionalRollback From de2c8c7de55d6c8a99f15588bed8786d65731a12 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 10 Jan 2022 08:06:53 +0100 Subject: [PATCH 36/62] refactor: Reformat db-config query --- src/PostgREST/Config/Database.hs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/PostgREST/Config/Database.hs b/src/PostgREST/Config/Database.hs index a0f4107f16..f5cb478f39 100644 --- a/src/PostgREST/Config/Database.hs +++ b/src/PostgREST/Config/Database.hs @@ -36,19 +36,26 @@ dbSettingsStatement :: SQL.Statement () [(Text, Text)] dbSettingsStatement = SQL.Statement sql HE.noParams decodeSettings False where sql = [q| - with - role_setting as ( - select setdatabase, unnest(setconfig) as setting from pg_catalog.pg_db_role_setting - where setrole = current_user::regrole::oid - and setdatabase in (0, (select oid from pg_catalog.pg_database where datname = current_catalog)) + WITH + role_setting (database, setting) AS ( + SELECT setdatabase, + unnest(setconfig) + FROM pg_catalog.pg_db_role_setting + WHERE setrole = CURRENT_USER::regrole::oid + AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG)) ), - kv_settings as ( - select setdatabase, split_part(setting, '=', 1) as k, split_part(setting, '=', 2) as value from role_setting - where setting like 'pgrst.%' + kv_settings (database, k, v) AS ( + SELECT database, + split_part(setting, '=', 1), + split_part(setting, '=', 2) + FROM role_setting + WHERE setting LIKE 'pgrst.%' ) - select distinct on (key) replace(k, 'pgrst.', '') as key, value - from kv_settings - order by key, setdatabase desc; + SELECT DISTINCT ON (key) + replace(k, 'pgrst.', '') AS key, + v AS value + FROM kv_settings + ORDER BY key, database DESC; |] decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text From 5f5a8e40c61eccbe879741e212c09f57413df8f2 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 10 Jan 2022 08:37:01 +0100 Subject: [PATCH 37/62] fix: Read database configuration properly when `=` is present in value Resolves #2120 --- CHANGELOG.md | 3 +++ src/PostgREST/Config/Database.hs | 4 ++-- test/io/configs/expected/no-defaults-with-db.config | 4 ++-- test/io/db_config.sql | 6 +++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 458cc1ca7d..76ccd03dc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez - #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther - #1724, Fix wrong CORS header Authentication -> Authorization - @wolfgangwalther + - #2107, Clarify error for failed schema cache load. - @steve-chavez + + From `Database connection lost. Retrying the connection` to `Could not query the database for the schema cache. Retrying.` + - #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/Config/Database.hs b/src/PostgREST/Config/Database.hs index f5cb478f39..51c009de1a 100644 --- a/src/PostgREST/Config/Database.hs +++ b/src/PostgREST/Config/Database.hs @@ -46,8 +46,8 @@ dbSettingsStatement = SQL.Statement sql HE.noParams decodeSettings False ), kv_settings (database, k, v) AS ( SELECT database, - split_part(setting, '=', 1), - split_part(setting, '=', 2) + substr(setting, 1, strpos(setting, '=') - 1), + substr(setting, strpos(setting, '=') + 1) FROM role_setting WHERE setting LIKE 'pgrst.%' ) diff --git a/test/io/configs/expected/no-defaults-with-db.config b/test/io/configs/expected/no-defaults-with-db.config index 4159b6b4da..d3b2406506 100644 --- a/test/io/configs/expected/no-defaults-with-db.config +++ b/test/io/configs/expected/no-defaults-with-db.config @@ -15,8 +15,8 @@ db-uri = "" db-use-legacy-gucs = false jwt-aud = "https://example.org" jwt-role-claim-key = ".\"a\".\"role\"" -jwt-secret = "OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE" -jwt-secret-is-base64 = true +jwt-secret = "OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE" +jwt-secret-is-base64 = false log-level = "info" openapi-mode = "ignore-privileges" openapi-server-proxy-uri = "https://example.org/api" diff --git a/test/io/db_config.sql b/test/io/db_config.sql index c0fee5a826..973e727933 100644 --- a/test/io/db_config.sql +++ b/test/io/db_config.sql @@ -4,8 +4,8 @@ CREATE ROLE db_config_authenticator LOGIN NOINHERIT; ALTER ROLE db_config_authenticator SET pgrst.jwt_aud = 'https://example.org'; ALTER ROLE db_config_authenticator SET pgrst.openapi_server_proxy_uri = 'https://example.org/api'; ALTER ROLE db_config_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrst.db-config'; -ALTER ROLE db_config_authenticator SET pgrst.jwt_secret = 'REALLYREALLYREALLYREALLYVERYSAFE'; -ALTER ROLE db_config_authenticator SET pgrst.jwt_secret_is_base64 = 'true'; +ALTER ROLE db_config_authenticator SET pgrst.jwt_secret = 'REALLY=REALLY=REALLY=REALLY=VERY=SAFE'; +ALTER ROLE db_config_authenticator SET pgrst.jwt_secret_is_base64 = 'false'; ALTER ROLE db_config_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"'; ALTER ROLE db_config_authenticator SET pgrst.db_tx_end = 'commit-allow-override'; ALTER ROLE db_config_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2'; @@ -16,7 +16,7 @@ ALTER ROLE db_config_authenticator SET pgrst.db_max_rows = '1000'; ALTER ROLE db_config_authenticator SET pgrst.db_extra_search_path = 'public, extensions'; -- override with database specific setting -ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE'; +ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE'; ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.db_extra_search_path = 'public, extensions, private'; -- other database settings that should be ignored From a7da373d70fe16782c57817517dfb481b558af76 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 10 Jan 2022 09:10:02 +0100 Subject: [PATCH 38/62] Align columns in postgrest.cabal --- postgrest.cabal | 92 ++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/postgrest.cabal b/postgrest.cabal index 31a9587020..4008bb2433 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -233,51 +233,51 @@ test-suite spec -fwrite-ide-info test-suite querycost - type: exitcode-stdio-1.0 - default-language: Haskell2010 - default-extensions: OverloadedStrings - QuasiQuotes - NoImplicitPrelude - hs-source-dirs: test/spec - main-is: QueryCost.hs - other-modules: SpecHelper - build-depends: base >= 4.9 && < 4.16 - , aeson >= 1.4.7 && < 1.6 - , base64-bytestring >= 1 && < 1.3 - , bytestring >= 0.10.8 && < 0.11 - , case-insensitive >= 1.2 && < 1.3 - , containers >= 0.5.7 && < 0.7 - , contravariant >= 1.4 && < 1.6 - , hasql >= 1.4 && < 1.5 - , hasql-dynamic-statements == 0.3.1 - , hasql-pool >= 0.5 && < 0.6 - , hasql-transaction >= 1.0.1 && < 1.1 - , heredoc >= 0.2 && < 0.3 - , hspec >= 2.3 && < 2.9 - , hspec-wai >= 0.10 && < 0.12 - , http-types >= 0.12.3 && < 0.13 - , lens >= 4.14 && < 5.1 - , lens-aeson >= 1.0.1 && < 1.2 - , postgrest - , process >= 1.4.2 && < 1.7 - , protolude >= 0.3 && < 0.4 - , regex-tdfa >= 1.2.2 && < 1.4 - , wai-extra >= 3.0.19 && < 3.2 - ghc-options: -O0 -Werror -Wall -fwarn-identities - -fno-spec-constr -optP-Wno-nonportable-include-path - -fwrite-ide-info + type: exitcode-stdio-1.0 + default-language: Haskell2010 + default-extensions: OverloadedStrings + QuasiQuotes + NoImplicitPrelude + hs-source-dirs: test/spec + main-is: QueryCost.hs + other-modules: SpecHelper + build-depends: base >= 4.9 && < 4.16 + , aeson >= 1.4.7 && < 1.6 + , base64-bytestring >= 1 && < 1.3 + , bytestring >= 0.10.8 && < 0.11 + , case-insensitive >= 1.2 && < 1.3 + , containers >= 0.5.7 && < 0.7 + , contravariant >= 1.4 && < 1.6 + , hasql >= 1.4 && < 1.5 + , hasql-dynamic-statements == 0.3.1 + , hasql-pool >= 0.5 && < 0.6 + , hasql-transaction >= 1.0.1 && < 1.1 + , heredoc >= 0.2 && < 0.3 + , hspec >= 2.3 && < 2.9 + , hspec-wai >= 0.10 && < 0.12 + , http-types >= 0.12.3 && < 0.13 + , lens >= 4.14 && < 5.1 + , lens-aeson >= 1.0.1 && < 1.2 + , postgrest + , process >= 1.4.2 && < 1.7 + , protolude >= 0.3 && < 0.4 + , regex-tdfa >= 1.2.2 && < 1.4 + , wai-extra >= 3.0.19 && < 3.2 + ghc-options: -O0 -Werror -Wall -fwarn-identities + -fno-spec-constr -optP-Wno-nonportable-include-path + -fwrite-ide-info test-suite doctests - type: exitcode-stdio-1.0 - default-language: Haskell2010 - default-extensions: OverloadedStrings - NoImplicitPrelude - hs-source-dirs: test/doc - main-is: Main.hs - build-depends: base >= 4.9 && < 4.16 - , doctest >= 0.8 - , postgrest - , pretty-simple - , protolude >= 0.3 && < 0.4 - ghc-options: -threaded -O0 -Werror -Wall -fwarn-identities - -fno-spec-constr -optP-Wno-nonportable-include-path + type: exitcode-stdio-1.0 + default-language: Haskell2010 + default-extensions: OverloadedStrings + NoImplicitPrelude + hs-source-dirs: test/doc + main-is: Main.hs + build-depends: base >= 4.9 && < 4.16 + , doctest >= 0.8 + , postgrest + , pretty-simple + , protolude >= 0.3 && < 0.4 + ghc-options: -threaded -O0 -Werror -Wall -fwarn-identities + -fno-spec-constr -optP-Wno-nonportable-include-path From fa42f221b40e4f3cc1029e06af8c43be8a53accc Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 10 Jan 2022 09:20:28 +0100 Subject: [PATCH 39/62] ci: Add -with-rtsopts=-KxK option to prevent introducing space leaks Resolves #387 --- postgrest.cabal | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/postgrest.cabal b/postgrest.cabal index 4008bb2433..9a634d6ec4 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -150,6 +150,8 @@ executable postgrest if flag(dev) ghc-options: -O0 -fwrite-ide-info + -- https://github.com/PostgREST/postgrest/issues/387 + -with-rtsopts=-K1K if flag(hpc) ghc-options: -fhpc -hpcdir .hpc else @@ -231,6 +233,8 @@ test-suite spec -fno-spec-constr -optP-Wno-nonportable-include-path -fno-warn-missing-signatures -fwrite-ide-info + -- https://github.com/PostgREST/postgrest/issues/387 + -with-rtsopts=-K33K test-suite querycost type: exitcode-stdio-1.0 @@ -266,6 +270,8 @@ test-suite querycost ghc-options: -O0 -Werror -Wall -fwarn-identities -fno-spec-constr -optP-Wno-nonportable-include-path -fwrite-ide-info + -- https://github.com/PostgREST/postgrest/issues/387 + -with-rtsopts=-K1K test-suite doctests type: exitcode-stdio-1.0 From 27c4b439948c91fbf1c623194e48cbe6d4971cb1 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 24 Jan 2022 15:12:00 +0100 Subject: [PATCH 40/62] fix: Remove trigger functions from schema cache and OpenAPI output Trigger functions can't be called directly from SQL and can't be called via the /rpc prefix either - it makes no sense to expose them in the OpenAPI output. And we don't need to cache them in the schema cache either. Best practice would be to keep the trigger functions in a non-exposed schema anyway. --- CHANGELOG.md | 2 ++ src/PostgREST/DbStructure.hs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ccd03dc5..1a295e11d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2107, Clarify error for failed schema cache load. - @steve-chavez + From `Database connection lost. Retrying the connection` to `Could not query the database for the schema cache. Retrying.` - #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther + - #1771, Fix silently ignoring filter on a non-existent embedded resource - @steve-chavez + - #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 721a10ea1c..ebb872ba6d 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -227,12 +227,12 @@ decodeProcs = allProcs :: Bool -> SQL.Statement [Schema] ProcsMap allProcs = SQL.Statement sql (arrayParam HE.text) decodeProcs where - sql = procsSqlQuery <> " WHERE pn.nspname = ANY($1)" + sql = procsSqlQuery <> " AND pn.nspname = ANY($1)" accessibleProcs :: Bool -> SQL.Statement Schema ProcsMap accessibleProcs = SQL.Statement sql (param HE.text) decodeProcs where - sql = procsSqlQuery <> " WHERE pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" + sql = procsSqlQuery <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" procsSqlQuery :: SqlQuery procsSqlQuery = [q| @@ -297,6 +297,7 @@ procsSqlQuery = [q| JOIN pg_namespace tn ON tn.oid = t.typnamespace LEFT JOIN pg_class comp ON comp.oid = t.typrelid LEFT JOIN pg_catalog.pg_description as d ON d.objoid = p.oid + WHERE t.oid <> 'pg_catalog.trigger'::regtype |] schemaDescription :: Bool -> SQL.Statement Schema (Maybe Text) From a56b663a0c79b2e2b3cdfad8b5dd5c6c536c281d Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 28 Jan 2022 16:00:53 +0100 Subject: [PATCH 41/62] ci: Fix windows build failing because of outdated keyring Signed-off-by: Wolfgang Walther --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9eb5cd8bb9..2958bc5f60 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -168,6 +168,8 @@ jobs: ~\AppData\Local\Programs\stack .stack-work deps: | + stack exec -- pacman -Syu --noconfirm + stack exec -- pacman -S msys2-keyring --noconfirm stack exec -- pacman -S mingw64/mingw-w64-x86_64-postgresql --noconfirm # We'd need to make test/with_tmp_db run on Windows first # test: true From 1c80ffceacd12bbecc948feb293102c3f871bd26 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sat, 5 Feb 2022 12:43:45 +0100 Subject: [PATCH 42/62] fix: Take PG version into account in --dump-schema The PG version is only read by the Connection Worker, which is not used in the case dump-schema. Now, the pg version is read in the schema cache queries directly, avoiding this problem in all cases. Signed-off-by: Wolfgang Walther --- CHANGELOG.md | 7 +++++++ src/PostgREST/CLI.hs | 2 -- src/PostgREST/Config/Database.hs | 8 ++++++-- src/PostgREST/DbStructure.hs | 6 ++++-- src/PostgREST/Workers.hs | 3 +-- test/spec/Main.hs | 6 ++---- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a295e11d1..6f8ada225c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther - #1771, Fix silently ignoring filter on a non-existent embedded resource - @steve-chavez - #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther + - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther + - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther + +### Changed + + - #2001, Return 204 No Content without Content-Type for RPCs returning VOID - @wolfgangwalther + + Previously, those RPCs would return "null" as a body with Content-Type: application/json. ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/CLI.hs b/src/PostgREST/CLI.hs index 6cd604dfc5..6fddad387b 100644 --- a/src/PostgREST/CLI.hs +++ b/src/PostgREST/CLI.hs @@ -53,7 +53,6 @@ main installSignalHandlers runAppWithSocket CLI{cliCommand, cliPath} = do dumpSchema :: AppState -> IO LBS.ByteString dumpSchema appState = do AppConfig{..} <- AppState.getConfig appState - actualPgVersion <- AppState.getPgVersion appState result <- let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in SQL.use (AppState.getPool appState) $ @@ -61,7 +60,6 @@ dumpSchema appState = do queryDbStructure (toList configDbSchemas) configDbExtraSearchPath - actualPgVersion configDbPreparedStatements SQL.release $ AppState.getPool appState case result of diff --git a/src/PostgREST/Config/Database.hs b/src/PostgREST/Config/Database.hs index 51c009de1a..9e439ac198 100644 --- a/src/PostgREST/Config/Database.hs +++ b/src/PostgREST/Config/Database.hs @@ -1,7 +1,8 @@ {-# LANGUAGE QuasiQuotes #-} module PostgREST.Config.Database - ( queryDbSettings + ( pgVersionStatement + , queryDbSettings , queryPgVersion ) where @@ -20,7 +21,10 @@ import Text.InterpolatedString.Perl6 (q) import Protolude queryPgVersion :: Session PgVersion -queryPgVersion = statement mempty $ SQL.Statement sql HE.noParams versionRow False +queryPgVersion = statement mempty pgVersionStatement + +pgVersionStatement :: SQL.Statement () PgVersion +pgVersionStatement = SQL.Statement sql HE.noParams versionRow False where sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')" versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index ebb872ba6d..44199a1ee5 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -41,6 +41,7 @@ import Data.Set as S (fromList) import Data.Text (split) import Text.InterpolatedString.Perl6 (q) +import PostgREST.Config.Database (pgVersionStatement) import PostgREST.Config.PgVersion (PgVersion, pgVersion100) import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..), Schema, TableName) @@ -83,9 +84,10 @@ type ViewColumn = Column -- | A SQL query that can be executed independently type SqlQuery = ByteString -queryDbStructure :: [Schema] -> [Schema] -> PgVersion -> Bool -> SQL.Transaction DbStructure -queryDbStructure schemas extraSearchPath pgVer prepared = do +queryDbStructure :: [Schema] -> [Schema] -> Bool -> SQL.Transaction DbStructure +queryDbStructure schemas extraSearchPath prepared = do SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object + pgVer <- SQL.statement mempty pgVersionStatement tabs <- SQL.statement mempty $ allTables pgVer prepared cols <- SQL.statement schemas $ allColumns tabs prepared srcCols <- SQL.statement (schemas, extraSearchPath) $ pfkSourceColumns cols prepared diff --git a/src/PostgREST/Workers.hs b/src/PostgREST/Workers.hs index 9f716e3829..0144ef5454 100644 --- a/src/PostgREST/Workers.hs +++ b/src/PostgREST/Workers.hs @@ -152,11 +152,10 @@ connectionStatus appState = loadSchemaCache :: AppState -> IO SCacheStatus loadSchemaCache appState = do AppConfig{..} <- AppState.getConfig appState - actualPgVersion <- AppState.getPgVersion appState result <- let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in SQL.use (AppState.getPool appState) . transaction SQL.ReadCommitted SQL.Read $ - queryDbStructure (toList configDbSchemas) configDbExtraSearchPath actualPgVersion configDbPreparedStatements + queryDbStructure (toList configDbSchemas) configDbExtraSearchPath configDbPreparedStatements case result of Left e -> do let diff --git a/test/spec/Main.hs b/test/spec/Main.hs index a292efd6a2..9e1e09a417 100644 --- a/test/spec/Main.hs +++ b/test/spec/Main.hs @@ -68,7 +68,6 @@ main = do loadDbStructure pool (configDbSchemas $ testCfg testDbConn) (configDbExtraSearchPath $ testCfg testDbConn) - actualPgVersion let -- For tests that run with the same refDbStructure @@ -88,7 +87,6 @@ main = do loadDbStructure pool (configDbSchemas config) (configDbExtraSearchPath config) - actualPgVersion appState <- AppState.initWithPool pool config AppState.putPgVersion appState actualPgVersion AppState.putDbStructure appState customDbStructure @@ -232,5 +230,5 @@ main = do describe "Feature.RollbackForcedSpec" Feature.RollbackSpec.forced where - loadDbStructure pool schemas extraSearchPath actualPgVersion = - either (panic.show) id <$> P.use pool (HT.transaction HT.ReadCommitted HT.Read $ queryDbStructure (toList schemas) extraSearchPath actualPgVersion True) + loadDbStructure pool schemas extraSearchPath = + either (panic.show) id <$> P.use pool (HT.transaction HT.ReadCommitted HT.Read $ queryDbStructure (toList schemas) extraSearchPath True) From f9ce5958a3a89d63c323b2f72ef4e1f280de555f Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sat, 5 Feb 2022 11:29:06 +0100 Subject: [PATCH 43/62] fix: Remove aggregates, procedures and window functions from schema cache and OpenAPI output Aggregates and Window functions can't be called as RPCs in a useful way. Procedures are not supported right now, but might be added later. Resolves #2101 Signed-off-by: Wolfgang Walther --- CHANGELOG.md | 1 + src/PostgREST/App.hs | 2 +- src/PostgREST/DbStructure.hs | 23 ++++++++++++----------- test/spec/fixtures/schema.sql | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8ada225c..4181805eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther - #1771, Fix silently ignoring filter on a non-existent embedded resource - @steve-chavez - #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther + - #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index a2d84fe220..14af71940b 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -469,7 +469,7 @@ handleOpenApi headersOnly tSchema (RequestContext conf@AppConfig{..} dbStructure OAFollowPriv -> OpenAPI.encode conf dbStructure <$> SQL.statement tSchema (DbStructure.accessibleTables ctxPgVersion configDbPreparedStatements) - <*> SQL.statement tSchema (DbStructure.accessibleProcs configDbPreparedStatements) + <*> SQL.statement tSchema (DbStructure.accessibleProcs ctxPgVersion configDbPreparedStatements) <*> SQL.statement tSchema (DbStructure.schemaDescription configDbPreparedStatements) OAIgnorePriv -> OpenAPI.encode conf dbStructure diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 44199a1ee5..1caee31f97 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -42,7 +42,8 @@ import Data.Text (split) import Text.InterpolatedString.Perl6 (q) import PostgREST.Config.Database (pgVersionStatement) -import PostgREST.Config.PgVersion (PgVersion, pgVersion100) +import PostgREST.Config.PgVersion (PgVersion, pgVersion100, + pgVersion110) import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..), Schema, TableName) import PostgREST.DbStructure.Proc (PgType (..), @@ -93,7 +94,7 @@ queryDbStructure schemas extraSearchPath prepared = do srcCols <- SQL.statement (schemas, extraSearchPath) $ pfkSourceColumns cols prepared m2oRels <- SQL.statement mempty $ allM2ORels tabs cols prepared keys <- SQL.statement mempty $ allPrimaryKeys tabs prepared - procs <- SQL.statement schemas $ allProcs prepared + procs <- SQL.statement schemas $ allProcs pgVer prepared let rels = addO2MRels . addM2MRels $ addViewM2ORels srcCols m2oRels keys' = addViewPrimaryKeys srcCols keys @@ -226,18 +227,18 @@ decodeProcs = | v == 's' = Stable | otherwise = Volatile -- only 'v' can happen here -allProcs :: Bool -> SQL.Statement [Schema] ProcsMap -allProcs = SQL.Statement sql (arrayParam HE.text) decodeProcs +allProcs :: PgVersion -> Bool -> SQL.Statement [Schema] ProcsMap +allProcs pgVer = SQL.Statement sql (arrayParam HE.text) decodeProcs where - sql = procsSqlQuery <> " AND pn.nspname = ANY($1)" + sql = procsSqlQuery pgVer <> " AND pn.nspname = ANY($1)" -accessibleProcs :: Bool -> SQL.Statement Schema ProcsMap -accessibleProcs = SQL.Statement sql (param HE.text) decodeProcs +accessibleProcs :: PgVersion -> Bool -> SQL.Statement Schema ProcsMap +accessibleProcs pgVer = SQL.Statement sql (param HE.text) decodeProcs where - sql = procsSqlQuery <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" + sql = procsSqlQuery pgVer <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" -procsSqlQuery :: SqlQuery -procsSqlQuery = [q| +procsSqlQuery :: PgVersion -> SqlQuery +procsSqlQuery pgVer = [q| -- Recursively get the base types of domains WITH base_types AS ( @@ -300,7 +301,7 @@ procsSqlQuery = [q| LEFT JOIN pg_class comp ON comp.oid = t.typrelid LEFT JOIN pg_catalog.pg_description as d ON d.objoid = p.oid WHERE t.oid <> 'pg_catalog.trigger'::regtype -|] +|] <> (if pgVer >= pgVersion110 then "AND prokind = 'f'" else "AND NOT (proisagg OR proiswindow)") schemaDescription :: Bool -> SQL.Statement Schema (Maybe Text) schemaDescription = diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index eb25c6cc09..88a6d3b182 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2427,3 +2427,21 @@ BEGIN END IF; END $do$; + +-- This procedure is to confirm that procedures don't show up in the OpenAPI output right now. +-- Procedures are not supported, yet. +do $do$begin + if (select current_setting('server_version_num')::int >= 110000) then + CREATE PROCEDURE test.unsupported_proc () + LANGUAGE SQL AS ''; + end if; +end $do$; + +CREATE FUNCTION public.dummy(int) RETURNS int +LANGUAGE SQL AS $$ SELECT 1 $$; + +-- This aggregate is to confirm that aggregates don't show up in the OpenAPI output. +CREATE AGGREGATE test.unsupported_agg (*) ( + SFUNC = public.dummy, + STYPE = int +); From 92a30415fad1f497c3c2d8ceb5867950b08b1d43 Mon Sep 17 00:00:00 2001 From: Steve Chavez Date: Wed, 9 Feb 2022 17:27:29 -0500 Subject: [PATCH 44/62] fix: Keep working when EMFILE is reached (#2158) Done by upgrading to warp 3.3.19 --- CHANGELOG.md | 5 +++++ nix/overlays/haskell-packages.nix | 9 +++++++++ postgrest.cabal | 2 +- stack.yaml | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4181805eeb..85408341f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther + - #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther + - #2152, Remove functions, which are uncallable because of unnamend arguments from schema cache and OpenAPI output. - @wolfgangwalther + - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther + - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther + - #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez ### Changed diff --git a/nix/overlays/haskell-packages.nix b/nix/overlays/haskell-packages.nix index 75d27d7613..665a993cfa 100644 --- a/nix/overlays/haskell-packages.nix +++ b/nix/overlays/haskell-packages.nix @@ -50,6 +50,15 @@ let } { }; + warp = + lib.dontCheck (prev.callHackageDirect + { + pkg = "warp"; + ver = "3.3.19"; + sha256 = "0y3jj4bhviss6ff9lwxki0zbdcl1rb398bk4s80zvfpnpy7p94cx"; + } + { }); + hasql-dynamic-statements = lib.dontCheck (lib.unmarkBroken prev.hasql-dynamic-statements); diff --git a/postgrest.cabal b/postgrest.cabal index 9a634d6ec4..5d0653556d 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -110,7 +110,7 @@ library , wai >= 3.2.1 && < 3.3 , wai-cors >= 0.2.5 && < 0.3 , wai-extra >= 3.1.8 && < 3.2 - , warp >= 3.2.12 && < 3.4 + , warp >= 3.3.19 && < 3.4 -- -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 diff --git a/stack.yaml b/stack.yaml index fd03c455de..8e2426c588 100644 --- a/stack.yaml +++ b/stack.yaml @@ -15,3 +15,4 @@ extra-deps: - ptr-0.16.8.1@sha256:525219ec5f5da5c699725f7efcef91b00a7d44120fc019878b85c09440bf51d6,2686 - wai-extra-3.1.8@sha256:bf3dbe8f4c707b502b2a88262ed71c807220651597b76b56983f864af6197890,7280 - wai-logger-2.3.7@sha256:19a0dc5122e22d274776d80786fb9501956f5e75b8f82464bbdad5604d154d82,1671 + - warp-3.3.19@sha256:c6a47029537d42844386170d732cdfe6d85b2f4279bbaefdd9b50caff6faeebb,10910 From 06f291198d2c126dd21217d78a4ce726c9bbd481 Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Mon, 28 Feb 2022 13:23:55 -0500 Subject: [PATCH 45/62] fix: Using GET with certain Content-Type headers now correctly calls the no parameter function if it exists Using GET with text/plain or application/octet-stream as Content-Type headers no longer returns 404 Not Found when a function with no parameters exists --- CHANGELOG.md | 2 ++ src/PostgREST/Request/ApiRequest.hs | 2 +- test/spec/Feature/RpcSpec.hs | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85408341f9..375cfc8d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther - #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez + - #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available. + - ``` ### Changed diff --git a/src/PostgREST/Request/ApiRequest.hs b/src/PostgREST/Request/ApiRequest.hs index cd52719653..00e076d1b1 100644 --- a/src/PostgREST/Request/ApiRequest.hs +++ b/src/PostgREST/Request/ApiRequest.hs @@ -505,7 +505,7 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost = then length params == 1 && (ppType <$> headMay params) `elem` [Just "json", Just "jsonb"] -- If the function has no parameters, the arguments keys must be empty as well else if null params - then null argumentsKeys && contentType `notElem` [CTTextPlain, CTOctetStream] + then null argumentsKeys && not (isInvPost && contentType `elem` [CTTextPlain, CTOctetStream]) -- A function has optional and required parameters. Optional parameters have a default value and -- don't require arguments for the function to be executed, required parameters must have an argument present. else case L.partition ppReq params of diff --git a/test/spec/Feature/RpcSpec.hs b/test/spec/Feature/RpcSpec.hs index 9c861b7f58..a225973e32 100644 --- a/test/spec/Feature/RpcSpec.hs +++ b/test/spec/Feature/RpcSpec.hs @@ -1232,6 +1232,16 @@ spec actualPgVersion = let respBody = simpleBody r respBody `shouldBe` file + it "should call the function with no parameters and not fallback to the single unnamed parameter function when using GET with Content-Type headers" $ do + request methodGet "/rpc/overloaded_unnamed_param" [("Content-Type", "text/plain")] "" + `shouldRespondWith` + [json| 1|] + { matchStatus = 200 } + request methodGet "/rpc/overloaded_unnamed_param" [("Content-Type", "application/octet-stream")] "" + `shouldRespondWith` + [json| 1|] + { matchStatus = 200 } + it "should fail to fallback to any single unnamed parameter function when using an unsupported Content-Type header" $ do request methodPost "/rpc/overloaded_unnamed_param" [("Content-Type", "text/csv")] From 7b4120bc40bfd89770ab4bae0bee5a57e5d055c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 22:54:47 +0100 Subject: [PATCH 46/62] build(deps): bump actions/download-artifact from 2.1.0 to 3 (#2185) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2958bc5f60..1734c07668 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -294,7 +294,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Download all artifacts - uses: actions/download-artifact@v2.1.0 + uses: actions/download-artifact@v3 with: path: artifacts - name: Create release bundle with archives for all builds @@ -357,7 +357,7 @@ jobs: with: tools: release - name: Download Docker image - uses: actions/download-artifact@v2.1.0 + uses: actions/download-artifact@v3 with: name: postgrest-docker-x64 - name: Publish images on Docker Hub From 4e65a6f5839a7b3781baa484117da64880509b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 01:46:30 +0100 Subject: [PATCH 47/62] build(deps): bump actions/upload-artifact from 2.3.1 to 3 (#2187) --- .github/workflows/ci.yaml | 12 ++++++------ .github/workflows/loadtest.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1734c07668..96fa957983 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -115,7 +115,7 @@ jobs: - name: Build static executable run: nix-build -A postgrestStatic - name: Save built executable as artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: postgrest-linux-static-x64 path: result/bin/postgrest @@ -124,7 +124,7 @@ jobs: - name: Build Docker image run: nix-build -A docker.image --out-link postgrest-docker.tar.gz - name: Save built Docker image as artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: postgrest-docker-x64 path: postgrest-docker.tar.gz @@ -196,7 +196,7 @@ jobs: echo "Using PostgreSQL binaries at $postgresql_bin ..." PATH="$postgresql_bin:$PATH" test/with_tmp_db stack test - name: Save built executable as artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: ${{ matrix.artifact }} path: | @@ -216,7 +216,7 @@ jobs: GITHUB_COMMIT: ${{github.event.pull_request.head.sha || github.sha}} run: .github/get_cirrusci_freebsd - name: Save executable as artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: postgrest-freebsd-x64 path: postgrest @@ -276,7 +276,7 @@ jobs: echo "Relevant extract from CHANGELOG.md:" cat CHANGES.md - name: Save CHANGES.md as artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: release-changes path: CHANGES.md @@ -321,7 +321,7 @@ jobs: artifacts/postgrest-windows-x64/postgrest.exe - name: Save release bundle - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: release-bundle path: release-bundle diff --git a/.github/workflows/loadtest.yaml b/.github/workflows/loadtest.yaml index 8bcedac39d..228b163790 100644 --- a/.github/workflows/loadtest.yaml +++ b/.github/workflows/loadtest.yaml @@ -27,7 +27,7 @@ jobs: postgrest-loadtest-against main postgrest-loadtest-report > loadtest/loadtest.md - name: Upload report - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: loadtest.md path: loadtest/loadtest.md From 10a8828f66284a51ec22050dc0c91016594bfcec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 01:56:58 +0100 Subject: [PATCH 48/62] build(deps): bump actions/checkout from 2.4.0 to 3 (#2186) --- .github/workflows/ci.yaml | 20 ++++++++++---------- .github/workflows/loadtest.yaml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 96fa957983..65a651c38e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: name: Lint & check code style runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: @@ -35,7 +35,7 @@ jobs: # https://github.com/actions/runner/issues/241#issuecomment-842566950 shell: script -qec "bash --noprofile --norc -eo pipefail {0}" steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: @@ -70,7 +70,7 @@ jobs: # https://github.com/actions/runner/issues/241#issuecomment-842566950 shell: script -qec "bash --noprofile --norc -eo pipefail {0}" steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: @@ -93,7 +93,7 @@ jobs: name: Test memory (Nix) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: @@ -106,7 +106,7 @@ jobs: name: Build Linux static (Nix) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: @@ -178,7 +178,7 @@ jobs: name: Build ${{ matrix.name }} (Stack) runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Stack working files cache uses: actions/cache@v2.1.7 with: @@ -209,7 +209,7 @@ jobs: name: Get FreeBSD build from CirrusCI runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Get FreeBSD executable from CirrusCI env: # GITHUB_SHA does weird things for pull request, so we roll our own: @@ -239,7 +239,7 @@ jobs: version: ${{ steps.Identify-Version.outputs.version }} isprerelease: ${{ steps.Identify-Version.outputs.isprerelease }} steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - id: Identify-Version name: Identify the version to be released run: | @@ -292,7 +292,7 @@ jobs: env: VERSION: ${{ needs.Prepare-Release.outputs.version }} steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Download all artifacts uses: actions/download-artifact@v3 with: @@ -351,7 +351,7 @@ jobs: VERSION: ${{ needs.Prepare-Release.outputs.version }} ISPRERELEASE: ${{ needs.Prepare-Release.outputs.isprerelease }} steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Setup Nix Environment uses: ./.github/actions/setup-nix with: diff --git a/.github/workflows/loadtest.yaml b/.github/workflows/loadtest.yaml index 228b163790..b14f7c11b0 100644 --- a/.github/workflows/loadtest.yaml +++ b/.github/workflows/loadtest.yaml @@ -15,7 +15,7 @@ jobs: name: Loadtest (Nix) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup Nix Environment From 15a6eeb5e842e1f0a99e266c7fca584a91a5ca48 Mon Sep 17 00:00:00 2001 From: Ezequiel Alvarez Date: Sat, 26 Mar 2022 11:16:35 -0300 Subject: [PATCH 49/62] fix: json/jsonb columns should not have type "string" in OpenAPI spec (#2203) * Switch to no type for json/jsonb --- CHANGELOG.md | 1 + src/PostgREST/OpenAPI.hs | 34 +++++++++++++++++--------------- test/spec/Feature/OpenApiSpec.hs | 34 ++++++++++++++++++++++++++++---- test/spec/fixtures/schema.sql | 4 +++- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 375cfc8d8a..d6145d7790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed + - #2165, Fix json/jsonb columns should not have type in OpenAPI spec - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez diff --git a/src/PostgREST/OpenAPI.hs b/src/PostgREST/OpenAPI.hs index cfdaae5c28..50e932466c 100644 --- a/src/PostgREST/OpenAPI.hs +++ b/src/PostgREST/OpenAPI.hs @@ -55,24 +55,26 @@ encode conf dbStructure tables procs schemaDescription = makeMimeList :: [ContentType] -> MimeList makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs -toSwaggerType :: Text -> SwaggerType t -toSwaggerType "character varying" = SwaggerString -toSwaggerType "character" = SwaggerString -toSwaggerType "text" = SwaggerString -toSwaggerType "boolean" = SwaggerBoolean -toSwaggerType "smallint" = SwaggerInteger -toSwaggerType "integer" = SwaggerInteger -toSwaggerType "bigint" = SwaggerInteger -toSwaggerType "numeric" = SwaggerNumber -toSwaggerType "real" = SwaggerNumber -toSwaggerType "double precision" = SwaggerNumber -toSwaggerType "ARRAY" = SwaggerArray -toSwaggerType _ = SwaggerString +toSwaggerType :: Text -> Maybe (SwaggerType t) +toSwaggerType "character varying" = Just SwaggerString +toSwaggerType "character" = Just SwaggerString +toSwaggerType "text" = Just SwaggerString +toSwaggerType "boolean" = Just SwaggerBoolean +toSwaggerType "smallint" = Just SwaggerInteger +toSwaggerType "integer" = Just SwaggerInteger +toSwaggerType "bigint" = Just SwaggerInteger +toSwaggerType "numeric" = Just SwaggerNumber +toSwaggerType "real" = Just SwaggerNumber +toSwaggerType "double precision" = Just SwaggerNumber +toSwaggerType "ARRAY" = Just SwaggerArray +toSwaggerType "json" = Nothing +toSwaggerType "jsonb" = Nothing +toSwaggerType _ = Just SwaggerString parseDefault :: Text -> Text -> Text parseDefault colType colDefault = case toSwaggerType colType of - SwaggerString -> wrapInQuotations $ case T.stripSuffix ("::" <> colType) colDefault of + Just SwaggerString -> wrapInQuotations $ case T.stripSuffix ("::" <> colType) colDefault of Just def -> T.dropAround (=='\'') def Nothing -> colDefault _ -> colDefault @@ -124,7 +126,7 @@ makeProperty rels pks c = (colName c, Inline s) & enum_ .~ e & format ?~ colType c & maxLength .~ (fromIntegral <$> colMaxLen c) - & type_ ?~ toSwaggerType (colType c) + & type_ .~ toSwaggerType (colType c) makeProcSchema :: ProcDescription -> Schema makeProcSchema pd = @@ -138,7 +140,7 @@ makeProcProperty :: ProcParam -> (Text, Referenced Schema) makeProcProperty (ProcParam n t _ _) = (n, Inline s) where s = (mempty :: Schema) - & type_ ?~ toSwaggerType t + & type_ .~ toSwaggerType t & format ?~ t makePreferParam :: [Text] -> Param diff --git a/test/spec/Feature/OpenApiSpec.hs b/test/spec/Feature/OpenApiSpec.hs index e594bb04e9..7e888a0caf 100644 --- a/test/spec/Feature/OpenApiSpec.hs +++ b/test/spec/Feature/OpenApiSpec.hs @@ -425,6 +425,34 @@ spec actualPgVersion = describe "OpenAPI" $ do } |] + it "json to any" $ do + r <- simpleBody <$> get "/" + + let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_json" + + liftIO $ + + types `shouldBe` Just + [aesonQQ| + { + "format": "json" + } + |] + + it "jsonb to any" $ do + r <- simpleBody <$> get "/" + + let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_jsonb" + + liftIO $ + + types `shouldBe` Just + [aesonQQ| + { + "format": "jsonb" + } + |] + describe "Detects default values" $ do it "text" $ do @@ -543,12 +571,10 @@ spec actualPgVersion = describe "OpenAPI" $ do "type": "integer" }, "json": { - "format": "json", - "type": "string" + "format": "json" }, "jsonb": { - "format": "jsonb", - "type": "string" + "format": "jsonb" } }, "type": "object", diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index 88a6d3b182..43f0ed8512 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -1807,7 +1807,9 @@ CREATE TABLE test.openapi_types( "a_bigint" bigint, "a_numeric" numeric, "a_real" real, - "a_double_precision" double precision + "a_double_precision" double precision, + "a_json" json, + "a_jsonb" jsonb ); CREATE TABLE test.openapi_defaults( From 092e83103390bf2b31932e32b400a264efb4c02a Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Sat, 26 Mar 2022 15:14:54 -0500 Subject: [PATCH 50/62] Bypass FreeBSD builds due to timeouts --- .github/workflows/ci.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 65a651c38e..8e9f5e820b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -234,7 +234,7 @@ jobs: - Test-Memory-Nix - Build-Static-Nix - Build-Stack - - Get-FreeBSD-CirrusCI + #- Get-FreeBSD-CirrusCI outputs: version: ${{ steps.Identify-Version.outputs.version }} isprerelease: ${{ steps.Identify-Version.outputs.isprerelease }} @@ -314,8 +314,9 @@ jobs: tar cJvf "release-bundle/postgrest-v$VERSION-macos-x64.tar.xz" \ -C artifacts/postgrest-macos-x64 postgrest - tar cJvf "release-bundle/postgrest-v$VERSION-freebsd-x64.tar.xz" \ - -C artifacts/postgrest-freebsd-x64 postgrest + # TODO: Fix timeouts for FreeBSD builds in Cirrus + #tar cJvf "release-bundle/postgrest-v$VERSION-freebsd-x64.tar.xz" \ + # -C artifacts/postgrest-freebsd-x64 postgrest zip "release-bundle/postgrest-v$VERSION-windows-x64.zip" \ artifacts/postgrest-windows-x64/postgrest.exe From 1cc3f617752a5a397d3029f88b2bbb8c16217d29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 04:12:30 +0000 Subject: [PATCH 51/62] build(deps): bump actions/cache from 2.1.7 to 3 Bumps [actions/cache](https://github.com/actions/cache) from 2.1.7 to 3. - [Release notes](https://github.com/actions/cache/releases) - [Commits](https://github.com/actions/cache/compare/v2.1.7...v3) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8e9f5e820b..06eb8d3349 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -180,7 +180,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Stack working files cache - uses: actions/cache@v2.1.7 + uses: actions/cache@v3 with: path: ${{ matrix.cache }} key: ${{ runner.os }}-${{ hashFiles('stack.yaml.lock') }} From 0c144031ae99d6e3f7debf066dbbbe210a0a6fa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 04:16:14 +0000 Subject: [PATCH 52/62] build(deps): bump codecov/codecov-action from 2.1.0 to 3.0.0 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.1.0 to 3.0.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v2.1.0...v3.0.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 06eb8d3349..2406054e69 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,7 @@ jobs: - name: Run coverage (IO tests and Spec tests against PostgreSQL 14) run: postgrest-coverage - name: Upload coverage to codecov - uses: codecov/codecov-action@v2.1.0 + uses: codecov/codecov-action@v3.0.0 with: files: ./coverage/codecov.json From ba7bdbd485ab8fea3112035412ca8881ce3192cb Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Mon, 11 Apr 2022 17:02:19 -0500 Subject: [PATCH 53/62] Fix misleading disambiguation error where `relationship` looks like valid syntax * Add columns for the m2m relationship --- CHANGELOG.md | 2 + src/PostgREST/Error.hs | 9 ++-- test/spec/Feature/EmbedDisambiguationSpec.hs | 46 +++++++++++++++----- test/spec/fixtures/privileges.sql | 1 + test/spec/fixtures/schema.sql | 6 +++ 5 files changed, 48 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6145d7790..49d11e6e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez - #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available. - ``` + - #2155, Ignore `max-rows` on POST, PATCH, PUT and DELETE - @steve-chavez + - #2239, Fix misleading disambiguation error where the content of the `relationship` key looks like valid syntax - @laurenceisla ### Changed diff --git a/src/PostgREST/Error.hs b/src/PostgREST/Error.hs index bf676eb715..721ce0afcb 100644 --- a/src/PostgREST/Error.hs +++ b/src/PostgREST/Error.hs @@ -126,23 +126,22 @@ instance JSON.ToJSON ApiRequestError where compressedRel :: Relationship -> JSON.Value compressedRel Relationship{..} = let - fmtTbl Table{..} = tableSchema <> "." <> tableName - fmtEls els = "[" <> T.intercalate ", " els <> "]" + fmtEls els = "(" <> T.intercalate ", " els <> ")" in JSON.object $ ("embedding" .= (tableName relTable <> " with " <> tableName relForeignTable :: Text)) : case relCardinality of M2M Junction{..} -> [ "cardinality" .= ("many-to-many" :: Text) - , "relationship" .= (fmtTbl junTable <> fmtEls [junConstraint1] <> fmtEls [junConstraint2]) + , "relationship" .= (tableName junTable <> " using " <> junConstraint1 <> fmtEls (colName <$> junColumns1) <> " and " <> junConstraint2 <> fmtEls (colName <$> junColumns2)) ] M2O cons -> [ "cardinality" .= ("many-to-one" :: Text) - , "relationship" .= (cons <> fmtEls (colName <$> relColumns) <> fmtEls (colName <$> relForeignColumns)) + , "relationship" .= (cons <> " using " <> tableName relTable <> fmtEls (colName <$> relColumns) <> " and " <> tableName relForeignTable <> fmtEls (colName <$> relForeignColumns)) ] O2M cons -> [ "cardinality" .= ("one-to-many" :: Text) - , "relationship" .= (cons <> fmtEls (colName <$> relColumns) <> fmtEls (colName <$> relForeignColumns)) + , "relationship" .= (cons <> " using " <> tableName relTable <> fmtEls (colName <$> relColumns) <> " and " <> tableName relForeignTable <> fmtEls (colName <$> relForeignColumns)) ] relHint :: [Relationship] -> Text diff --git a/test/spec/Feature/EmbedDisambiguationSpec.hs b/test/spec/Feature/EmbedDisambiguationSpec.hs index 44bfa608f0..28ada4bad7 100644 --- a/test/spec/Feature/EmbedDisambiguationSpec.hs +++ b/test/spec/Feature/EmbedDisambiguationSpec.hs @@ -20,12 +20,12 @@ spec = "details": [ { "cardinality": "many-to-one", - "relationship": "message_sender_fkey[sender][id]", + "relationship": "message_sender_fkey using message(sender) and person(id)", "embedding": "message with person" }, { "cardinality": "many-to-one", - "relationship": "message_sender_fkey[sender][id]", + "relationship": "message_sender_fkey using message(sender) and person_detail(id)", "embedding": "message with person_detail" } ], @@ -37,6 +37,30 @@ spec = , matchHeaders = [matchContentTypeJson] } + it "errs when there's a table and view that point to the same fk (composite pk)" $ + get "/activities?select=fst_shift(*)" `shouldRespondWith` + [json| + { + "details": [ + { + "cardinality": "one-to-many", + "relationship": "fst_shift using activities(id, schedule_id) and unit_workdays(fst_shift_activity_id, fst_shift_schedule_id)", + "embedding": "activities with unit_workdays" + }, + { + "cardinality": "one-to-many", + "relationship": "fst_shift using activities(id, schedule_id) and unit_workdays_fst_shift(fst_shift_activity_id, fst_shift_schedule_id)", + "embedding": "activities with unit_workdays_fst_shift" + } + ], + "hint": "Try changing 'fst_shift' to one of the following: 'unit_workdays!fst_shift', 'unit_workdays_fst_shift!fst_shift'. Find the desired relationship in the 'details' key.", + "message": "Could not embed because more than one relationship was found for 'activities' and 'fst_shift'" + } + |] + { matchStatus = 300 + , matchHeaders = [matchContentTypeJson] + } + it "errs when there are o2m and m2m cardinalities to the target table" $ get "/sites?select=*,big_projects(*)" `shouldRespondWith` [json| @@ -44,17 +68,17 @@ spec = "details": [ { "cardinality": "many-to-one", - "relationship": "main_project[main_project_id][big_project_id]", + "relationship": "main_project using sites(main_project_id) and big_projects(big_project_id)", "embedding": "sites with big_projects" }, { "cardinality": "many-to-many", - "relationship": "test.jobs[jobs_site_id_fkey][jobs_big_project_id_fkey]", + "relationship": "jobs using jobs_site_id_fkey(site_id) and jobs_big_project_id_fkey(big_project_id)", "embedding": "sites with big_projects" }, { "cardinality": "many-to-many", - "relationship": "test.main_jobs[jobs_site_id_fkey][jobs_big_project_id_fkey]", + "relationship": "main_jobs using jobs_site_id_fkey(site_id) and jobs_big_project_id_fkey(big_project_id)", "embedding": "sites with big_projects" } ], @@ -73,12 +97,12 @@ spec = "details": [ { "cardinality": "many-to-one", - "relationship": "agents_department_id_fkey[department_id][id]", + "relationship": "agents_department_id_fkey using agents(department_id) and departments(id)", "embedding": "agents with departments" }, { "cardinality": "one-to-many", - "relationship": "departments_head_id_fkey[id][head_id]", + "relationship": "departments_head_id_fkey using agents(id) and departments(head_id)", "embedding": "agents with departments" } ], @@ -100,22 +124,22 @@ spec = "details": [ { "cardinality": "many-to-many", - "relationship": "test.whatev_jobs[whatev_jobs_site_id_1_fkey][whatev_jobs_project_id_1_fkey]", + "relationship": "whatev_jobs using whatev_jobs_site_id_1_fkey(site_id_1) and whatev_jobs_project_id_1_fkey(project_id_1)", "embedding": "whatev_sites with whatev_projects" }, { "cardinality": "many-to-many", - "relationship": "test.whatev_jobs[whatev_jobs_site_id_1_fkey][whatev_jobs_project_id_2_fkey]", + "relationship": "whatev_jobs using whatev_jobs_site_id_1_fkey(site_id_1) and whatev_jobs_project_id_2_fkey(project_id_2)", "embedding": "whatev_sites with whatev_projects" }, { "cardinality": "many-to-many", - "relationship": "test.whatev_jobs[whatev_jobs_site_id_2_fkey][whatev_jobs_project_id_1_fkey]", + "relationship": "whatev_jobs using whatev_jobs_site_id_2_fkey(site_id_2) and whatev_jobs_project_id_1_fkey(project_id_1)", "embedding": "whatev_sites with whatev_projects" }, { "cardinality": "many-to-many", - "relationship": "test.whatev_jobs[whatev_jobs_site_id_2_fkey][whatev_jobs_project_id_2_fkey]", + "relationship": "whatev_jobs using whatev_jobs_site_id_2_fkey(site_id_2) and whatev_jobs_project_id_2_fkey(project_id_2)", "embedding": "whatev_sites with whatev_projects" } ], diff --git a/test/spec/fixtures/privileges.sql b/test/spec/fixtures/privileges.sql index b6899e0122..78aa30405e 100644 --- a/test/spec/fixtures/privileges.sql +++ b/test/spec/fixtures/privileges.sql @@ -134,6 +134,7 @@ GRANT ALL ON TABLE , schedules , activities , unit_workdays + , unit_workdays_fst_shift , stuff , loc_test , v1.parents diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index 43f0ed8512..0d3fd4c95c 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2013,6 +2013,12 @@ add constraint fst_shift foreign key (fst_shift_activity_id, fst_shift add constraint snd_shift foreign key (snd_shift_activity_id, snd_shift_schedule_id) references activities (id, schedule_id); +create view unit_workdays_fst_shift as +select unit_id, day, fst_shift_activity_id, fst_shift_schedule_id +from unit_workdays +where fst_shift_activity_id is not null + and fst_shift_schedule_id is not null; + -- for a pre-request function create or replace function custom_headers() returns void as $$ declare From 8aa96b973131ffaf7cc7fd4cf6ea5ba6d1282777 Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Mon, 18 Apr 2022 10:40:32 -0500 Subject: [PATCH 54/62] Add CI for ARM architectures (#2127) --- .github/scripts/arm/build.sh | 57 +++++++++++ .github/scripts/arm/docker-env/Dockerfile | 73 ++++++++++++++ .github/scripts/arm/docker-publish.sh | 52 ++++++++++ .github/workflows/ci.yaml | 113 +++++++++++++++++++++- 4 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/arm/build.sh create mode 100644 .github/scripts/arm/docker-env/Dockerfile create mode 100644 .github/scripts/arm/docker-publish.sh diff --git a/.github/scripts/arm/build.sh b/.github/scripts/arm/build.sh new file mode 100644 index 0000000000..1784e830f5 --- /dev/null +++ b/.github/scripts/arm/build.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# This script builds PostgREST in a remote ARM server. It uses Docker to +# build for multiple platforms (aarch64 and armv7 on ubuntu). +# The Dockerfile is located in ./docker-env + +[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; } +[ -z "$2" ] && { echo "Missing 2nd argument: Docker repo"; exit 1; } +[ -z "$3" ] && { echo "Missing 3rd argument: Docker username"; exit 1; } +[ -z "$4" ] && { echo "Missing 4th argument: Docker password"; exit 1; } +[ -z "$5" ] && { echo "Missing 5th argument: Build environment directory name"; exit 1; } + +PGRST_GITHUB_COMMIT="$1" +DOCKER_REPO="$2" +DOCKER_USER="$3" +DOCKER_PASS="$4" +SCRIPT_PATH="$5" + +DOCKER_BUILD_PATH="$SCRIPT_PATH/docker-env" + +clean_env() +{ + sudo docker logout +} + +# Login to Docker +sudo docker logout +{ echo $DOCKER_PASS | sudo docker login -u $DOCKER_USER --password-stdin; } || { echo "Couldn't login to docker"; exit 1; } + +trap clean_env sigint sigterm exit + +# Move to the docker build environment +cd ~/$DOCKER_BUILD_PATH + +# Build ARM versions +sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --platform linux/arm/v7,linux/arm64 \ + --cache-from $DOCKER_REPO/postgrest-build-arm \ + --target=postgrest-build \ + -t $DOCKER_REPO/postgrest-build-arm \ + --push . + +sudo docker logout + +# Generate and copy binaries to the local filesystem +sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \ + --cache-from $DOCKER_REPO/postgrest-build-arm \ + --platform linux/arm/v7,linux/arm64 \ + --target=postgrest-bin \ + -o result . + +# Compress binaries +sudo chown -R ubuntu:ubuntu ~/$DOCKER_BUILD_PATH/result +mv ~/$DOCKER_BUILD_PATH/result ~/$SCRIPT_PATH/result +cd ~/$SCRIPT_PATH +tar -cJf result.tar.xz result diff --git a/.github/scripts/arm/docker-env/Dockerfile b/.github/scripts/arm/docker-env/Dockerfile new file mode 100644 index 0000000000..1f0e7ab941 --- /dev/null +++ b/.github/scripts/arm/docker-env/Dockerfile @@ -0,0 +1,73 @@ +# Build PostgREST for ARM architectures + +FROM ubuntu:focal as postgrest-build + +RUN apt-get update -y \ + && apt-get upgrade -y \ + && apt-get install -y git build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 llvm libnuma-dev zlib1g-dev libpq-dev jq gcc \ + && apt-get clean + +# Install ghcup +ENV BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +RUN bash -c "curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh" + +# Add ghcup to PATH +ENV PATH=${PATH}:/root/.local/bin +ENV PATH=${PATH}:/root/.ghcup/bin + +# Install cabal +RUN bash -c "ghcup upgrade" +RUN bash -c "ghcup install cabal 3.4.0.0" +RUN bash -c "ghcup set cabal 3.4.0.0" + +# Install GHC +RUN bash -c "ghcup install ghc 8.10.7" +RUN bash -c "ghcup set ghc 8.10.7" + +# Update Path to include Cabal and GHC exports +RUN bash -c "echo PATH="$HOME/.local/bin:$PATH" >> $HOME/.bashrc" +RUN bash -c "echo export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" >> $HOME/.bashrc" +RUN bash -c "source $HOME/.bashrc" + +# Clone the repository +RUN git clone https://github.com/PostgREST/postgrest.git /postgrest +WORKDIR /postgrest +RUN cabal v2-update && cabal v2-build + +# Arguments are declared here to save the above installation in cache +ARG PGRST_GITHUB_COMMIT + +RUN git pull origin main \ + && git checkout $PGRST_GITHUB_COMMIT + +# Build PostgREST +RUN mkdir -p /build-export +RUN cabal v2-update && cabal v2-build +RUN PGRST_BIN=$(cabal exec which postgrest | tail -1) \ + && mv $PGRST_BIN /build-export + + + +# Simple image to generate the PostgREST binaries + +FROM scratch as postgrest-bin + +COPY --from=postgrest-build /build-export / + + + +# PostgREST docker hub image + +FROM ubuntu:focal AS postgrest + +RUN apt-get update -y +RUN apt install libpq-dev zlib1g-dev jq gcc libnuma-dev -y +RUN apt-get clean + +COPY --from=postgrest-bin postgrest /usr/bin/postgrest + +EXPOSE 3000 + +USER 1000 + +CMD postgrest diff --git a/.github/scripts/arm/docker-publish.sh b/.github/scripts/arm/docker-publish.sh new file mode 100644 index 0000000000..faba6dd119 --- /dev/null +++ b/.github/scripts/arm/docker-publish.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# This script publishes the Docker ARM images to Docker Hub. + +[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; } +[ -z "$2" ] && { echo "Missing 2nd argument: Docker repo"; exit 1; } +[ -z "$3" ] && { echo "Missing 3rd argument: Docker username"; exit 1; } +[ -z "$4" ] && { echo "Missing 4th argument: Docker password"; exit 1; } +[ -z "$5" ] && { echo "Missing 5th argument: Build environment directory name"; exit 1; } +[ -z "$6" ] && { echo "Missing 6th argument: PostgREST version"; exit 1; } + +PGRST_GITHUB_COMMIT="$1" +DOCKER_REPO="$2" +DOCKER_USER="$3" +DOCKER_PASS="$4" +SCRIPT_PATH="$5" +PGRST_VERSION="v$6" +IS_PRERELEASE="$7" + +DOCKER_BUILD_PATH="$SCRIPT_PATH/docker-env" + +clean_env() +{ + sudo docker logout +} + +# Login to Docker +sudo docker logout +{ echo $DOCKER_PASS | sudo docker login -u $DOCKER_USER --password-stdin; } || { echo "Couldn't login to docker"; exit 1; } + +trap clean_env sigint sigterm exit + +# Move to the docker build environment +cd ~/$DOCKER_BUILD_PATH + +# Push final images to Docker hub +# NOTE: This command publishes a separate ARM image because the builds cannot +# be added to the manifest if they are not in the registry beforehand. +# This image must be manually deleted from Docker Hub at the end of the process. +sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \ + --platform linux/arm/v7,linux/arm64 \ + --cache-from $DOCKER_REPO/postgrest-build-arm \ + -t $DOCKER_REPO/postgrest:$PGRST_VERSION-arm \ + --push . + +# Add the arm images to the manifest +# NOTE: This assumes that there already is a `postgrest:` image +# for the amd64 architecture pushed to Docker Hub +sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:$PGRST_VERSION $DOCKER_REPO/postgrest:$PGRST_VERSION-arm +[ -z $IS_PRERELEASE ] && sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:latest $DOCKER_REPO/postgrest:$PGRST_VERSION-arm + +sudo docker logout diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2406054e69..c54e7f653d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -204,7 +204,6 @@ jobs: result/postgrest.exe if-no-files-found: error - Get-FreeBSD-CirrusCI: name: Get FreeBSD build from CirrusCI runs-on: ubuntu-latest @@ -222,6 +221,70 @@ jobs: path: postgrest if-no-files-found: error + Build-Cabal-Arm: + name: Build armv7/aarch64 (Cabal) + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + outputs: + remotepath: ${{ steps.Remote-Dir.outputs.remotepath }} + env: + GITHUB_COMMIT: ${{ github.sha }} + DOCKER_REPO: "postgrest" + DOCKER_USER: "stevechavez" + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} + steps: + - uses: actions/checkout@v2.4.0 + - id: Remote-Dir + name: Unique directory name for the remote build + run: echo "::set-output name=remotepath::postgrest-build-$(uuidgen)" + - name: Copy script files to the remote server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.SSH_ARM_HOST }} + username: ubuntu + key: ${{ secrets.SSH_ARM_PRIVATE_KEY }} + fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }} + source: ".github/scripts/arm/*" + target: ${{ steps.Remote-Dir.outputs.remotepath }} + strip_components: 3 + - name: Build ARM + uses: appleboy/ssh-action@master + env: + REMOTE_DIR: ${{ steps.Remote-Dir.outputs.remotepath }} + with: + host: ${{ secrets.SSH_ARM_HOST }} + username: ubuntu + key: ${{ secrets.SSH_ARM_PRIVATE_KEY }} + fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }} + command_timeout: 120m + script_stop: true + envs: GITHUB_COMMIT,DOCKER_REPO,DOCKER_USER,DOCKER_PASS,REMOTE_DIR + script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" + - name: Download binaries from remote server + uses: nicklasfrahm/scp-action@main + with: + direction: download + host: ${{ secrets.SSH_ARM_HOST }} + username: ubuntu + key: ${{ secrets.SSH_ARM_PRIVATE_KEY }} + fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }} + source: "${{ steps.Remote-Dir.outputs.remotepath }}/result.tar.xz" + target: "result.tar.xz" + - name: Extract downloaded binaries + run: tar -xvf result.tar.xz && rm result.tar.xz + - name: Save aarch64 executable as artifact + uses: actions/upload-artifact@v2.3.1 + with: + name: postgrest-ubuntu-aarch64 + path: result/linux_arm64/postgrest + if-no-files-found: error + - name: Save armv7 executable as artifact + uses: actions/upload-artifact@v2.3.1 + with: + name: postgrest-ubuntu-armv7 + path: result/linux_arm_v7/postgrest + if-no-files-found: error + Prepare-Release: name: Prepare release @@ -235,6 +298,7 @@ jobs: - Build-Static-Nix - Build-Stack #- Get-FreeBSD-CirrusCI + - Build-Cabal-Arm outputs: version: ${{ steps.Identify-Version.outputs.version }} isprerelease: ${{ steps.Identify-Version.outputs.isprerelease }} @@ -318,6 +382,12 @@ jobs: #tar cJvf "release-bundle/postgrest-v$VERSION-freebsd-x64.tar.xz" \ # -C artifacts/postgrest-freebsd-x64 postgrest + tar cJvf "release-bundle/postgrest-v$VERSION-ubuntu-aarch64.tar.xz" \ + -C artifacts/postgrest-ubuntu-aarch64 postgrest + + tar cJvf "release-bundle/postgrest-v$VERSION-ubuntu-armv7.tar.xz" \ + -C artifacts/postgrest-ubuntu-armv7 postgrest + zip "release-bundle/postgrest-v$VERSION-windows-x64.zip" \ artifacts/postgrest-windows-x64/postgrest.exe @@ -345,10 +415,14 @@ jobs: Release-Docker: name: Release on Docker Hub runs-on: ubuntu-latest - needs: Prepare-Release + needs: + - Build-Cabal-Arm + - Prepare-Release env: + GITHUB_COMMIT: ${{ github.sha }} DOCKER_REPO: postgrest DOCKER_USER: stevechavez + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} VERSION: ${{ needs.Prepare-Release.outputs.version }} ISPRERELEASE: ${{ needs.Prepare-Release.outputs.isprerelease }} steps: @@ -363,7 +437,7 @@ jobs: name: postgrest-docker-x64 - name: Publish images on Docker Hub run: | - docker login -u "$DOCKER_USER" -p "${{ secrets.DOCKER_PASS }}" + docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" docker load -i postgrest-docker.tar.gz docker tag postgrest:latest "$DOCKER_REPO/postgrest:v$VERSION" @@ -377,6 +451,18 @@ jobs: else echo "Skipping pushing to 'latest' tag for v$VERSION pre-release..." fi + - name: Publish images for ARM builds on Docker Hub + uses: appleboy/ssh-action@master + env: + REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }} + with: + host: ${{ secrets.SSH_ARM_HOST }} + username: ubuntu + key: ${{ secrets.SSH_ARM_PRIVATE_KEY }} + fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }} + script_stop: true + envs: GITHUB_COMMIT,DOCKER_REPO,DOCKER_USER,DOCKER_PASS,REMOTE_DIR,VERSION,ISPRERELEASE + script: bash ~/$REMOTE_DIR/docker-publish.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" "$VERSION" "$ISPRERELEASE" # TODO: Enable dockerhub description update again, once a solution for the permission problem is found: # https://github.com/docker/hub-feedback/issues/1927 # - name: Update descriptions on Docker Hub @@ -389,3 +475,24 @@ jobs: # else # echo "Skipping updating description for pre-release..." # fi + + Clean-Arm-Server: + name: Remove copied files from server + needs: + - Build-Cabal-Arm + - Release-Docker + if: ${{ always() && github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + env: + REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }} + steps: + - uses: actions/checkout@v2.4.0 + - name: Remove uploaded files from server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SSH_ARM_HOST }} + username: ubuntu + key: ${{ secrets.SSH_ARM_PRIVATE_KEY }} + fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }} + envs: REMOTE_DIR + script: rm -rf $REMOTE_DIR From 5174e6103a4a550d9ce2918e346e4cafa7f3401d Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Sat, 23 Apr 2022 13:30:07 -0500 Subject: [PATCH 55/62] ci: remove armv7 architectures for ARM builds Now only builds aarch64 on the remote server without Docker --- .github/scripts/arm/build.sh | 98 ++++++++++++----------- .github/scripts/arm/docker-env/Dockerfile | 67 ++-------------- .github/scripts/arm/docker-publish.sh | 8 +- .github/workflows/ci.yaml | 20 +---- 4 files changed, 64 insertions(+), 129 deletions(-) diff --git a/.github/scripts/arm/build.sh b/.github/scripts/arm/build.sh index 1784e830f5..a22da7a3be 100644 --- a/.github/scripts/arm/build.sh +++ b/.github/scripts/arm/build.sh @@ -1,57 +1,63 @@ #!/bin/bash -# This script builds PostgREST in a remote ARM server. It uses Docker to -# build for multiple platforms (aarch64 and armv7 on ubuntu). -# The Dockerfile is located in ./docker-env +# This script builds PostgREST in a remote ARM server [ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; } -[ -z "$2" ] && { echo "Missing 2nd argument: Docker repo"; exit 1; } -[ -z "$3" ] && { echo "Missing 3rd argument: Docker username"; exit 1; } -[ -z "$4" ] && { echo "Missing 4th argument: Docker password"; exit 1; } -[ -z "$5" ] && { echo "Missing 5th argument: Build environment directory name"; exit 1; } +[ -z "$2" ] && { echo "Missing 2nd argument: Build environment directory name"; exit 1; } PGRST_GITHUB_COMMIT="$1" -DOCKER_REPO="$2" -DOCKER_USER="$3" -DOCKER_PASS="$4" -SCRIPT_PATH="$5" +SCRIPT_DIR="$2" -DOCKER_BUILD_PATH="$SCRIPT_PATH/docker-env" +DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env" -clean_env() -{ - sudo docker logout +install_packages() { + sudo apt-get update -y + sudo apt-get upgrade -y + sudo apt-get install -y git build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 llvm libnuma-dev zlib1g-dev libpq-dev jq gcc + sudo apt-get clean } -# Login to Docker -sudo docker logout -{ echo $DOCKER_PASS | sudo docker login -u $DOCKER_USER --password-stdin; } || { echo "Couldn't login to docker"; exit 1; } - -trap clean_env sigint sigterm exit - -# Move to the docker build environment -cd ~/$DOCKER_BUILD_PATH - -# Build ARM versions -sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --platform linux/arm/v7,linux/arm64 \ - --cache-from $DOCKER_REPO/postgrest-build-arm \ - --target=postgrest-build \ - -t $DOCKER_REPO/postgrest-build-arm \ - --push . - -sudo docker logout - -# Generate and copy binaries to the local filesystem -sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \ - --cache-from $DOCKER_REPO/postgrest-build-arm \ - --platform linux/arm/v7,linux/arm64 \ - --target=postgrest-bin \ - -o result . - -# Compress binaries -sudo chown -R ubuntu:ubuntu ~/$DOCKER_BUILD_PATH/result -mv ~/$DOCKER_BUILD_PATH/result ~/$SCRIPT_PATH/result -cd ~/$SCRIPT_PATH +install_ghcup() { + export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 + export BOOTSTRAP_HASKELL_MINIMAL=1 + curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh + source ~/.ghcup/env +} + +install_cabal() { + ghcup upgrade + ghcup install cabal 3.6.0.0 + ghcup set cabal 3.6.0.0 +} + +install_ghc() { + ghcup install ghc 8.10.7 + ghcup set ghc 8.10.7 +} + +install_packages + +# Add ghcup to the PATH for this session +[ -f ~/.ghcup/env ] && source ~/.ghcup/env + +ghcup --version || install_ghcup +cabal --version || install_cabal +ghc --version || install_ghc + +cd ~/$SCRIPT_DIR + +# Clone the repository and build the project +git clone https://github.com/PostgREST/postgrest.git +cd postgrest +git checkout $PGRST_GITHUB_COMMIT +cabal v2-update && cabal v2-build + +# Copy the built binary to the Dockerfile directory +PGRST_BIN=$(cabal exec which postgrest | tail -1) +cp $PGRST_BIN ~/$DOCKER_BUILD_DIR + +# Move and compress the built binary +mkdir -p ~/$SCRIPT_DIR/result +mv $PGRST_BIN ~/$SCRIPT_DIR/result +cd ~/$SCRIPT_DIR tar -cJf result.tar.xz result diff --git a/.github/scripts/arm/docker-env/Dockerfile b/.github/scripts/arm/docker-env/Dockerfile index 1f0e7ab941..93ec2d4631 100644 --- a/.github/scripts/arm/docker-env/Dockerfile +++ b/.github/scripts/arm/docker-env/Dockerfile @@ -1,70 +1,13 @@ -# Build PostgREST for ARM architectures - -FROM ubuntu:focal as postgrest-build - -RUN apt-get update -y \ - && apt-get upgrade -y \ - && apt-get install -y git build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 llvm libnuma-dev zlib1g-dev libpq-dev jq gcc \ - && apt-get clean - -# Install ghcup -ENV BOOTSTRAP_HASKELL_NONINTERACTIVE=1 -RUN bash -c "curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh" - -# Add ghcup to PATH -ENV PATH=${PATH}:/root/.local/bin -ENV PATH=${PATH}:/root/.ghcup/bin - -# Install cabal -RUN bash -c "ghcup upgrade" -RUN bash -c "ghcup install cabal 3.4.0.0" -RUN bash -c "ghcup set cabal 3.4.0.0" - -# Install GHC -RUN bash -c "ghcup install ghc 8.10.7" -RUN bash -c "ghcup set ghc 8.10.7" - -# Update Path to include Cabal and GHC exports -RUN bash -c "echo PATH="$HOME/.local/bin:$PATH" >> $HOME/.bashrc" -RUN bash -c "echo export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" >> $HOME/.bashrc" -RUN bash -c "source $HOME/.bashrc" - -# Clone the repository -RUN git clone https://github.com/PostgREST/postgrest.git /postgrest -WORKDIR /postgrest -RUN cabal v2-update && cabal v2-build - -# Arguments are declared here to save the above installation in cache -ARG PGRST_GITHUB_COMMIT - -RUN git pull origin main \ - && git checkout $PGRST_GITHUB_COMMIT - -# Build PostgREST -RUN mkdir -p /build-export -RUN cabal v2-update && cabal v2-build -RUN PGRST_BIN=$(cabal exec which postgrest | tail -1) \ - && mv $PGRST_BIN /build-export - - - -# Simple image to generate the PostgREST binaries - -FROM scratch as postgrest-bin - -COPY --from=postgrest-build /build-export / - - - # PostgREST docker hub image FROM ubuntu:focal AS postgrest -RUN apt-get update -y -RUN apt install libpq-dev zlib1g-dev jq gcc libnuma-dev -y -RUN apt-get clean +RUN apt-get update -y \ + && apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* -COPY --from=postgrest-bin postgrest /usr/bin/postgrest +COPY postgrest /usr/bin/postgrest EXPOSE 3000 diff --git a/.github/scripts/arm/docker-publish.sh b/.github/scripts/arm/docker-publish.sh index faba6dd119..d6ebeaf306 100644 --- a/.github/scripts/arm/docker-publish.sh +++ b/.github/scripts/arm/docker-publish.sh @@ -13,11 +13,11 @@ PGRST_GITHUB_COMMIT="$1" DOCKER_REPO="$2" DOCKER_USER="$3" DOCKER_PASS="$4" -SCRIPT_PATH="$5" +SCRIPT_DIR="$5" PGRST_VERSION="v$6" IS_PRERELEASE="$7" -DOCKER_BUILD_PATH="$SCRIPT_PATH/docker-env" +DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env" clean_env() { @@ -31,15 +31,13 @@ sudo docker logout trap clean_env sigint sigterm exit # Move to the docker build environment -cd ~/$DOCKER_BUILD_PATH +cd ~/$DOCKER_BUILD_DIR # Push final images to Docker hub # NOTE: This command publishes a separate ARM image because the builds cannot # be added to the manifest if they are not in the registry beforehand. # This image must be manually deleted from Docker Hub at the end of the process. sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \ - --platform linux/arm/v7,linux/arm64 \ - --cache-from $DOCKER_REPO/postgrest-build-arm \ -t $DOCKER_REPO/postgrest:$PGRST_VERSION-arm \ --push . diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c54e7f653d..b597bda048 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -222,16 +222,13 @@ jobs: if-no-files-found: error Build-Cabal-Arm: - name: Build armv7/aarch64 (Cabal) + name: Build aarch64 (Cabal) if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest outputs: remotepath: ${{ steps.Remote-Dir.outputs.remotepath }} env: GITHUB_COMMIT: ${{ github.sha }} - DOCKER_REPO: "postgrest" - DOCKER_USER: "stevechavez" - DOCKER_PASS: ${{ secrets.DOCKER_PASS }} steps: - uses: actions/checkout@v2.4.0 - id: Remote-Dir @@ -258,8 +255,8 @@ jobs: fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }} command_timeout: 120m script_stop: true - envs: GITHUB_COMMIT,DOCKER_REPO,DOCKER_USER,DOCKER_PASS,REMOTE_DIR - script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" + envs: GITHUB_COMMIT,REMOTE_DIR + script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$REMOTE_DIR" - name: Download binaries from remote server uses: nicklasfrahm/scp-action@main with: @@ -276,13 +273,7 @@ jobs: uses: actions/upload-artifact@v2.3.1 with: name: postgrest-ubuntu-aarch64 - path: result/linux_arm64/postgrest - if-no-files-found: error - - name: Save armv7 executable as artifact - uses: actions/upload-artifact@v2.3.1 - with: - name: postgrest-ubuntu-armv7 - path: result/linux_arm_v7/postgrest + path: result/postgrest if-no-files-found: error @@ -385,9 +376,6 @@ jobs: tar cJvf "release-bundle/postgrest-v$VERSION-ubuntu-aarch64.tar.xz" \ -C artifacts/postgrest-ubuntu-aarch64 postgrest - tar cJvf "release-bundle/postgrest-v$VERSION-ubuntu-armv7.tar.xz" \ - -C artifacts/postgrest-ubuntu-armv7 postgrest - zip "release-bundle/postgrest-v$VERSION-windows-x64.zip" \ artifacts/postgrest-windows-x64/postgrest.exe From f02e33fabb910fb6b20c694d36ab08a1ad8d9d5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 04:16:24 +0000 Subject: [PATCH 56/62] build(deps): bump codecov/codecov-action from 3.0.0 to 3.1.0 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b597bda048..9f6dd2438e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,7 @@ jobs: - name: Run coverage (IO tests and Spec tests against PostgreSQL 14) run: postgrest-coverage - name: Upload coverage to codecov - uses: codecov/codecov-action@v3.0.0 + uses: codecov/codecov-action@v3.1.0 with: files: ./coverage/codecov.json From 022db48c3af43de34881a2b94b52e6168c75475d Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Thu, 5 May 2022 00:06:00 -0500 Subject: [PATCH 57/62] ci: Fix Windows build using Stack Added PostgreSQL binaries to the PATH and removed the use of msys2-keyring --- .github/workflows/ci.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9f6dd2438e..eacb891c28 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -167,10 +167,7 @@ jobs: ~\AppData\Roaming\stack ~\AppData\Local\Programs\stack .stack-work - deps: | - stack exec -- pacman -Syu --noconfirm - stack exec -- pacman -S msys2-keyring --noconfirm - stack exec -- pacman -S mingw64/mingw-w64-x86_64-postgresql --noconfirm + deps: Add-Content $env:GITHUB_PATH $env:PGBIN # We'd need to make test/with_tmp_db run on Windows first # test: true artifact: postgrest-windows-x64 From 5a000da158aeb43255903010baebbc1443b4c477 Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Mon, 16 May 2022 17:52:49 -0500 Subject: [PATCH 58/62] ci: Run ARM builds on releases --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index eacb891c28..3949245f7a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -220,7 +220,7 @@ jobs: Build-Cabal-Arm: name: Build aarch64 (Cabal) - if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest outputs: remotepath: ${{ steps.Remote-Dir.outputs.remotepath }} @@ -466,7 +466,7 @@ jobs: needs: - Build-Cabal-Arm - Release-Docker - if: ${{ always() && github.ref == 'refs/heads/main' }} + if: ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }} runs-on: ubuntu-latest env: REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }} From 132a64235f6c18e2cc2544ccc2b57b23ad46f7fc Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Mon, 30 May 2022 22:38:05 -0500 Subject: [PATCH 59/62] ci: run on branches starting with "rel-" --- .github/workflows/ci.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3949245f7a..b8c27f35f4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,11 +4,13 @@ on: push: branches: - main + - rel-* tags: - v* pull_request: branches: - main + - rel-* jobs: Lint-Style: @@ -220,7 +222,7 @@ jobs: Build-Cabal-Arm: name: Build aarch64 (Cabal) - if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/rel-') }} runs-on: ubuntu-latest outputs: remotepath: ${{ steps.Remote-Dir.outputs.remotepath }} @@ -466,7 +468,7 @@ jobs: needs: - Build-Cabal-Arm - Release-Docker - if: ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }} + if: ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/rel-')) }} runs-on: ubuntu-latest env: REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }} From a3d4cb15186f184333a27863c2c3aeb2bd9ae2c3 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 30 May 2022 23:55:25 -0500 Subject: [PATCH 60/62] fix: disable parallel GC for perf on high-core CPU --- CHANGELOG.md | 8 ++++++++ postgrest.cabal | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d11e6e19..443c72b360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - ``` - #2155, Ignore `max-rows` on POST, PATCH, PUT and DELETE - @steve-chavez - #2239, Fix misleading disambiguation error where the content of the `relationship` key looks like valid syntax - @laurenceisla + - #2254, Fix inferring a foreign key column as a primary key column on views - @steve-chavez + - #2070, Restrict generated many-to-many relationships - @steve-chavez + + Only adds many-to-many relationships when: a table has FKs to two other tables and these FK columns are part of the table's PK columns. + - #2278, Allow casting to types with underscores and numbers(e.g. `select=oid_array::_int4`) - @steve-chavez + - #2277, #2238, #1643, Prevent views from breaking one-to-many/many-to-one embeds when using column or FK as target - @steve-chavez + + When using a column or FK as target for embedding(`/tbl?select=*,col-or-fk(*)`), only tables are now detected and views are not. + + You can still use a column or an inferred FK on a view to embed a table(`/view?select=*,col-or-fk(*)`) + - #2294, Disable parallel GC for better performance on higher core CPUs - @steve-chavez ### Changed diff --git a/postgrest.cabal b/postgrest.cabal index 5d0653556d..1877cd1f54 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -144,7 +144,7 @@ executable postgrest , containers >= 0.5.7 && < 0.7 , postgrest , protolude >= 0.3 && < 0.4 - ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2" + ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2 -qg" -O2 -Werror -Wall -fwarn-identities -fno-spec-constr -optP-Wno-nonportable-include-path From cc7364442b89f52466609cb7987a25c4c0aabf8d Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 30 May 2022 23:59:05 -0500 Subject: [PATCH 61/62] fix: using CPU while idle --- CHANGELOG.md | 1 + postgrest.cabal | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 443c72b360..47fc21307e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). + When using a column or FK as target for embedding(`/tbl?select=*,col-or-fk(*)`), only tables are now detected and views are not. + You can still use a column or an inferred FK on a view to embed a table(`/view?select=*,col-or-fk(*)`) - #2294, Disable parallel GC for better performance on higher core CPUs - @steve-chavez + - #1076, Fix using CPU while idle - @steve-chavez ### Changed diff --git a/postgrest.cabal b/postgrest.cabal index 1877cd1f54..06e65fd73f 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -144,7 +144,7 @@ executable postgrest , containers >= 0.5.7 && < 0.7 , postgrest , protolude >= 0.3 && < 0.4 - ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I2 -qg" + ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I0 -qg" -O2 -Werror -Wall -fwarn-identities -fno-spec-constr -optP-Wno-nonportable-include-path From 78eeb4137fa1ef754a3da59adaea53b46cfb893d Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Fri, 3 Jun 2022 19:55:55 -0500 Subject: [PATCH 62/62] bump version to 9.0.1 * Format CHANGELOG --- CHANGELOG.md | 32 ++++---------------------------- postgrest.cabal | 2 +- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47fc21307e..f10fcd0bf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,50 +3,26 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased - -### Added +## [9.0.1] - 2022-06-03 ### Fixed - - #2165, Fix json/jsonb columns should not have type in OpenAPI spec + - #2165, Fix json/jsonb columns should not have type in OpenAPI spec - @clrnd - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther - - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez - #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther - #1724, Fix wrong CORS header Authentication -> Authorization - @wolfgangwalther - - #2107, Clarify error for failed schema cache load. - @steve-chavez - + From `Database connection lost. Retrying the connection` to `Could not query the database for the schema cache. Retrying.` - #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther - - #1771, Fix silently ignoring filter on a non-existent embedded resource - @steve-chavez - #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther - #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther - - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther - - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther - - #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther - - #2152, Remove functions, which are uncallable because of unnamend arguments from schema cache and OpenAPI output. - @wolfgangwalther - - #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther - #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther - #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez - - #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available. - - ``` - - #2155, Ignore `max-rows` on POST, PATCH, PUT and DELETE - @steve-chavez + - #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. - @laurenceisla + + Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available. - #2239, Fix misleading disambiguation error where the content of the `relationship` key looks like valid syntax - @laurenceisla - - #2254, Fix inferring a foreign key column as a primary key column on views - @steve-chavez - - #2070, Restrict generated many-to-many relationships - @steve-chavez - + Only adds many-to-many relationships when: a table has FKs to two other tables and these FK columns are part of the table's PK columns. - - #2278, Allow casting to types with underscores and numbers(e.g. `select=oid_array::_int4`) - @steve-chavez - - #2277, #2238, #1643, Prevent views from breaking one-to-many/many-to-one embeds when using column or FK as target - @steve-chavez - + When using a column or FK as target for embedding(`/tbl?select=*,col-or-fk(*)`), only tables are now detected and views are not. - + You can still use a column or an inferred FK on a view to embed a table(`/view?select=*,col-or-fk(*)`) - #2294, Disable parallel GC for better performance on higher core CPUs - @steve-chavez - #1076, Fix using CPU while idle - @steve-chavez -### Changed - - - #2001, Return 204 No Content without Content-Type for RPCs returning VOID - @wolfgangwalther - + Previously, those RPCs would return "null" as a body with Content-Type: application/json. - ## [9.0.0] - 2021-11-25 ### Added diff --git a/postgrest.cabal b/postgrest.cabal index 06e65fd73f..4c47ca2835 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -1,5 +1,5 @@ name: postgrest -version: 9.0.0 +version: 9.0.1 synopsis: REST API for any Postgres database description: Reads the schema of a PostgreSQL database and creates RESTful routes for tables, views, and functions, supporting all HTTP verbs that security