Compare commits

..
Author SHA1 Message Date
Taimoor Zaeem a46ac79ea8 nix: exclude protolude from style checks
Currently, our vendored protolude has many failing checks for
style, lint and hsie. Temporarily excluding it. We should
reinstate these checks later.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-30 20:33:32 +05:00
Taimoor Zaeem a2faa667f3 chore: build postgrest with vendored protolude
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-30 19:56:33 +05:00
Taimoor Zaeem 366502729d chore: remove unneeded files and directories from src/protolude
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-30 15:38:39 +05:00
Taimoor Zaeem 7c8f79d86f chore: add .git-blame-ignore-revs
Ignore blame for commit that moved protolude files under src/protolude.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-30 14:24:21 +05:00
Taimoor Zaeem 827121b72f chore: merge protolude as subdirectory src/protolude 2026-04-30 14:17:07 +05:00
Taimoor Zaeem d4949c633e chore: move protolude files to src/protolude
Later we merge the src/protolude directory with src/PostgREST.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-30 14:16:23 +05:00
Laurence IslaandWolfgang Walther 5f6f7dca44 fix: unexpected results when embedding the same table twice 2026-04-29 09:10:48 +00:00
Wolfgang Walther ea08a4d767 ci: test build of dev shell on darwin 2026-04-28 12:56:53 +00:00
Wolfgang Walther 822f8ef802 nix: remove unused tools from dev shell
I doubt that anyone would use cabal2nix or update-nix-fetchgit manually
- and at the same time everyone surely has git installed through their
OS anyway, otherwise they can't even get that far.
2026-04-28 12:56:53 +00:00
Wolfgang Walther c8439e437c ci: use nix-build-uncached to save useless downloads
There is no point to actually realize the derivations to disk, when they
are in cache. `nix-build-uncached` checks the cache and stops before
downloading dependencies if they are available. It will however build as
normal on a cache-miss. Should speed up CI when no Nix dependencies
change a fair bit.

We can only do this for the MacOS job, which only tests the builds. We
can not do this for other jobs which require the build results to be
present before uploading parts of them as artifacts.
2026-04-28 12:56:53 +00:00
Laurence Isla dc179665dc chore: fix incorrect username in changelog entry 2026-04-27 18:22:58 -05:00
Wolfgang Walther 3ec9b94c79 ci: avoid useless download of GHC during stack builds
Forgot to adjust when updating stackage.
2026-04-27 19:52:57 +00:00
renovate[bot]andWolfgang Walther 9b9521b75a chore(deps): update korthout/backport-action action to v4.5 2026-04-27 19:51:37 +00:00
Laurence Isla 41b86fffa5 fix: no longer retries the transaction on 40001 errors 2026-04-27 14:30:10 -05:00
Taimoor ZaeemandWolfgang Walther bf758698b3 fix: login with uppercase and mixed case role names
PostgREST failed when querying role settings where current
role name contained uppercase letters. This commit resolves
it by quoting the CURRENT_USER.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-27 09:19:26 +00:00
Wolfgang WaltherandMichal Kleczek a5cc457875 chore(deps): update hasql to 1.9.3.1
Michael and I arrived at mostly the same things independently. Took some
of his, some of mine.

Co-authored-by: Michal Kleczek <michal@kleczek.org>
2026-04-27 09:12:31 +00:00
Wolfgang Walther f80122e12b ci: trigger workflows when nixpkgs pin is updated
Otherwise we might break things...
2026-04-27 08:29:41 +00:00
Wolfgang Walther 2e5baaf296 chore(deps): update nixpkgs pin 2026-04-27 08:29:41 +00:00
Wolfgang Walther 0e0bc6a2c1 chore(deps): fix docs deps
These should have been updated by postgrest-nixpkgs-upgrade, when I
upgraded the Nixpkgs pin, but I did not follow my own readme, and just
ran nix flake update...
2026-04-27 08:29:41 +00:00
Taimoor ZaeemandSteve Chavez 38829191a7 test(load): reduce num of tables in errors loadtest
The table not found error only generates hint when the total number of
tables in a schema are less than 500.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-26 12:32:47 -05:00
Taimoor ZaeemandWolfgang Walther c9253ed056 test(load): update error loadtest endpoint to generate hint
This endpoint didn't generate error hint which is not desired
for loadtest.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-24 10:30:30 +00:00
Taimoor ZaeemandWolfgang Walther a1ab388ab6 test(doctests): remove redundant doctests in Error.hs
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-23 08:23:28 +00:00
renovate[bot]andWolfgang Walther de4ad62562 chore(deps): update korthout/backport-action action to v4.4.0 2026-04-21 09:05:36 +00:00
Wolfgang Walther e2a677e21d chore: try to re-enable codecov annotations
Not sure whether this works, but it likely can't hurt.
2026-04-20 23:24:49 +02:00
Michał KłeczekandSteve Chavez baebacf3db fix: shutdown should wait for in flight requests
Upgraded warp to 3.4.13 which fixed https://github.com/yesodweb/wai/issues/853
Changed interrupt handling so that instead of killing the main thread, listening sockets are closed which triggers warp graceful shutdown.
2026-04-20 16:19:13 -05:00
Wolfgang Walther 3b1373ec02 chore: update the nix upgrade docs
These have been outdated for a while, not only because we're now using
flake.nix, but also in various other places.

Resolves #4816
2026-04-20 19:29:26 +00:00
Wolfgang Walther 695678ad40 chore: add changelog for static aarch64 builds and remove left-overs
I guess I forgot to update a few places when doing this.
2026-04-20 19:29:26 +00:00
Wolfgang Walther 92ba6d5caa ci: fix actions/cache-on-main
My recent commit to fix the stack cache on macOS only changed the
restore keys, but no cache would ever be saved that way. I effectively
disabled all caching...
2026-04-19 23:03:07 +02:00
Wolfgang Walther 6b4022b0c1 ci: fix docker push for release job
Unfortunately, `docker manifest create` does not support image digests,
so we need to push a tag for each version of the image. Furthermore, the
docker CLI does not provide a way to delete that tag afterwards, so we
will end up with those temporary tags.
2026-04-19 22:20:46 +02:00
Wolfgang Walther 0cb354be7a ci: fix stack cache for macOS
We now have two stack jobs for macOS, but they use the same cache, which
is very inefficient. Adding the architecture to the cache key will give
each of these jobs a separate cache.
2026-04-19 21:48:06 +02:00
Wolfgang Walther 1c869bf3a6 ci: remove builds for GHC 9.6.7 and 9.8.4
Now that we can build with at least GHC 9.10 on all platforms, there is
no need to keep testing these older versions anymore.
2026-04-19 20:17:48 +02:00
Wolfgang Walther fee820bac2 chore: release static executable for aarch64-linux
Also makes the aarch64 variant of the docker image consist of a single
static executable, similar to the x86_64 variant.
2026-04-19 17:53:48 +00:00
Wolfgang WaltherandMichal Kleczek 1676314fc3 nix: build with GHC 9.12.3
Jumping past GHC 9.6, 9.8 and 9.10 immediately, because these versions
do not successfully build the static binary with TemplateHaskell and
iserv-proxy, yet.

Michal wrote the changes to hsie.

Co-authored-by: Michal Kleczek <michal@kleczek.org>
2026-04-19 17:53:48 +00:00
Wolfgang Walther 49a16fe505 ci: build with cabal and GHC 9.12.3 2026-04-19 17:50:23 +00:00
Wolfgang Walther 43447ed711 test: fix two doctests
Spotted them randomly while debugging something else.
2026-04-19 19:10:56 +02:00
Wolfgang Walther edbd2a3dca chore: remove renovate haskell updates
These don't really make sense, because it's non-trivial to construct a
build plan that *ensures* the dependency that renovate creates a PR for
is tested with the latest version. Cabal might just select a build plan,
that doesn't include the dependency, in which case we could just as well
have no upper bounds at all.

Since there is no good way to test these changes (I tried, a while
ago!), let's just disable these PRs. They are more annoying than
anything else.
2026-04-19 18:38:51 +02:00
Wolfgang Walther 03f7234360 ci: test against PostgreSQL 18 2026-04-19 13:16:55 +00:00
Wolfgang Walther d79f28fd75 chore: fix stack.yml's nix integration once more
We don't need an impure environment anymore, because we can't run the
test suite via stack anyway.
2026-04-19 13:16:55 +00:00
Wolfgang Walther 051b4799de chore(deps): update nixpkgs to latest nixpkgs-unstable
This allows building TemplateHaskell with GHC 9.6+ via iserv-proxy.
2026-04-19 13:16:55 +00:00
Wolfgang Walther 1b74b93466 remove: drop support for PostgreSQL 13
PostgreSQL 13 has been EOL since November 2025 and will not be available
in the next Nixpkgs bump anymore.
2026-04-19 13:16:55 +00:00
Wolfgang Walther 4ccb125e85 chore: remove outdated comment
We started actually depending on postgresql-libpq in
5356f4e973.
2026-04-19 13:52:19 +02:00
steve-chavezandWolfgang Walther b6c6bb27a8 ci: reinstate macos x86 releases
Closes https://github.com/PostgREST/postgrest/issues/4755
2026-04-19 11:26:06 +00:00
Wolfgang Walther 1f70e42d3b ci: build with cabal and GHC 9.10.3 2026-04-19 10:07:14 +00:00
Wolfgang Walther be7a42ff30 chore: stackage 23.28 -> 24.37
This updates stack builds to GHC 9.10.3.
2026-04-19 10:07:14 +00:00
Wolfgang Walther 9ed7f52685 nix: put tmpdirs directly into /tmp to fix long unix socket paths
I'm not sure whether that's Lix specific, but when I enter nix-shell, I
get quite a long TMPDIR, which results in all postgrest-with-pg-XX
scripts failing, because the unix socket path is too long. If TMPDIR was
not set, mktemp would fall back to /tmp, so we can just do that in any
case, which guarantees a short path in every case.
2026-04-18 20:53:21 +02:00
Wolfgang Walther dfb3602318 nix: remove left-over comment 2026-04-18 20:53:20 +02:00
Wolfgang Walther f1cd9a2136 chore: stackage 22.44 -> 23.28
This updates stackage builds to GHC 9.8.4.
2026-04-18 18:38:32 +00:00
Taimoor ZaeemandWolfgang Walther 759917772c chore: add changelog entry for v14.10
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-17 10:20:30 +00:00
steve-chavez bcc8998e5e add: log schema cache queries' timings
This adds a new log line that shows each schema cache query time individually, only on
`log-level=debug`. Like so:

```
$ PGRST_LOG_LEVEL=debug postgrest-with-pg-17 -f test/spec/fixtures/load.sql postgrest-run

....
10/Apr/2026:21:48:45 -0500: Schema cache queried in 192.2 milliseconds
10/Apr/2026:21:48:45 -0500: tables: 72.027 ms, keydeps: 20.118 ms, rels: 6.189 ms, funcs: 35.010 ms, comprels: 4.319 ms, dreps: 1.614 ms, mhandlers: 7.419 ms, tzones: 43.025 ms
```

This helps debug specific schema cache queries being slow like on
https://github.com/PostgREST/postgrest/issues/4613#issuecomment-4210191065 and
https://github.com/PostgREST/postgrest/issues/3046#issuecomment-3469059948.
It also closes https://github.com/PostgREST/postgrest/issues/3215,
which main motivation was to find out which query is slow.

Implementation details
---------------------

To time each query inside a transaction in pure SQL, we do:

```sql
-- start timer
select set_config('pgrst.tmp_x', clock_timestamp()::text, false);
-- run the query
select <query>
-- end timer
select set_config('pgrst.tmp_x', (clock_timestamp() - current_setting('pgrst.tmp_x', false)::timestamptz)::text, false);

-- .... repeated for every query

-- at the end we capture all the timings with
select extract('milliseconds' from current_setting('pgrst.tmp_x', false)::interval), extract(..;
```

Considerations
--------------

Only added this on `log-level=debug` because while the queries are fast
and the data is valuable, it triples the amount of queries we run during schema cache refresh,
which could be troublesome on slow networks. It's possible to reduce the
amount of queries by starting and stopping timers in one statement, but
this would still double the amount of queries and makes the code messy,
doesn't seem worth it.

Also it would pollute pg_stat_statements, it's only required to debug certain
extreme cases anyway.
2026-04-16 13:10:24 -05:00
steve-chavez 6af77360d3 docs: add note about schema cache queries 2026-04-16 13:10:24 -05:00
renovate[bot]andWolfgang Walther f939a81ad6 chore(deps): update ubuntu:noble docker digest to c4a8d55 2026-04-16 10:44:13 +00:00
renovate[bot]andWolfgang Walther bf8a52694a chore(deps): update haskell-actions/setup action to v2.11.0 2026-04-15 18:05:32 +00:00
Wolfgang Walther 4fbde347f8 docs: fix broken link 2026-04-15 09:29:35 +02:00
Michal KleczekandGitHub aca58c8571 fix: Flush pool as late as possible during schema cache reloading (#4645)
retryingSchemaCacheLoad flushes the pool upon every retry before it starts reloading the schema. This is too early as schema reloading might take some time during which new connections might be acquired. The consequence is that:
* upon successful schema cache reload we might have some connections created with the old schema cache
* we close connections upon each retry and under load we will keep closing and re-opening connections until schema cache load succeeds

This change is to make sure we flush the pool only after successful schema cache querying but before loading (so that connections acquired during loading wait for it and do not interfere with timing the loading process).
2026-04-14 17:09:25 -05:00
Taimoor ZaeemandSteve Chavez 4ca34c0695 chore: update cabal spec version in postgrest.cabal
The current version is too old and it prevented using latest features
like common stanzas and visibility fields.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-14 12:16:48 -05:00
renovate[bot]andWolfgang Walther 26ce39bb46 chore(deps): update actions/cache action to v5.0.5 2026-04-13 21:04:11 +00:00
Taimoor ZaeemandSteve Chavez 4e8c4f218f chore: add changelog entry for v14.9
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-13 11:15:18 -05:00
renovate[bot]andWolfgang Walther 070eb2fc3a chore(deps): update actions/create-github-app-token action to v3.1.1 2026-04-12 15:51:35 +00:00
renovate[bot]andWolfgang Walther 431f8586f5 chore(deps): update actions/upload-artifact action to v7.0.1 2026-04-12 15:25:15 +00:00
steve-chavez c9adaed79d docs: target_session_attrs=read-only in libpq>=14 2026-04-10 15:14:41 -05:00
Michał KłeczekandSteve Chavez 381e12efa2 refactor: remove usage of Control.Debounce
This change replaces usage of Control.Debounce in Logger with makeDebouncer function extracted from AppState to a new module PostgREST.Debounce.
2026-04-10 12:22:08 -05:00
Michał KłeczekandSteve Chavez f4fc29855e refactor: remove leftover comment in AppState 2026-04-10 12:22:08 -05:00
steve-chavez 2fb2e86473 fix: remove red herring warp logs on default log-level
The logs added on e95e815483 are red
herrings under normal operation.

This moves them to `log-level=debug` and removes "error" from the
message prefix.

Fixes https://github.com/PostgREST/postgrest/issues/4799
2026-04-09 16:44:25 -05:00
Michał KłeczekandSteve Chavez c31aeecdcf test: add test_pool_acquisition_timeout_logs_are_debounced to io tests 2026-04-09 13:59:17 -05:00
Michał KłeczekandSteve Chavez 5d4f82d70f add: log pool flushes
Emit a dedicated PoolFlushed observation when the DB pool is released during schema cache reload.
2026-04-09 10:41:29 -05:00
Michał KłeczekandSteve Chavez ad907219be test(refactor): make debug output testing more robust
This change introduces a match_log function, that implements lenient matching of log output lines. Tests of log output are retrofited to use the function.
2026-04-09 10:41:29 -05:00
renovate[bot]andWolfgang Walther 463b701e9f chore(deps): update haskell-actions/setup action to v2.10.4 2026-04-09 08:48:59 +00:00
Steve Chavez 3d98f8d65b refactor: clarify debouncer function 2026-04-07 13:44:03 -05:00
Michał KłeczekandSteve Chavez a4c1d945ee refactor: Simplify schema cache loading triggering logic
DISCLAIMER:
This commit was authored entirely by a human without the assistance of LLMs.

Using debouncer to trigger schema cache loading makes it difficult to understand when exactly it is triggered.
2026-04-07 13:44:03 -05:00
renovate[bot]andWolfgang Walther ff068aa1b7 chore(deps): update ubuntu:noble docker digest to 84e77de 2026-04-07 08:10:36 +00:00
Taimoor ZaeemandSteve Chavez 9e48a338c3 chore: add changelog entry for v14.8
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-06 10:50:16 -05:00
steve-chavez b610b1ea51 docs: clarify set operators need views/functions
Closes https://github.com/PostgREST/postgrest/issues/4780.
2026-04-03 17:02:21 -05:00
Artur Bento de CarvalhoandGitHub 69c6a0aa36 fix: use int32/int64 formats for integer types
Fixed integer type mapping in OpenAPI 2.0: replaced the invalid integer format with int32/int64 and added the toSwaggerFormat function to map PostgreSQL types to valid OpenAPI 2.0 formats:

smallint -> int32
integer -> int32
bigint -> int64
2026-04-03 14:21:32 -05:00
renovate[bot]andWolfgang Walther a5e66a4448 chore(deps): update docker/login-action action to v4.1.0 2026-04-02 20:07:13 +00:00
Michał KłeczekandSteve Chavez 328598eaed test: Schema cache load debouncing
test: adjust replicateM to 100
2026-04-02 09:02:18 -05:00
Taimoor ZaeemandSteve Chavez d6c338d588 add: config db-timezone-enabled for optional querying of timezones
To avoid repeated querying of `pg_timezone_names` every time schema
cache is reset, `Prefer: timezone` can be disabled by setting
`db-timezone-enabled = false`.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-01 12:48:38 -05:00
Michał KłeczekandSteve Chavez bd5de884fa test(io): add test_so_reuseport_zero_downtime_handover 2026-03-31 13:33:33 -05:00
Michal KleczekandGitHub 8d5b1018d3 refactor(test): provide means to validate metrics and observations
Some helpers are provided for introspecting metrics already (used in JWT cache tests). This change provides facilities to additionally validate emited Observation events.
A new Spec module is also implemented, adding basic tests of schema cache reloading - their main goal is to excercise the new infrastructure.
2026-03-31 13:15:56 -05:00
renovate[bot]andWolfgang Walther ae7d4d2553 chore(deps): update codecov/codecov-action action to v6 2026-03-27 10:34:37 +00:00
renovate[bot]andWolfgang Walther 88afa05cf8 chore(deps): update korthout/backport-action action to v4.3.0 2026-03-27 08:45:54 +00:00
Taimoor ZaeemandWolfgang Walther 748ba7f02b chore: remove blank line in CHANGELOG.md
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-27 08:44:09 +00:00
Taimoor ZaeemandWolfgang Walther 0b665ecd10 chore: correct config name in CHANGELOG.md
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-27 08:44:09 +00:00
Taimoor ZaeemandSteve Chavez 1b402d16ad test(spec): move preference tests into separate modules
Towards #4751.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-26 12:36:47 -05:00
Taimoor ZaeemandSteve Chavez 135b77d12b test(io): rename jwt-role-claim-key*.config dump files
These files were added to cover the config dumping of
string comparison operators in `JSPath`. Renaming and
adding a comment in these to make it clear.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-24 11:11:43 -05:00
steve-chavez effcc080e5 chore: put ai policy first in CONTRIBUTING
Otherwise it's not that visible.
2026-03-23 10:10:58 -05:00
arturbent0andWolfgang Walther 5e504a77fa docs: clarify debounce time window in schema cache reloads 2026-03-23 12:04:01 +00:00
arturbent0andWolfgang Walther c9c6ebb53a docs: add new terms to spellcheck dictionary 2026-03-23 12:04:01 +00:00
Artur Bento de CarvalhoandWolfgang Walther 941c2f2d17 docs: explain schema cache reload behavior with NOTIFY debouncing
Add a "debouncing" section to explain how PostgREST handles multiple NOTIFY events.
2026-03-23 12:04:01 +00:00
Taimoor ZaeemandWolfgang Walther bf7dc485b3 chore: add changelog entry for v14.7
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-23 11:22:29 +00:00
steve-chavez 622c6d3f19 fix: not logging termination unix signals
Under container environments like ECS, it's hard to know when PostgREST
is being terminated.
2026-03-20 13:09:10 -05:00
Taimoor ZaeemandSteve Chavez 12ef63370b nix(test): add test suite for observability tests
- Create separate test suite for observability tests

- Create wrapper script `postgrest-test-observability`

- Add to CI and `postgrest-check`

- Move JWT cache tests under observability tests

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-19 10:38:42 -05:00
renovate[bot]andWolfgang Walther 796339172c chore(deps): update ubuntu:noble docker digest to 186072b 2026-03-19 09:16:11 +00:00
renovate[bot]andWolfgang Walther 238e9078bf chore(deps): update cachix/cachix-action action to v17 2026-03-18 21:19:20 +00:00
renovate[bot]andWolfgang Walther 1934179c64 chore(deps): update codecov/codecov-action action to v5.5.3 2026-03-18 20:02:06 +00:00
renovate[bot]andWolfgang Walther 88c365fb52 chore(deps): update actions/cache action to v5.0.4 2026-03-18 19:08:42 +00:00
Taimoor ZaeemandSteve Chavez 0ff8a1a37a refactor(auth): use wildcard type constructor import
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-18 13:09:27 -05:00
renovate[bot]andWolfgang Walther c448712e88 chore(deps): update ubuntu:noble docker digest to 0d39fcc 2026-03-17 10:19:37 +00:00
Taimoor ZaeemandSteve Chavez d4ba41bd43 nix(shell): bypass proxy variables using NO_PROXY=*
Tested using:

```sh
export HTTP_PROXY=http://localhost:7890
nix-shell
postgrest-test-io
```

Closes #4633.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-16 10:53:10 -05:00
Taimoor ZaeemandSteve Chavez 40a1fea916 refactor: move role insertion in claims to PreQuery.hs
Also add a comment on `AuthResult` type to explain that
we can also access the claims in the db using GUCs.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-16 10:24:24 -05:00
Andrei DziahelandSteve Chavez 1f54e2accc add: add "Vary" header to responses
Co-authored-by: Steve Chavez <stevechavezast@gmail.com>
2026-03-16 09:55:20 -05:00
renovate[bot]andWolfgang Walther d5df12c9c2 chore(deps): update actions/create-github-app-token action to v3 2026-03-14 18:04:36 +00:00
steve-chavezandWolfgang Walther af4c4157f5 nix: remove uneeded slocat
slocat was introduced to test pipeline mode (see https://github.com/PostgREST/postgrest/pull/2682),
but pipeline mode was not merged https://github.com/PostgREST/postgrest/pull/2707.

So it's really not needed on the loadtests.
2026-03-13 18:56:31 +00:00
renovate[bot]andWolfgang Walther 5ff271592c chore(deps): update ubuntu:noble docker digest to d1e2e92 2026-03-13 14:06:41 +00:00
renovate[bot]andWolfgang Walther bb4358c8bb chore(deps): update cachix/cachix-action digest to 3ba601f 2026-03-13 14:06:12 +00:00
renovate[bot]andWolfgang Walther 2ab2706c8e chore(deps): update actions/cache action to v5.0.3 2026-03-13 14:05:23 +00:00
renovate[bot]andWolfgang Walther b83fbb76a9 chore(deps): update haskell-actions/setup action to v2.10.3 2026-03-13 14:04:41 +00:00
renovate[bot]andWolfgang Walther fb54db143d chore(deps): update actions/checkout action to v6.0.2 2026-03-13 14:03:45 +00:00
renovate[bot]andWolfgang Walther 8ee8cdd7f8 chore(deps): update github artifact actions 2026-03-12 21:08:47 +00:00
Michał KłeczekandSteve Chavez bee2ea6fa6 test(io): add test_graceful_shutdown_waits_for_in_flight_request 2026-03-12 15:24:46 -05:00
Michał KłeczekandSteve Chavez a4aa58b4de refactor: move socket creation and management to App module
Right now listening sockets initialization, management and usage is split between App, AppState and Admin modules: they are created in AppState.init and remembered in AppState but used only in App and Admin.

It has several negative consequences:
- sockets are initialized even if not needed (eg. command line invocations like dump-config or dump-schema)
- it is impossible to start listening on a socket after initial schema cache load because it requires AppState

This change decouples listen socket management from AppState. Sockets are created only when needed (ie. not in command line tools invocation) and passed to admin application and to Warp by the App module.
2026-03-12 10:03:47 -05:00
Michał KłeczekandSteve Chavez e741c1bca7 refactor: provide AppState infrastructure to wait for schema cache load
This commit replaces ioRef based implementation of schema cache status tracking to MVar based, so that it is possible to wait for schema cache loading.

Waiting for schema cache loading is necessary to implement zero-downtime upgrades  with SO_REUSEPORT, where listening on a socket must wait for schema cache loading.
2026-03-12 09:04:56 -05:00
Wolfgang WaltherandAndrei Dziahel d5694672a4 nix(flake): add devShell
Adds a devShell to the flake for use with `nix develop`.

Co-authored-by: Andrei Dziahel <develop7@develop7.info>
2026-03-11 19:50:01 +00:00
Wolfgang Walther ca858f7544 docs: fix prometheus text format link
Reported by linkcheck.
2026-03-11 08:48:49 +01:00
renovate[bot]andWolfgang Walther cab60b9ee8 chore(deps): update docker/setup-buildx-action action to v4 2026-03-10 18:40:00 +00:00
steve-chavezandWolfgang Walther 0b7f0197fc chore: update license 2026-03-10 16:41:42 +00:00
Taimoor ZaeemandSteve Chavez 93db1a5a47 refactor: remove redundant parameters from actionResponse
Towards #4608.

We already have access to these parameters via `ApiRequest` type,
hence no need to pass them separately.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-10 09:55:42 -05:00
renovate[bot]andWolfgang Walther f657ac4e0b chore(deps): update docker/login-action action to v4 2026-03-10 14:15:16 +00:00
Taimoor ZaeemandSteve Chavez f5b9ed28db nix(chore): remove defunct config in loadtest.nix
`PGRST_JWT_CACHE_MAX_LIFETIME` is defunct since #4084 is merged.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-09 15:46:23 -05:00
renovate[bot]andWolfgang Walther 0eb2bdd8e1 chore(deps): update korthout/backport-action action to v4.2.0 2026-03-09 13:13:13 +00:00
Taimoor ZaeemandSteve Chavez 58368513ab chore: add changelog entry for v14.6
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-08 21:01:30 -05:00
Taimoor ZaeemandSteve Chavez 5abacba0d8 fix(error): leaking table and function names when calculating hint
Increase similarity score to 0.75 from 0.33 for table and functions
error hint.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-06 16:03:21 -05:00
Laurence Isla 2861b35f41 chore: update sponsors
* The list of sponsors is now ordered by total contribution value (highest first)
* Removed former backers from sponsor pages
2026-03-03 22:02:18 -05:00
steve-chavez 4e4ae3c7a2 chore: add AI policy in CONTRIBUTING
To preserve our scarce review capacity, we reserve the right to reject
seemingly AI generated low-value contributions.

---

Also delete the first paragraph since now it can be interpreted as
low-effort contributions.
2026-03-03 19:56:08 -05:00
steve-chavez 1c6d0816a5 chore: mention tests are required in CONTRIBUTING
Also remove the Haskell conventions subtitle since these are general
recommendations.
2026-03-02 17:57:01 -05:00
Laurence Isla 341e40196d chore: update sponsor 2026-03-02 17:36:04 -05:00
Michał KłeczekandSteve Chavez 8e1f0a0471 refactor: standardize query logging to follow code logging other observations
This commit addresses the following TODO in observationMessage function:
"TODO pending refactor: The logic for printing the query cannot be done here. Join the observationMessages function into observationLogger to avoid this mempty."

Changing observationMessage to observationMessages (that returns [Text] instead of Text) made it possible to address issue documented as TODO and harmonize query logging with other observations.
2026-03-02 15:46:17 -05:00
Michał KłeczekandSteve Chavez 2408cd332d refactor: get rid of redundant SchemaCacheSummaryObs
There is unnecessary coupling between observation messages and emited log entries. This causes schema loading logic to emit redundant events: SchemaCacheSummaryObs and SchemaCacheLoadedObs.

Logically - we want to emit a single event containing both summary and timing information. How it is logged is a different matter and should be decoupled.

This commit
* changes observationMessage function returning Text to observationMessages returning [Text] so that it is possible to return multiple (or zero) messages to log based on an observation event
* Removes SchemaCacheSummaryObs constructor from Observation type and adds summary text to SchemaCacheLoadedObs
2026-03-02 14:45:31 -05:00
Michał KłeczekandSteve Chavez 58a973e664 refactor: make Observation Logger and Listener modules more cohesive
Observation module exports observationMessage and isDbListenerBug functions. The first one is used only in Logger module and the second one - only in Listener.

This change moves observationMessage function to Logger module and isDbListenerBug function to Listener module, making all three modules more cohesive and lessening dependencies.
It also gives the compiler more opportunities for intra-module inlining and optimization.
2026-03-02 12:26:22 -05:00
Taimoor ZaeemandSteve Chavez a36963026e docs: add client error verbosity section to errors page
Add a new section "Client Error Verbosity". Move the details from
the config page to this section and link it.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-03-02 12:07:55 -05:00
Michał KłeczekandSteve Chavez 78f231c87a refactor: failure in schema cache load should not set listener delay
Listener has its own backoff logic independent of schema cache loading. It also uses listener delay value to identify its retries and to trigger schema cache reloads upon failures.
If schema cache reloading sets listener delay it might lead to some unexpected reloading loops.
That's especially important in set ups where listener connection points to master and the pool to replica(s).
2026-02-25 17:34:38 -05:00
Taimoor ZaeemandSteve Chavez 5992430e9f chore: update CHANGELOG.md
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-25 15:24:27 -05:00
Taimoor ZaeemandSteve Chavez 83dc082acf add: config client-error-verbosity to set error verbosity
Set error verbosity using this config. The verbosity can
be set to `verbose` or `minimal` for client error responses.

This only affects client side HTTP responses, server side logs
are not affected by this config.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-25 15:24:27 -05:00
Taimoor ZaeemandSteve Chavez 2edc44c352 refactor: remove ToJSON instance definition on error types
Towards #4088.

- Some of these instances are not used. Reduces number of lines
  significantly.

- Removing this gives us more flexibility for cases like conditional
  encoding based on some outside parameter, without needing to
  add the conditional at type level.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-23 12:10:51 -05:00
Taimoor ZaeemandSteve Chavez 02feaf087e refactor: remove commented out code from Auth/Jwt.hs
Some commented out code got left in the codebase in
5196823365.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-20 11:07:30 -05:00
Taimoor ZaeemandSteve Chavez c52fd3fd2b refactor: rename ApiRequestError type constructor to ApiRequestErr
Rename to keep the naming convention consistent with other
type constructor names.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-19 11:29:51 -05:00
Taimoor ZaeemandSteve Chavez 248b777117 refactor: move error data types to Error/Types.hs
The `Error.hs` module is getting bloated. This moves types
to another module for better separation of concerns.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-19 11:29:51 -05:00
steve-chavez 66fda76644 docs: missing link to prefer:tx in config page 2026-02-19 10:23:22 -05:00
dshukertjrandWolfgang Walther 23692ab8e7 docs: Update the Supabase logo to a correct one 2026-02-19 09:52:46 +00:00
Taimoor ZaeemandWolfgang Walther b977ffbb90 test(io): reset statement_timeout of anon role to correct value
Forgot to do this in bfb4f900e7.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-18 12:46:30 +00:00
Michał KłeczekandSteve Chavez 85a313a8cc test(spec): Move metrics state helpers from JwtCacheSpec to SpecHelpers
Refactoring: State validation helpers used in JwtCacheSpec moved to SpecHelper
to make them available in other Spec modules.
2026-02-17 17:55:16 -05:00
Taimoor ZaeemandSteve Chavez bfb4f900e7 test(io): increase statement_timeout value of anonymous role
It is too low which leaves a small window of values to
use when testing other features.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-17 11:35:12 -05:00
Taimoor ZaeemandSteve Chavez 055efe7abd refactor: move Proxy-Status header to error type class
Headers that apply to all errors should all be in one place.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-16 13:09:01 -05:00
Michał KłeczekandSteve Chavez ca96328142 refactor: Remove unnecessary lazy initialization of stateLogDebouncePoolTimeout
stateLogDebouncePoolTimeout is an MVar initialized on the first logging of PoolAcqTimeoutObs. The code in logWithDebounce has race condition that could lead to creation of multiple debouncers.

This change simplifies logic by getting rid of lazy initialization of debouncer.
2026-02-15 13:14:10 -05:00
Michał KłeczekandSteve Chavez d6816d8d2a refactor: Remove unnecessary SQL.UsageError parameter from PoolAcqTimeoutObs
PoolAcqTimeoutObs is always created with SQL.AcquisitionTimeoutUsageError so the parameter does not provide any useful information.
On the other hand, it complicates debouncing logic in Logger as logWithDebounce has to lazily create the debouncer (which is error prone and hence the logic has a race condition).

This change removes the parameter from PoolAcqTimeoutObs. It is a prerequisite to simplifying logWithDebounce.
2026-02-15 13:14:10 -05:00
Taimoor ZaeemandSteve Chavez c5d495006a chore: add changelog entry for v14.5
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-13 08:58:57 -05:00
steve-chavez abe414ad88 nix: print how to tail the server logs
For easier debugging during loadtests.

Also prefix the echo traces for the postgrest-with-pgrst command.
2026-02-12 20:18:08 -05:00
steve-chavez e95e815483 fix: don't hide async exceptions in logs
Fixes #4646. Using the repro on #4646, this now produces the log:

```
11/Feb/2026:09:40:08 -0500: Warp server error: stack overflow
```

When:
```
$ curl localhost:3000/
curl: (52) Empty reply from server
```
2026-02-12 19:43:50 -05:00
Taimoor ZaeemandWolfgang Walther c3500bd3af docs(install): update postgresql minimum supported version
PostgREST dropped support for PostgreSQL version 12 however,
it was not reflected in the docs.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-12 09:22:01 +00:00
Taimoor ZaeemandSteve Chavez 99984d328e docs: add section on docker resource constraints
Closes #4623.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-06 16:30:47 -05:00
Taimoor ZaeemandSteve Chavez aaaf7e96bd test(io): set client_min_messages to warning in SQL fixtures
To suppress unwanted "NOTICE: ..." messages which pollute the log.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-04 09:20:03 -05:00
Taimoor ZaeemandSteve Chavez 37fc0a211b test(io): refactor SQL fixtures
- Move privileges and grants to privileges.sql
- Move schema, tables and functions to schema.sql
- Move global database settings to database.sql

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-04 09:20:03 -05:00
Taimoor ZaeemandSteve Chavez c35eb7a6c2 test(io): move role fixtures to roles.sql
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-02 17:38:32 -05:00
Taimoor ZaeemandSteve Chavez 2bd4b07418 chore: add entry for a feature which was a breaking change
Adds the associated breaking change entry in CHANGELOG.md
for #4517.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-02-02 09:14:58 -05:00
steve-chavez 55cd5a23ff chore: move fixed entry to feature in changelog 2026-01-30 16:23:30 -05:00
Taimoor ZaeemandSteve Chavez 72a9d70f09 chore: add changelog entry for v14.4
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-30 08:46:47 -05:00
Laurence Isla d10c779fc6 perf: optimize count=exact when there's no limits, offsets or db-max-rows 2026-01-28 18:43:44 -05:00
Laurence Isla d031bb2df5 refactor: use a single function to get the page_total count 2026-01-28 18:43:44 -05:00
Michal KleczekandGitHub 73a465501e fix: listener running with exception masked after first failure 2026-01-28 17:13:59 -05:00
Wolfgang Walther 6f1fe69433 docs: add Wolfgang Walther as author
This was done in other places in
ffa393850a, but missed in the docs.
2026-01-28 09:59:42 +01:00
Wolfgang Walther 246239f42a docs: fix links
datrium.com doesn't exist anymore, while euronodes.com seems to only
fail SSL in CI.
2026-01-28 09:57:09 +01:00
Laurence Isla 1682677297 fix: filtering the returned representation whenn using or/and filters on mutations 2026-01-27 21:24:19 +00:00
Michał KłeczekandSteve Chavez 21b8c3458a add: Log pg version details of listener connection
Follow-up to #4617 adding more information to log entry produced upon successful listener connection establishement.
2026-01-27 15:44:05 -05:00
Michał KłeczekandSteve Chavez 5356f4e973 add: Log actual host and port of listener connection
Diagnosing problems with listener channel notifications not being handled properly by PostgREST connected to read replicas is difficult. Issues might be related to lost connections and listener not being connected to the right host after failover or database server restarts.
This patch adds logging of actual host:port used by libpq connection opened by the listener. It should make it easier to find out if PostgREST is connected to the right host.
2026-01-27 11:00:30 -05:00
Michal KleczekandGitHub 00c7cb1a22 fix: ensure Listener connections are released
retryingListen function potentially leaks database connections. This patch ensures the connections are released in case of listen/notify errors.
2026-01-26 16:26:24 -05:00
Taimoor ZaeemandSteve Chavez d75b1a7e96 docs: using default parameters on RPC with GET request
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-26 08:06:48 -05:00
Taimoor ZaeemandGitHub fe0386e9c4 add: string slicing operator for jwt-role-claim-key (#4603) 2026-01-24 05:40:21 -05:00
Taimoor ZaeemandWolfgang Walther 5a4c11c72a chore(ci): correct slightly misleading name of coverage workflow
The coverage workflow mentions that we run the tests against PG 15,
however that is incorrect and misleading. We actually run it against the
latest supported PostgreSQL version according to the nix scripts.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-18 14:53:52 +00:00
Laurence IslaandGitHub c15e908266 add(logs): Include a HINT when the LISTEN channel breaks due to a Postgres bug
The HINT shows a SQL command that solves the issue.
2026-01-18 18:47:53 +07:00
Taimoor ZaeemandSteve Chavez c18727ff43 refactor: move walkJSPath to Config/JSPath.hs module
The logic to evaluate `JSPath` belongs to `JSPath.hs` module.
Hence, moving this logic from `Auth/Jwt.hs` to here.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-14 22:34:08 +07:00
Michał KłeczekandSteve Chavez 886df84e87 test: Fix flakiness of test_second_request_for_non_existent_table_should_be_quick
Changed divider in assertion (response.elapsed.total_seconds() < first_duration / divider) to 2 (from 10).
2026-01-13 11:37:50 +07:00
renovate[bot]andWolfgang Walther 8f5fe3f46b chore(deps): update haskell-actions/setup action to v2.10.2 2026-01-11 17:31:31 +00:00
steve-chavez ffa393850a chore: update LICENSE 2026-01-11 11:59:31 +08:00
Kian-Meng AngandSteve Chavez f5c7cde9b7 docs: fix typos
Found via `codespell -S test -L notin,nam,plantext,cryto,corelation,inout,ot`
2026-01-06 05:24:51 +08:00
renovate[bot]andWolfgang Walther 0018c38f79 chore(deps): update haskell-actions/setup action to v2.10.1 2026-01-05 19:02:21 +00:00
Taimoor ZaeemandSteve Chavez 9042457326 chore: add changelog entry for v14.3
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-04 13:13:00 +08:00
Taimoor ZaeemandSteve Chavez e988fe49ed chore: move changelog entry to unreleased section
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-03 15:18:51 +08:00
Michał KłeczekandSteve Chavez e592d568c6 fix: Performance and high memory usage of relation hint calculation
* Calculation of hint message when requested relation is not present in schema cache requires creation of a FuzzySet (to use fuzzy search to find candidate tables). For schemas with many tables it is costly.
This patch introduces dbTablesFuzzyIndex in SchemaCache to memoize the FuzzySet creation.

* Additionally, because of FuzzySet large memory requirements, this patch introduces a limit of 500 relations per schema, above which FuzzySet is not created and hint calculation disabled.
2026-01-03 07:56:12 +08:00
Taimoor ZaeemandSteve Chavez 9ec5b030ce refactor: move content negotiation logic to Negotiate.hs
Moves the `negotiateContent` function to `Plan/Negotiate.hs`
module. This also adds comments to describe the current approach
we have for negotiation.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-01-02 06:32:17 +08:00
steve-chavez 76e0e1fbff nix: fix sleep default on postgrest-with-pgrst
The sleep was always applying since it had a default.
2026-01-02 06:11:07 +08:00
steve-chavez b8ca1bb04c nix: add new loadtest for errors
Also included it in CI.
2025-12-23 15:00:24 -05:00
steve-chavez 57c5ff5db3 nix: show wait time for fixture loading 2025-12-23 15:00:24 -05:00
steve-chavez 2b83dcc794 nix: sleep param for withPgrst 2025-12-23 15:00:24 -05:00
steve-chavez 7c3bccf4ba nix: show wait time on withPgrst 2025-12-23 15:00:24 -05:00
steve-chavez d2aa6c1b8a nix: timeout param for withPgrst 2025-12-23 15:00:24 -05:00
Taimoor ZaeemandSteve Chavez a329bca16b nix: add config file for hlint
Adds a config file for hlint containing arguments and
custom warnings.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-12-22 14:19:46 -05:00
388930336d refactor: fix definition of Ord instance for Routine type (#4577)
The `Ord` instance definition for type `Routine` had a logical
error when comparing two routines. The error did not affect any
end users. However, for correctness and completeness reasons, this
commit fixes the error.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
Co-authored-by: Joel Jacobson <joel@compiler.org>
2025-12-22 08:34:29 +00:00
renovate[bot]andWolfgang Walther d06377a37b chore(deps): update docker/setup-buildx-action action to v3.12.0 2025-12-20 20:25:19 +00:00
Taimoor ZaeemandSteve Chavez eed018fe1d chore: add changelog entry for v14.2
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-12-19 12:29:57 -05:00
steve-chavez 013f078bc4 fix: inaccurate Server-Timing durations
The transaction duration was notably off, doing:

```
curl localhost:3000/rpc/sleep?seconds=5 -i
```

Shows `46.1` for the `transaction;dur`, with this fix we obtain
`5007.3`.

Fixes https://github.com/PostgREST/postgrest/issues/4522

This also fixes inaccurate "schema cache queried" logs,
see https://github.com/PostgREST/postgrest/issues/4551.
2025-12-18 17:35:18 -05:00
renovate[bot]andWolfgang Walther 2bcd336400 chore(deps): update haskell-actions/setup action to v2.9.1 2025-12-17 18:01:04 +00:00
steve-chavez 8f34afd66e nix: jwt loadtest chain commands and split rsa gen
Chaining the target generation command after the build is done ensures
that if a build takes too long, the dynamic target generation for vegeta
won't cause 401 Unauthorized errors due to already expired JWTs.

Also split the rsa materials writing to another python program for
easier maintenance.
2025-12-16 21:15:28 -05:00
steve-chavez 4cdc4c4861 nix: show build time on postgrest-with-pgrst 2025-12-16 21:15:28 -05:00
steve-chavez 050554e2bb nix: fail jwt loadtest in case of 401
Also print last unauthorized body
2025-12-16 21:15:28 -05:00
Taimoor ZaeemandSteve Chavez b3b7bd9327 docs: add link on how to start docker service
Closes #4552.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-12-16 11:34:04 -05:00
renovate[bot]andWolfgang Walther dac8a5fb86 chore(deps): update haskell-actions/setup action to v2.9.0 2025-12-15 21:01:22 +00:00
Joel JakobssonandGitHub fd6a3bdccf fix: hasSingleUnnamedParam incorrectly matching named parameters
The hasSingleUnnamedParam function was only checking the parameter type
but not whether the parameter actually had no name. This caused functions
with a single NAMED parameter (e.g., `foo(data json)`) to incorrectly
match the single-param fallback mode.

The result was a confusing PostgreSQL error 42883 "function does not exist"
instead of a clean PGRST202 error explaining that no matching function
was found.

Added ppName == mempty check so functions with named parameters don't
incorrectly match the single-param fallback.
2025-12-15 15:34:48 -05:00
renovate[bot]andWolfgang Walther 60c70c5b37 chore(deps): update korthout/backport-action action to v4.0.1 2025-12-15 20:18:02 +00:00
renovate[bot]andWolfgang Walther c9f9b93ad7 chore(deps): update github artifact actions 2025-12-13 14:36:30 +00:00
renovate[bot]andWolfgang Walther e39b91c27b chore(deps): update korthout/backport-action action to v4 2025-12-13 14:36:12 +00:00
renovate[bot]andWolfgang Walther d694ad90e0 chore(deps): update actions/cache action to v5.0.1 2025-12-13 11:31:32 +00:00
steve-chavez 99b8246d6e nix: add --method for postgrest-loadtest
Currently the `postgrest-loadtest -k jwt-*` loadtests use the OPTIONS method,
which is good for isolating the perf changes for JWT auth.

But using GET is more accurate for actual perf improvements,
since it actually hits the database.

This adds a method argument for postgrest-loadtest, so GET can be used like:

```
postgrest-loadtest -k jwt-hs --method GET
```

CI is not changed, this is mostly useful for manually checking variance.
2025-12-12 14:46:46 -05:00
steve-chavez 90bf1762ae nix: rename genTargetsHs to genTargets
Previous name wasn't accurate as RSA is used as well.
2025-12-12 14:46:46 -05:00
renovate[bot]andWolfgang Walther c9c975ce49 chore(deps): update actions/cache action to v5 2025-12-12 08:29:32 +00:00
Taimoor ZaeemandSteve Chavez 29c2aa015f refactor: create function to handle response preferences
Centralizes handling of preferences that are used to
create `Preference-Applied` header which is returned on
responses.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-12-11 17:55:45 -05:00
Taimoor ZaeemandSteve Chavez abad47ff1b add: log error when db-schemas config contain restricted schemas
We don't allow accessing schemas "pg_catalog" and "information_schema".
Instead of silently failing requests on these schemas, this now adds
logging an error on startup when these schemas are found in `db-schemas`
config.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-12-11 14:28:08 -05:00
Laurence IslaandSteve Chavez c232e97f80 chore(changelog): move incorrectly placed fixes 2025-12-10 20:00:31 -05:00
Laurence Isla ab9b00ce88 refactor: use only Lazy.ByteString to calculate the response body length 2025-12-10 14:18:19 +00:00
Laurence Isla 9200a98087 fix: add missing Content-Length to empty HTTP 201 responses
For when a preference other than return=representation is requested.
2025-12-10 14:18:19 +00:00
Laurence Isla 3b27b2220d fix: regression that truncates error message when offset is out of bounds
- Happens when offset > the number of rows and when "Prefer: count=exact" header is sent
- Regression introduced in commit 57ef998
2025-12-10 14:18:19 +00:00
renovate[bot]andWolfgang Walther 6573953ebd chore(deps): update codecov/codecov-action action to v5.5.2 2025-12-10 12:24:43 +00:00
Taimoor ZaeemandSteve Chavez 0fa2f569a7 fix: misleading logs on unsupported postgresql versions
Postgrest fails on unsupported pg versions. However before killing
the thread, it continues to print a few more log messages which
were misleading. This commit fixes this by making sure that the
no log message should be printed after the unsupported pg version
observation and kill the thread immediately.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-12-09 13:46:34 -05:00
renovate[bot]andWolfgang Walther 24a27d17d0 chore(deps): update actions/create-github-app-token action to v2.2.1 2025-12-06 11:00:06 +00:00
renovate[bot]andGitHub 1724e4551d chore(deps): update actions/checkout digest to 8e8c483 (main) (#4511) 2025-12-03 19:32:01 +00:00
renovate[bot]andWolfgang Walther 457f20f962 chore(deps): update actions/checkout action to v6.0.1 2025-12-03 19:30:42 +00:00
renovate[bot]andWolfgang Walther 70240b0d08 chore(deps): update actions/create-github-app-token action to v2.2.0 2025-12-03 19:29:54 +00:00
Taimoor ZaeemandSteve Chavez 50eec773bf test(io): fix freeport function to prevent failures
Sometimes, a healthcheck related test fails as occurred in
https://github.com/PostgREST/postgrest/actions/runs/19771357953/job/56655949002.
This happens due to freeport function accidently picking up a used port.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-29 18:17:00 -05:00
Taimoor ZaeemandSteve Chavez 35de13eebd test(io): move fixtures to fixtures/ directory
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-27 17:08:03 -05:00
Taimoor ZaeemandSteve Chavez 3baa6b0063 test(io): rename fixtures.sql to load.sql
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-27 17:08:03 -05:00
Andrei DziahelandGitHub 6d462f762e nix: override PGRST_CMD for postgrest-loadtest from env
Enables `PGRST_CMD=postgrest-profiled-run postgrest-loadtest` running
loadtest against profiled executable, which wasn't available before
2025-11-25 19:46:23 +00:00
Taimoor ZaeemandGitHub 802cce9a28 nix(shell): remove postgrest/ directory prefix when running pg (#4502)
When running postgres from nix-shell, nix creates a directory
structure like `postgrest/postgrest-with-pg-17-XXX` in the `/tmp`
directory. This commit removes the extra `postgrest/` prefix to
shorten length of absolute path length of filenames.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-25 15:54:25 +00:00
Marek BriegerandGitHub ee334cbcf8 chore: improve grammar in README.md 2025-11-25 12:20:26 +00:00
Taimoor ZaeemandSteve Chavez c561a3749c test(io): move authentication related tests to test_auth.py
The `test_io.py` module is too bloated (2100+ lines). To
logically group related tests, as a first step, this commit
separates authentication related IO tests into `test_auth.py`
module.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-21 13:21:15 -05:00
renovate[bot]andWolfgang Walther f8b56736d7 chore(deps): update actions/checkout digest to 1af3b93 2025-11-21 17:29:18 +00:00
renovate[bot]andWolfgang Walther 7bcd46b145 chore(deps): update actions/checkout action to v6 2025-11-21 17:29:09 +00:00
Taimoor ZaeemandSteve Chavez d905592115 docs: add asterisk character to reserved characters list
The `*` character is a reserved character in the postgrest
url grammar but we didn't mention it in the docs. Now added.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-20 16:20:28 -05:00
Taimoor ZaeemandSteve Chavez 4b8dec8901 docs: clarify postgrest reserved characters in url grammar
Fixes a typo in the documentation where it said that `()`
is a reserved character, when we actually meant to say that
`(` and `)` are reserved characters.

Related discussion in #4254.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-20 16:20:28 -05:00
Taimoor ZaeemandSteve Chavez 379eaec8e0 test(io): remove stale jwt cache test
Removes a test related to jwt cache which is stale
since #4084.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-19 11:17:18 -05:00
Andrei DziahelandWolfgang Walther ae2d650516 fix: hashtables <1.4.1 fail to build with GCC 15
Introduces hashtables >= 1.4.1 requirement
2025-11-18 19:26:16 +00:00
Wolfgang Walther 4a83e79923 ci/test: remove macos x86 flake check
See previous commit.
2025-11-18 11:05:23 +01:00
Wolfgang Walther eb00c1991c ci: remove macos x86 builds
The x86 GitHub runner will not be available anymore, soon.

We might be able to re-introduce this, once we can build a static
executable via Nix on darwin, too.
2025-11-18 11:01:05 +01:00
Taimoor ZaeemandWolfgang Walther 462798dc2d nix: shorten postgrest-with-postgresql-xx scripts
Renames these scripts to `postgrest-with-pg-xx`. The renaming
helps reduce the length of temporary filenames. This is needed
to ensure that socket file names remain under the maximum
allowed length of 107 chars.

Closes #4461.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-18 09:40:16 +00:00
renovate[bot]andWolfgang Walther 1efd5781f8 chore(deps): update actions/checkout action to v5.0.1 2025-11-18 09:38:41 +00:00
Taimoor ZaeemandWolfgang Walther 08c6deba12 docs: update server-host config in docker-compose example
The docker-compose example did not work with multiple containers
when PGRST_SERVER_HOST is set to `localhost`. This updates the
value to `0.0.0.0` allowing other containers to connect.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-18 09:36:37 +00:00
renovate[bot]andWolfgang Walther 531848366d chore(deps): update ubuntu:noble docker digest to c35e29c 2025-11-15 13:52:00 +00:00
Taimoor ZaeemandSteve Chavez 91abcd49e1 test(io): move resource embedding tests to test_io.py
- Adds fixtures to `test/io/fixtures.sql` to test resource
  embedding related queries.

- Moves the resource embedding related tests that no longer
  require big schema from `test_big_schema.py` to `test_io.py`.

Closes #4417.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-11 13:43:10 -05:00
Jens TroegerandSteve Chavez 770c404ebe docs: add another project to the list of Example Apps 2025-11-11 13:25:55 -05:00
Taimoor ZaeemandSteve Chavez 99217433b1 chore(cabal): remove unused haskell dependencies
This should reduce setup time for build process.

- cache: introduced in #2928, defunct since #4084
- clock: introduced in #2928, defunct since #4084
- heredoc: introduced in #714, defunct since #4390
- iproute: introduced in #3560, defunct since #4288

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-09 13:49:18 -05:00
Taimoor ZaeemandWolfgang Walther bccf7ac97a Revert "chore: remove unused haskell dependencies"
This reverts commit b6bb8382e7.
2025-11-06 08:11:44 +00:00
Taimoor ZaeemandWolfgang Walther ed8be34b20 chore: add changelog entry for v14.1
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-06 08:11:32 +00:00
Taimoor ZaeemandSteve Chavez b6bb8382e7 chore: remove unused haskell dependencies
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-04 14:12:08 -05:00
renovate[bot]andWolfgang Walther 893e0061a3 chore(deps): update actions/checkout digest to 71cf226 2025-11-03 20:47:50 +00:00
Taimoor ZaeemandSteve Chavez 58efc2680e fix: server-host !6 incorrectly binds to IPv4 address
Updates streaming-commons to version 0.2.3.1. This resolves #3202.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-02 12:24:14 -05:00
Taimoor ZaeemandWolfgang Walther 8180905890 chore(changelog): fix typo in changelog entry
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-11-01 09:00:31 +00:00
Taimoor ZaeemandSteve Chavez db2be093b5 refactor: move dumpQi to Config.hs and clarify quoteQi
The `dumpQi` function is only used in the `Config.hs` module, so
it is moved there. This also adds a doctest for `quoteQi` and
clarifies its usage.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-10-30 12:26:29 -05:00
Taimoor ZaeemandSteve Chavez a688878236 fix: db-pre-config function failing with pg reserved words
When db-pre-config is accidentally set to a pg reserved word
like "true", it fails with a confusing error. The function
names should be properly quoted to avoid such errors. This commit
resolves this by quoting the pre-config function name.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-10-29 16:54:24 -05:00
Michał KłeczekandSteve Chavez 75d4131aa6 chore: Add commit structuring guidelines to CONTRIBUTING.md 2025-10-28 10:02:56 -05:00
Taimoor ZaeemandSteve Chavez 66f84c5903 refactor: move escapeIdent function to Identifiers.hs
Moves the functions `escapeIdent` and `trimNullChars` to
SchemaCache/Identifiers.hs module.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-10-28 09:47:37 -05:00
Taimoor ZaeemandSteve Chavez 5d9b169380 refactor: sort exports of Identifiers.hs and SqlFragments.hs
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-10-28 09:47:37 -05:00
Taimoor ZaeemandSteve Chavez eb908c696f chore(changelog): update versioning scheme description
The changelog description mentioned that we follow semantic
versioning but from now on we don't. Hence updated the description
to reflect new versioning policy.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2025-10-27 10:00:40 -05:00
renovate[bot]andWolfgang Walther 21edcc86ce chore(deps): update github artifact actions 2025-10-25 08:23:40 +00:00
Wolfgang Walther 6f46233ad7 ci: fix tag job with new release workflow
A single component version is the development version, everything with
more components is not. Thus, we only need to check for a single dot.
2025-10-25 10:20:32 +02:00
steve-chavez 018803473c bump version to 15 2025-10-24 17:39:04 -05:00
Adam WespiserandGitHub a4880f0372 bump version 0.3.5 (#152) 2025-05-15 22:15:12 -04:00
daad7c317d Support GHC 9.12 (#151)
* Test 9.6 and 9.8 in CI

* Support GHC 9.10

* Support GHC 9.12

---------

Co-authored-by: Tom Ellis <tom-git@jaguarpaw.co.uk>
2025-05-15 22:13:14 -04:00
Adam WespiserandGitHub 20f2e894c9 0.3.4 release (#148) 2024-02-04 22:23:39 -05:00
Erik de Castro LopoandGitHub 5a98f0c26d Make it build with ghc 9.8 (update deps only) (#146) 2024-02-04 22:19:13 -05:00
4680e62d7b ghc 9.4 PR (w/ Cabal improvement) (#142)
* Release 0.3.3, with support for GHC 9.4

- allow base-4.18 (GHC 9.4)
- allow ghc-prim 0.10
- allow bytestring 0.11.4
- test against GHC 9.4.4

Further CI tweaks:

- cabal: update GHC versions to the latest releases of the respective major version
- cabal: remove non-latest minor versions
- stack: add lts-20 (GHC 9.2.5)
- stack: bring the list of GHC versions up to date

* Update Cabal CI

---------

Co-authored-by: Robert Vollmert <rob@vllmrt.net>
2023-02-15 21:41:08 -05:00
Christoph SchiesslandGitHub cd83ebec00 Updates README to correctly list all supported stack LTS versions (#140)
* Updates README to correctly list all supported stack LTS versions

* Updates README to include LTS 19.14 and higher
2022-08-27 19:59:13 -04:00
Adam WespiserandGitHub e40b7351ec 0.3.2 Release (#127) (#138) 2022-06-12 13:46:49 -04:00
Adam WespiserandGitHub 3e249724fd 0.3.1 Release (#133) 2022-03-12 21:42:32 -05:00
Adam WespiserandGitHub 1b453b6064 Re-export isInfixOf, isSuffixOf from Data.List, Issue #119 (#135) 2022-03-06 11:20:07 -05:00
Adam WespiserandGitHub 3611929f01 Fix Hackage Formatting (#134) 2022-02-28 23:34:03 -05:00
f8cf1375d5 Add HasCallStack to unsafe* functions (#129)
Co-authored-by: Adam Wespiser <adamwespiser@gmail.com>
2022-02-21 14:38:19 -05:00
Adam WespiserandGitHub ee4d94feab Support ghc-9.0.1 (#131) 2022-02-20 20:19:01 -05:00
4e11d80eb7 Allow transformers-compat 0.7 (#125)
Tested to build fine.

Co-authored-by: Adam Wespiser <adamwespiser@gmail.com>
2021-12-01 00:33:59 -05:00
84d228a3b5 gcdInt' and gcdWord' are defined only with integer-gmp (#123)
Co-authored-by: Adam Wespiser <adam.wespiser@simspace.com>
2021-11-20 16:03:29 -05:00
Adam WespiserandGitHub f162c21d1b Update github actions, add new lts to tests (#124) 2021-11-17 09:17:40 -05:00
Stephen DiehlandGitHub e613ed4dd2 Merge pull request #120 from martijnbastiaan/readMaybeEither
Banish `String` on `readMaybe` and `readEither`
2021-01-04 18:30:54 +00:00
Martijn Bastiaan 36ce510547 Banish String on readMaybe and readEither 2020-12-05 20:06:45 +01:00
sdiehl 22e2cecaee 8.10.1 support to CI 2020-07-15 14:40:41 +01:00
Stephen DiehlandGitHub 0a92513108 Merge pull request #118 from protolude/strconv
Migrate show to use Conv.StringConv
2020-05-04 16:08:54 +01:00
sdiehl 9309ca3107 Migrate show to use Conv.StringConv 2020-04-25 11:58:41 +01:00
sdiehl 8ff8b7ae49 Update bounds in README 2020-03-27 12:40:31 +00:00
sdiehl 68f668951f Bump base & ghc-prim bounds for 8.10.1 2020-03-27 09:12:59 +00:00
sdiehl 7e081500f3 Supports lts 15.x 2020-03-21 13:20:57 +00:00
Stephen DiehlandGitHub 2666f1e830 0.3 Release (#115)
* Add total ConvertText alternative to Conv

* Explain ConvertText's meaning

* Rename convertText to toS for compatibility

* 0.3.0 Refactor (#111)

* Begin work on 0.3 refactor

* Remove old docs folder

* Fix dodgy exports

* 4.8 constraint for null and length

* Guards for Semigroup export for base<4.9

* Fix Data.Bits exports

* Guards for typeable exports

* Remove unused pragmas

* Update hlint.yaml

* Use liftM because of pre-AMP quirkk

* Data.Char exports

* Rework Exception exports

* Fix bounds

* Adding Monad.Fail shim

* Fix displayException export

* Explicit GHC.Prim import for ancient ghc

* Explicit raise# for base-4.7

* Flush legacy testing infrastructure

* Update git location

* Update README

* Add Partial module

* Add concurrency exports

* Explicit GHC.Float exports

* Bounds for threadWaitReadSTM, threadWaitWriteSTM, forkOSWithUnmask

* Fix for weird undocumented underflowError weirdness

* forkOSWithUnask for base>4.8

* Fix GHC.Real exports pre base-4.7

* Fix withMVarMasked for base-4.7

* Update nix derivation

* Update derivation

* Update derivation

* Explicit functor exports

* <$ export

* Overwrite binary

* Fix some missing exports

* Update Changelog

* Minor export fixes base<4.9

* Explicit exports

* Export handler

* Generate export lists for multiple versions (#114)

* Generate export lists for #112

* Make export lists format OccName uniformly

* Make compile on ancient GHC

* Use Foldable.concat

* Hacks to make ghc-7.6 API happy

* liftIO shim

* Fix sortOn warning

* Update ChangeLog

* Update base bounds

* Export conventions

* Update Haddocks

* Document GHC magic
2020-01-24 11:06:09 +00:00
sdiehl 03f73d8cde Name nix workflow 2020-01-04 23:12:42 +00:00
sdiehl af6cabe185 Added Nix CI 2020-01-04 23:02:48 +00:00
sdiehl e7a8e99f3f Fix install path for cabal. 2020-01-04 19:07:36 +00:00
sdiehl 0fda50b372 Cache cabal builds 2020-01-04 19:04:42 +00:00
sdiehl 76ad00a018 Run cabal update. 2020-01-04 18:49:38 +00:00
sdiehl e9da6f635e Add HLint action. 2020-01-04 18:48:01 +00:00
sdiehl e02ac85388 Fix Stack build names 2020-01-04 17:57:19 +00:00
sdiehl 3dd5152aba Build Stack CI on PRs 2020-01-04 17:55:55 +00:00
sdiehl 0ea2c5146a Seperate Stack and Cabal scripts 2020-01-04 17:54:59 +00:00
sdiehl 612726f87e Fix YAML for workflow 2020-01-04 17:29:28 +00:00
sdiehl 4d1f8dc86b Add Stack CI script 2020-01-04 17:28:27 +00:00
sdiehl 1277dfd941 Test all supported GHC versions 2020-01-04 17:19:50 +00:00
sdiehl a645953330 Fix GHC version 2020-01-04 16:26:15 +00:00
sdiehl fa0f1dc0fa Build on pull requeests 2020-01-04 16:23:51 +00:00
sdiehl 5e1b1ea982 Update build status badges 2020-01-04 16:18:33 +00:00
sdiehl f39de46d9f Don't install 2020-01-04 15:58:57 +00:00
sdiehl 95ccde5a2d Use cabal-new-build 2020-01-04 15:54:10 +00:00
Stephen DiehlandGitHub b8fa8a0729 Create haskell.yml 2020-01-04 15:39:54 +00:00
sdiehl ae9dd77749 Support all lts-14 resolvers 2019-11-22 08:06:09 +00:00
sdiehl 66d6d3506f Add lockfiles for resolvers 2019-11-02 18:46:01 +00:00
Stephen Diehl 357c0fdbed Update readme for 8.8.1 support 2019-10-10 11:49:44 +01:00
Stephen Diehl a93c7e6343 Support lts-14.* and GHC 8.8.1 2019-10-10 10:37:27 +01:00
Pi3randStephen Diehl 65c61c1d63 Fix #107: support ghc-8.8 (#108)
* Fix #107: support ghc-8.8

* Add travis build for ghc 8.8.1
2019-10-08 11:43:18 +02:00
Ary BorenszweigandStephen Diehl 2770a143d0 Update link to exported functions (#106)
The file `Symbols.md` doesn't exist anymore in the repository.

In the meantime Haddock fixed a long standing bug that allows Hackage to display all exported symbols now so we can now link to it.
2019-07-23 06:28:56 +01:00
Stephen Diehl c649da1944 Update LTS 13 resolvers 2019-06-20 11:29:01 +01:00
Stephen Diehl 9d450cd9ff Fixity declaration for <<*>>, fixes #103 2019-04-15 10:04:25 +01:00
Thomas ScholtesandStephen Diehl 7491e92f2a 'Protolude.Error.error' produces call stacks (#102)
Calling `Protolude.Error.error` will now include the call stack in the
`ErrorCall` that is raised. Before this was not the case because we used
the wrong implicit variable.
2019-04-10 15:38:14 +01:00
VilemandStephen Diehl 3add75ac4d Add lines, words, unlines, unwords (#101)
These operations (over `String` instead of `Text`) are part of Prelude.
2019-04-06 14:30:53 +01:00
Stephen Diehl 278b6c59e6 Bump licenses for 2019 2019-04-02 15:02:33 +01:00
Stephen Diehl ff8ed02b81 Support LTS-13.x resolvers 2019-03-28 16:58:36 +00:00
Alex PankoffandStephen Diehl cfa026a068 Remove note about error function from README (#100) 2019-03-18 10:38:04 +00:00
Greg SteuckandStephen Diehl a69d4ec1dd Typo (#98) 2019-01-24 13:30:54 +00:00
Stephen Diehl b45c3e6e73 New resolvers 2018-12-06 16:41:46 +00:00
Stephen Diehl b1f7eb5899 All LTS 1.2x resolvers 2018-11-21 12:39:16 +00:00
Stephen Diehl 8bf457d6b7 Remove symbols.md file 2018-11-08 14:12:58 +00:00
Stephen Diehl 8c3c133342 Fix for TypeInType before 8.6 2018-10-02 10:37:58 +01:00
Stephen Diehl ced5fd366e Minor version bump 2018-10-02 10:30:43 +01:00
Andre Van Der MerweandStephen Diehl 8c88bc7d64 GHC 8.6.1 support (#95)
* GHC 8.6.1 support

 - Bounds changes for base, containers and stm
 - stack lts 12.* tests
 - works with stack 8.6.1 nightly
    stack build --resolver nightly --haddock --test --bench --no-run-benchmarks
 - works with cabal new-build

* GHCVER-8.6.1
2018-10-01 20:20:06 +01:00
Stephen DiehlandGitHub 5024f3fef9 Add support for GHC 8.6 (#94)
* Add support for GHC 8.6

* Fix STM version bounds on pragma
2018-10-01 17:35:27 +01:00
Stephen Diehl 1b1f82b749 Export groupBy, fixes #91 2018-08-03 13:14:31 +01:00
Tony DayandStephen Diehl f75c99bda8 Some fixes for ghc-8.6 (#93) 2018-07-29 08:04:03 +01:00
Stephen Diehl 7c0e45c844 Support LTS-11 family of resolvers for testing 2018-06-15 10:29:42 +01:00
David HewsonandStephen Diehl ace10b648c <&> docs fix (#88)
`@<&>@`  appears as `&` on the page, which seems wrong
2018-06-10 09:20:15 +01:00
Michael SnoymanandStephen Diehl 8a8eb8c728 Add extra-source-files (#87) 2018-05-28 11:21:12 +01:00
Stephen Diehl 3bd6f07f9b Remove error function from symbol list 2018-05-08 09:39:55 +01:00
Stephen Diehl d6a710e40e Expose fromLeft and fromRight 2018-04-16 14:42:53 +01:00
Stephen Diehl 675837c232 Fix Data.Functor export list 2018-03-26 13:05:57 +01:00
Stephen Diehl 243a309bc1 On Base 4.11 use the provided <&>, fixes #53 2018-03-26 11:33:46 +01:00
Stephen Diehl 60943c0596 Drop safe dependency 2018-03-26 11:18:42 +01:00
Stephen DiehlandGitHub a8d1363581 Rework Safe Module (#83)
* Initial fork on minimal safe submodule

* Add new Safe module

Signed-off-by: Stephen Diehl <stephen.m.diehl@gmail.com>

* Refactor Safe module
2018-03-22 13:31:29 +00:00
Tony DayandStephen Diehl 57912017fe Bump upper bound for base & ghc84 bumps (#82) 2018-03-19 10:11:35 +00:00
Stephen Diehl f542a361a4 Bump upper bound of protolude transformers-compat 2018-03-01 14:21:01 +00:00
venandStephen Diehl 70fca4ab9c Fix symbols.md rendering (#80) 2018-02-08 13:55:00 +00:00
Stephen Diehl 81dcc985fc Bump minor version 2018-02-05 14:52:51 +00:00
Stephen Diehl 9a61a70a19 Looser lower-bound on Data.Kind export for GHC 8.0.x for #79 2018-02-05 14:33:12 +00:00
Stephen Diehl 8c9d0f4666 Bump upper bound for async 2.2.1 2018-02-05 10:20:40 +00:00
Stephen Diehl fd7fc5fb8e Update changelog 2018-01-12 11:11:09 +00:00
Stephen Diehl 65a0518008 Update changelog 2018-01-08 17:05:20 +00:00
Stephen Diehl 7f29497262 Minor version bump 2018-01-08 16:29:18 +00:00
Stephen Diehl 6d2a32f0dd Add transformers-compat for ancients MTL/Transformer versions 2018-01-08 16:28:14 +00:00
Stephen Diehl 74f185e22f Update stack build scripts 2018-01-07 13:03:56 +00:00
Stephen Diehl 8db43f6bff Bump upper bounds for safe 2018-01-07 09:04:20 +00:00
Ian JeffriesandStephen Diehl 4694fee383 Fix spelling. (#77) 2017-12-27 10:29:52 +00:00
Moritz KieferandStephen Diehl 2d8d1f3357 Reexport scanl' (#74)
This is useful for the same reasons that foldl' is useful
2017-12-18 19:03:45 +00:00
ProfpatschandStephen Diehl d462550f07 add mapExcept(T) and withExcept(T) (#76)
Since we already export functions like `withState` and the names don’t clash
with anything, we export these transformation functions as well.
Every function from Control.Monad.Except is now exported, the user doesn’t need
to import it for common error handling tasks.
2017-12-18 13:04:43 +00:00
Pi3randStephen Diehl c9a2d176d1 Add throwE & catchE (#73)
* Add throwE & catchE

My understanding is that `throwError` & `catchError` are deprecated (maybe we should remove them?)

* Update Protolude.hs

* Update Protolude.hs
2017-11-30 13:31:19 +00:00
TimothyandStephen Diehl 8eb1633dd8 Add warnings for partial functions (#70) 2017-10-24 14:27:18 +01:00
Stephen Diehl 2c44add128 Merge branch '4e6-ghc-822rc1' 2017-10-05 13:20:58 +01:00
Stephen Diehl 62393669dc update the travis file for 8.2.2 2017-10-05 13:20:47 +01:00
Dmitry Bushev abf2fbc369 Support GHC-8.2.2
When compiling with GHC 8.2.2-rc1:

```
src/Error.hs:25:17: error:
    Variable ‘r’ used as both a kind and a type
    Did you intend to use TypeInType?
   |
25 | error :: forall (r :: RuntimeRep) . forall (a :: TYPE r) . HasCallStack => Text -> a
   |                 ^^^^^^^^^^^^^^^^^
```

`TypeInType` should be enabled, see GHC issue 14121
https://ghc.haskell.org/trac/ghc/ticket/14121
2017-10-05 14:29:35 +03:00
Pi3randStephen Diehl 72085dec92 Add putErrLn to match putStrLn (#67)
* Add `putErrLn` to match `putStrLn`

Fix #66

* Add putErr for the most usual inference case

* Rename `putErr` into `putErrText`

In order to allow further specializations in the future if necessary.
2017-10-04 08:53:19 +01:00
Tony DayandStephen Diehl 923194c5b2 ghc-8.2 HasField, Type & * (#65) 2017-09-15 13:43:41 +01:00
Stephen Diehl 5964f545cf fix bifunctor import for base 4.8 2017-08-22 19:55:51 +01:00
Stephen DiehlandGitHub 8387808d6e Fix README with relative links 2017-08-22 19:26:57 +01:00
Stephen Diehl 2cd38fcb03 remove hidden modules 2017-08-22 19:25:27 +01:00
Stephen Diehl 9533e739ff use hierarchical namespace for all modules 2017-08-22 19:23:46 +01:00
Stephen Diehl 6c9b60700e expose debug module 2017-08-17 10:05:02 +01:00
Michael BurgeandStephen Diehl 7908bad846 Debug trace functions now output to stderr (#62) 2017-08-05 14:22:21 +01:00
Tony DayandStephen Diehl bfc7306ed7 Added RealFloat (#61) 2017-07-28 13:06:13 +01:00
Stephen Diehl 63d561de0c bump docs upper bounds for base for GHC 8.2 2017-07-25 13:03:07 +01:00
Pi3randStephen Diehl 3175e6c1eb Update protolude.cabal (#60)
Increase upperbound of safe.

This allows the master git version to build using the latest stackage`lts-8.23`
2017-07-24 17:54:42 +01:00
Stephen Diehl eccb07f726 polymorphic return type for die 2017-07-17 16:33:12 +01:00
Stephen Diehl 459d82012b Merge branch 'cocreature-ghc-8.2' 2017-07-05 14:42:40 +01:00
Stephen Diehl 5717b9a42c description and synopsis lines for latest cabal 2017-07-05 14:42:30 +01:00
Moritz Kiefer d7bc982142 Bump base upper bound for GHC 8.2 2017-07-05 08:02:57 +02:00
ProfpatschandStephen Diehl d30d6a39ab add (<&>), fixes issue #53 (#56) 2017-06-14 07:33:23 +02:00
Stephen DiehlandGitHub 46220a1a4a Added HasCallStack constraint for trace functions (#55)
* added HasCallStack constraint for trace functions, for issue #39

* unpack string on 7.6
2017-05-31 10:13:08 +01:00
Stephen Diehl c2570f77e6 better named either helper function, fixes #54 2017-05-20 19:03:29 +01:00
Stephen Diehl b4d33c3c96 include until from GHC.Base 2017-05-06 13:39:48 +01:00
Stephen Diehl 8c676e28de expose all pointers 2017-05-03 10:48:48 +01:00
Stephen Diehl a2c66fc236 update changelog 2017-05-03 10:44:31 +01:00
Stephen Diehl 456a063e35 update changelog 2017-05-03 10:37:39 +01:00
Stephen Diehl 269c81b1d2 make die take Text argument, fixes #51 2017-05-03 10:29:55 +01:00
Moritz KieferandStephen Diehl 307ecd7d6a Add hPutStr and hPutStrLn (#52) 2017-05-03 10:01:19 +01:00
Stephen Diehl 41710698ee note about unzip exposure 2017-03-24 10:14:49 +00:00
Stephen Diehl 8b9197ff01 ghc 8.0.2 supported 2017-03-17 13:09:12 +00:00
Stephen Diehl 89d4a49c7e export constructors for GHC.Generics sum/product 2017-03-17 13:07:24 +00:00
Stephen Diehl ca5087201b adds unzip, fixes issue #45 2017-03-12 15:54:30 +00:00
Stephen Diehl 8ad2522cda add strictness functions documentation 2017-03-11 09:59:58 +00:00
Stephen Diehl 5866877d3f reformat base exports of typelits 2017-03-11 09:54:30 +00:00
Pi3randStephen Diehl 92905843a4 Update CHANGES (#42) 2017-03-09 22:48:04 +00:00
Pi3randStephen Diehl a324e26d54 Update safe upper bound for ghc >=7.8.0 (#43) 2017-03-09 22:47:29 +00:00
Pi3randStephen Diehl 79bbc2de6e Add short-circuiting version of '<||>' and '<&&>' (#41) 2017-03-05 14:05:05 +00:00
Pi3randStephen Diehl 721374d42c Add (<&&>),(<||>) (#40) 2017-02-28 14:59:05 +00:00
Stephen Diehl 0ab61665ee liftIO1 and liftIO2 for issue #38 2017-02-11 14:52:56 +00:00
Stephen Diehl 6ac3e01da0 fix safe bounds for breakage on GHC 7.0.1 build 2017-02-11 13:47:31 +00:00
Alexander KjeldaasandStephen Diehl fd3d667cde Typo? (#37) 2017-02-07 14:12:47 +00:00
Stephen Diehl f9b5a5cbf4 export Floating 2017-01-21 14:53:04 +00:00
Carl BaatzandStephen Diehl 12cd9fae46 String is not exported. (#36) 2017-01-13 07:26:34 +00:00
Stephen Diehl 531a07ccfc test all stack resolvers 2017-01-03 15:50:05 +00:00
Stephen Diehl e72d06929e Hold off on exporting TypeInType machinery 2017-01-03 14:13:25 +00:00
Stephen Diehl 39d56792ce Fix up PBase exports, expose Generics metadata 2017-01-02 13:28:21 +00:00
Stephen Diehl 3994639b2b Mask supporting modules 2017-01-02 12:06:37 +00:00
Stephen Diehl 03f5746e96 Rework implicit exports to be explicit. 2017-01-02 11:46:11 +00:00
Stephen Diehl 070ac54067 Fix byteswap functions under GHC 7.6 2017-01-02 10:59:05 +00:00
Stephen Diehl e452604224 Fix Void and Proxy under GHC 7.6 2017-01-02 10:53:01 +00:00
Stephen Diehl e836d3f690 stack test harness 2017-01-02 10:45:43 +00:00
Ian JeffriesandStephen Diehl 51fdf38c71 Rename msg to fatalErrorMessage. (#34)
This is to free up `msg` as a variable name.
2017-01-01 18:45:08 -05:00
Stephen Diehl d0e81532af Use Data.Function (&) if base>4.8, fixes #33 2017-01-01 23:43:43 +00:00
Stephen Diehl 4a2ea2735b Merge branch 'docs'
Conflicts:
	CHANGES.md
2017-01-01 20:24:57 +00:00
Stephen Diehl 6d3940b4e5 new difflog 2017-01-01 20:24:32 +00:00
Stephen Diehl 254dafcb12 basic examples 2016-12-29 19:46:57 +00:00
Stephen Diehl 33e412b59b bump license 2016-12-26 17:49:43 +00:00
Stephen Diehl 5ce97cd0e9 expand list processing functions 2016-12-15 15:38:15 +00:00
Stephen Diehl 1140df0f91 stub out rest of toc 2016-12-15 15:06:15 +00:00
Stephen Diehl ed6fa9c8ea refine monaderror section 2016-12-14 17:16:20 +00:00
Stephen Diehl 1f2ed3e4cb fix lower bounds 2016-12-13 12:31:47 +00:00
Stephen Diehl cc0565112f monoid and fold rearranging 2016-12-13 09:14:39 +00:00
Stephen Diehl 0fb7730b0c expand semigroup 2016-12-12 16:30:52 +00:00
Stephen Diehl d6d1004070 monoid module 2016-12-12 16:21:49 +00:00
Stephen Diehl c7eeb5349e fix exports for functor module 2016-12-12 16:10:49 +00:00
Stephen Diehl 257a38f68f functor module 2016-12-12 13:09:18 +00:00
Stephen Diehl 66958def93 export encoding error handlers 2016-12-12 11:14:42 +00:00
Stephen Diehl c692eb03fb only export byteswap functions for ghc >7.6 2016-12-12 11:04:52 +00:00
Stephen Diehl a33669ca6d maybe functions 2016-12-12 11:03:34 +00:00
Stephen Diehl 6cb17bb94b utility functions 2016-12-10 16:01:47 +00:00
Stephen Diehl 00c2fce11a console functions 2016-12-10 14:51:33 +00:00
Stephen Diehl 8995aa71f3 bits and files 2016-12-10 13:28:32 +00:00
Stephen Diehl b32c80d0bb basic hierarchy in place 2016-12-10 12:46:48 +00:00
Stephen Diehl c28fa6b00c debug module 2016-12-08 13:12:42 +00:00
Stephen Diehl e041d6de8a added makefile 2016-12-08 12:23:56 +00:00
Stephen Diehl 7cdf6f1cc8 markdown source processor 2016-12-08 12:23:15 +00:00
Stephen Diehl fb6a942954 export generic list functions 2016-12-08 09:55:27 +00:00
Stephen Diehl ca74e2901d document structure 2016-12-08 09:47:06 +00:00
Stephen Diehl 60ca33fecb begin conversion to rst 2016-12-08 09:36:00 +00:00
Stephen Diehl fb6f00b90b clearer directions 2016-12-08 09:03:22 +00:00
Stephen Diehl dac4888f61 ignore ghc 8.0 only pragma 2016-12-06 13:57:48 +00:00
Stephen Diehl 50faedad80 added ghc 8.0.1 test 2016-12-06 09:39:23 +00:00
Stephen Diehl d5db73dbbf conditional safe version bounds for ghc 2016-12-06 09:06:47 +00:00
Stephen Diehl 6da0d42e8d updated changelog 2016-12-04 08:46:11 +00:00
Stephen Diehl 7060f55fdf export typelits symbols from base 2016-12-03 14:31:42 +00:00
Stephen Diehl 2b729aaadc Type level literals 2016-12-03 14:17:49 +00:00
Stephen Diehl fa482cfd46 bump version to 0.1.11 2016-11-15 16:18:43 +00:00
Stephen Diehl 3972d847e6 constrain safe upper bound for backwards compat with ghc 7.6 2016-11-15 15:14:16 +00:00
Stephen Diehl 82e0b04e4d use default monoid (<>) instead of semigroup 2016-11-15 14:57:16 +00:00
Stephen Diehl 7560af0955 relax lower async bound 2016-11-08 01:40:20 +00:00
Stephen Diehl 5b6bc27cbe constrainted bytestring print functions 2016-10-28 19:04:58 +01:00
Stephen Diehl aeacdd9630 include hashable 2016-10-28 16:41:28 +01:00
Stephen Diehl 250cfe2b7f version bump 2016-10-27 10:12:29 +01:00
Stephen Diehl c43a282322 Merge branch 'master' of github.com:sdiehl/protolude 2016-10-27 10:07:23 +01:00
Stephen Diehl d090411207 bump version 2016-10-27 10:05:52 +01:00
Stephen DiehlandGitHub 077198f1a5 Merge pull request #27 from cocreature/fixproduct
Fix product implementation
2016-10-18 15:59:15 +01:00
Moritz Kiefer bccd31e327 Fix product implementation 2016-10-18 15:13:01 +02:00
Stephen Diehl cf2fb7cd3a Note about strictness 2016-10-16 20:13:13 +01:00
Stephen DiehlandGitHub f95489b8f2 Merge pull request #26 from cocreature/sumproduct
Export strict versions of sum and product
2016-10-16 20:11:59 +01:00
Moritz Kiefer 961f71b971 Export strict versions of sum and product 2016-10-14 22:06:44 +02:00
Stephen Diehl 547aca5af4 fix nested functor composition 2016-10-14 17:21:57 +01:00
Stephen Diehl 860f74b3c2 Use explicit 'bool' import. 2016-10-14 14:06:57 +01:00
Stephen Diehl a86046b654 guarded and guardedA added to root. 2016-10-14 11:03:37 +01:00
Stephen Diehl c061df8f06 Applicative combinators. 2016-10-14 10:56:37 +01:00
Stephen Diehl a2e97dc936 Update FAQ. 2016-10-12 11:05:39 +01:00
Stephen Diehl e3e2857424 Changelog for 0.1.8. 2016-10-10 12:50:31 +01:00
Stephen Diehl 4f96a523da AMP fallout compatability fix. 2016-10-10 12:36:09 +01:00
Stephen Diehl 9e4f374811 Bump version. 2016-10-10 11:15:12 +01:00
Stephen Diehl 7a0e274b20 note and hush for #22 2016-10-10 11:13:26 +01:00
Stephen Diehl 2010cec78e Mask yield, fixes #23 2016-10-10 11:05:40 +01:00
Stephen Diehl 0346b366ee Remove Handler export, fixes #25. 2016-10-10 11:02:24 +01:00
Stephen Diehl ee06770742 `pass` function, fixes #25 . 2016-10-10 11:00:42 +01:00
Stephen Diehl a678ca3b60 Remove head test from travis. 2016-09-14 16:33:02 +01:00
Stephen Diehl 36b56ad3a2 for function for #19 2016-09-14 09:47:47 +01:00
Stephen Diehl e14e1e3613 Change log for 0.1.7. 2016-08-15 08:43:55 -04:00
Stephen Diehl d17fc9aab3 traceShow and traceShowId for #16 2016-08-13 16:14:15 -04:00
Stephen Diehl 5bf3b7f812 Full outline. 2016-07-29 12:28:29 -04:00
Stephen Diehl 5de8f47138 Stubs. 2016-07-29 12:07:32 -04:00
Stephen Diehl 4437583e3f Fix links. 2016-07-29 11:36:38 -04:00
Stephen Diehl 76f3f9f17c monad docs 2016-07-29 11:33:32 -04:00
Stephen Diehl 1cb0630529 New symbol documentation. 2016-07-21 14:49:09 -04:00
Stephen Diehl 7aa8b6646e Expose `state and `reader` functions. 2016-07-05 07:22:46 -04:00
Stephen Diehl 245dd5190e Explicitly lift throwIO and throwTo. 2016-07-02 20:11:20 -04:00
Stephen Diehl 2f619f3ebd Link to pretty printer library. 2016-07-02 17:57:44 -04:00
Stephen Diehl b658b4c8ef Note explicit String types. 2016-07-02 17:32:11 -04:00
Stephen Diehl b621258ce7 Better example text for panic. 2016-07-02 17:31:00 -04:00
Stephen Diehl 827c0ef87b Formatting for FAQ. 2016-07-02 17:27:40 -04:00
Stephen Diehl 6b6f8967ac Note about exception handling. 2016-07-02 14:00:26 -04:00
Stephen Diehl 21be7f2908 Fix 0.1.6 changelog. 2016-07-02 13:50:31 -04:00
Stephen Diehl f0fd11791c Add unsnoc function. 2016-07-01 11:25:10 -04:00
Stephen Diehl f6586ba8b4 Change safety language flags. 2016-06-30 23:02:39 -04:00
Stephen Diehl 63d5ed625d Encoding functions in scope by default. 2016-06-30 22:56:00 -04:00
Stephen Diehl c2ca1677c8 Control.Exception as qualified import. 2016-06-30 18:49:28 -04:00
Stephen Diehl b8479ebcb5 Move throw to Unsafe exports. 2016-06-30 18:48:55 -04:00
Stephen Diehl 89fdc587e0 Single SrcLoc export. 2016-06-30 18:28:21 -04:00
Stephen Diehl cf1fa981ef Remove showCallStack. 2016-06-30 18:14:30 -04:00
Stephen Diehl cef28ed3a0 Remove throw and assert exports. 2016-06-30 17:59:09 -04:00
Stephen Diehl 205643ea14 Callstack for GHC 8.x. 2016-06-30 17:45:39 -04:00
Stephen Diehl fa58c5452a Added changelog. 2016-06-30 11:14:47 -04:00
Stephen Diehl 22c0e743f9 Added changelog. 2016-06-30 11:09:58 -04:00
Stephen Diehl f4178f0472 FAQ section. 2016-06-30 11:04:51 -04:00
Stephen Diehl e2b886bb64 MOre specializers. 2016-06-30 10:59:15 -04:00
Stephen Diehl a7bc40452f Fix up bound documentation. 2016-06-30 10:49:13 -04:00
Stephen Diehl 0aa40b8b21 Don't support alternative Base. 2016-06-30 10:41:55 -04:00
Stephen Diehl dce9f0c9d5 github table formatting 2016-06-17 09:51:34 -04:00
Stephen Diehl b6805513f6 Dependencies note. 2016-06-17 09:49:49 -04:00
Stephen Diehl e0cace8a4d Symbols list. 2016-06-11 16:19:03 -04:00
Stephen Diehl 8d794da86c Remove printf. 2016-06-09 21:18:10 -04:00
Stephen Diehl 78b16ebcfa IO handle functions. 2016-06-09 20:18:55 -04:00
Stephen Diehl 173b86de45 Read actions for Strict text in scope by default. 2016-06-08 23:02:57 -04:00
Stephen Diehl 30d0bbca93 Debug functions to use Text. 2016-06-08 20:08:21 -04:00
Stephen Diehl a9c8d300a5 Remove string-conv dependency. 2016-06-08 19:47:08 -04:00
Stephen Diehl 6edc320b69 DeriveTyepable pragma. 2016-06-08 19:45:33 -04:00
Stephen Diehl 6ae19be4b7 Explicit Typeable for older GHC. 2016-06-08 19:38:45 -04:00
Stephen Diehl 6df421ad72 Panic module. 2016-06-08 19:32:03 -04:00
Stephen Diehl 1e690a9e78 bump version 2016-06-08 19:15:03 -04:00
Stephen Diehl f05079bf98 Add 8.0.1 support to Travis. 2016-06-08 19:05:43 -04:00
Stephen Diehl d39e014533 unsafeFromJust 2016-06-06 08:50:12 -04:00
Stephen Diehl 75f5f4d88f pull Conv into core 2016-05-09 14:22:13 -04:00
Stephen Diehl a70f6e2d84 bump version 2016-05-07 09:43:20 -04:00
Stephen Diehl 561ee5077e fix traceShow fixes #10 2016-04-27 15:27:42 -04:00
Stephen Diehl a9cc28f3b1 remove partial functions 2016-04-21 18:50:01 -04:00
Stephen Diehl 0b3090973e pulling semiring in module 2016-04-19 09:32:59 -04:00
Stephen Diehl fba329438f bump version 2016-04-19 09:13:06 -04:00
Stephen Diehl c0b161602d export generics 2016-04-18 18:09:50 -04:00
Stephen Diehl 2c83d2dbf6 data.records still in flux upstream 2016-04-18 14:02:58 -04:00
Stephen Diehl 5c8186f618 handle semigroup monoid 8.0 compat 2016-04-18 13:50:18 -04:00
Stephen Diehl aecd72fa5c ghc 8.0 record and typeintype prelude objects 2016-04-18 13:39:36 -04:00
Stephen Diehl 8756b6507b unbreak functor/monad issue in old ghc vers 2016-04-18 11:17:02 -04:00
Stephen Diehl 4b3caa5c18 bring 7.10 type-level equality into scope 2016-04-18 11:09:03 -04:00
Stephen Diehl da7da2fe75 mask fail function 2016-04-18 10:51:02 -04:00
Stephen Diehl e442b31203 polymorphic show func 2016-04-18 10:35:34 -04:00
Stephen Diehl ed6d680c70 bump version 2016-04-16 11:08:30 -04:00
Stephen Diehl ea709c2945 Squashed commit of the following:
commit 416dc35d5a6ead5ce62063b56539d899e91bfd36
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Thu Apr 14 14:00:23 2016 -0400

    fix readme formatting

commit 8bf3029d01
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 18:39:39 2016 -0400

    design points in readme

commit 76bc617db9
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 17:09:10 2016 -0400

    functor include

commit cbaa117b8a
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 16:39:47 2016 -0400

    brenden's list function

commit ef52c8d507
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 16:36:42 2016 -0400

    gracefully handle 7.10 modules if present

commit f6fca1059d
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 16:28:37 2016 -0400

    not worth backporting void

commit 26d679e257
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 16:12:55 2016 -0400

    explicit base types, IsString export, Proxy

commit a14ad9bdd6
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 15:20:15 2016 -0400

    all non-partial list functions
2016-04-14 14:02:20 -04:00
Stephen Diehl e7debe1a2c Merge pull request #7 from sdiehl/list-extras
all non-partial list functions
2016-04-13 18:56:58 -04:00
Stephen Diehl 8bf3029d01 design points in readme 2016-04-13 18:39:39 -04:00
Stephen Diehl 76bc617db9 functor include 2016-04-13 17:09:10 -04:00
Stephen Diehl cbaa117b8a brenden's list function 2016-04-13 16:39:47 -04:00
Stephen Diehl ef52c8d507 gracefully handle 7.10 modules if present 2016-04-13 16:36:42 -04:00
Stephen Diehl f6fca1059d not worth backporting void 2016-04-13 16:28:37 -04:00
Stephen Diehl 26d679e257 explicit base types, IsString export, Proxy 2016-04-13 16:12:55 -04:00
Stephen Diehl a14ad9bdd6 all non-partial list functions 2016-04-13 15:20:15 -04:00
Stephen Diehl 5388b8cd4b Squashed commit of the following:
commit a6f62265f345c8b7a7226515171752586b0d66e5
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 13:41:05 2016 -0400

    bump version

commit 1a809623cd1484ce215612e6b3ecef15015bc602
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 13:26:11 2016 -0400

    masking for ghc 8.0 specific type

commit 54bafb0bb80c8450863eac53a1f3a20a21c8b0c9
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 12:55:08 2016 -0400

    bump ghc-prim for GHC 8.0 compat

commit 8808d5f37369107b3e5961255089a8a9e02bf3d5
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 12:53:39 2016 -0400

    expose base State transformers

commit 05c32a4542aa66f715aea3855e140f74e97d4852
Author: Stephen Diehl <stephen.m.diehl@gmail.com>
Date:   Wed Apr 13 12:46:00 2016 -0400

    adds compiler-independent base module
2016-04-13 13:51:47 -04:00
Stephen Diehl 99427e741c Merge pull request #5 from mitchellwrosen/master
Fix some re-exports
2016-04-13 11:42:26 -04:00
Mitchell Rosen 33d029c1b0 Fix some re-exports 2016-04-13 11:03:23 -04:00
Stephen Diehl 949d62cc12 qualified import of ByteString fixes #4 2016-04-12 09:43:59 -04:00
Stephen Diehl ef5b47b4f7 expose seq and asTypeOf 2016-04-09 20:43:26 -04:00
Stephen Diehl 26c148510e expose rest of safe functions 2016-04-09 20:17:55 -04:00
Stephen Diehl 1a6ce9436f include either module 2016-04-09 20:02:11 -04:00
Stephen Diehl 1f0a5a9576 backport bifunctor 2016-04-09 19:57:02 -04:00
Stephen Diehl 806939605a better readme 2016-04-09 19:35:23 -04:00
Stephen Diehl bd5f8f1b23 fix backwards compart for ($!) 2016-04-09 19:25:55 -04:00
Stephen Diehl 2c47ec7262 selectively expose more of ghc.base 2016-04-09 15:53:25 -04:00
Stephen Diehl cff538114d fix data.functor exports 2016-04-09 15:34:30 -04:00
Stephen Diehl 1c8fc29c4a bring flip into scope 2016-04-09 15:18:30 -04:00
Stephen Diehl 22fda7cedc added enum class 2016-04-09 15:16:56 -04:00
Stephen Diehl b3674708d4 functor module 2016-04-09 15:14:21 -04:00
Stephen Diehl 7a62c5d489 functor module 2016-04-09 15:13:13 -04:00
Stephen Diehl 959cd272f7 fix typo in fun name 2016-04-09 14:56:55 -04:00
Stephen Diehl 8cd2b4ffd3 build status 2016-04-09 14:50:45 -04:00
Stephen Diehl d5bdaf0c88 use in module traceM 2016-04-09 14:49:16 -04:00
Stephen Diehl 8f6e97633c backwards compat for traceM 2016-04-09 14:25:20 -04:00
Stephen Diehl 4bd0ed3e83 modern ghc only 2016-04-09 13:20:05 -04:00
Stephen Diehl 6ede03c8ce travis build 2016-04-09 13:07:45 -04:00
Stephen Diehl dd6a97adfa hackage package 2016-04-08 11:25:58 -04:00
Stephen Diehl ccf89378f8 fix import formatting 2016-04-07 17:33:20 -04:00
Stephen Diehl 8417d2a031 fix gitignore 2016-04-07 17:14:57 -04:00
Stephen Diehl 28dad7fca1 fix warnings 2016-04-07 17:14:42 -04:00
Stephen Diehl e94c364d65 readme 2016-04-07 15:34:56 -04:00
Stephen Diehl 29b854cf98 initial commit 2016-04-07 15:22:44 -04:00
218 changed files with 7312 additions and 2491 deletions
+2
View File
@@ -0,0 +1,2 @@
# Ignore blame for commit that moved protolude files under src/protolude
d4949c633e8172d0e4dd8f5c991eaaae6b48fbb0
-5
View File
@@ -1,5 +0,0 @@
# TODO: Remove this once a new actionlint release has been cut
# and made its way to us through nixpkgs.
self-hosted-runner:
labels:
- ubuntu-24.04-arm
@@ -112,7 +112,7 @@ runs:
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
- name: Save artifact to GitHub Actions
if: steps.find-task.outputs.task_found
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.upload }}
path: ${{ steps.download.outputs.artifacts }}
+6 -7
View File
@@ -8,7 +8,6 @@ inputs:
required: true
save-prs:
description: Whether to additionally store the cache in a pull request, too. Should only be used for very small caches.
type: boolean
prefix:
description: Cache key prefix to be used in both primary key and restore-keys.
required: true
@@ -19,17 +18,17 @@ inputs:
runs:
using: composite
steps:
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-${{ inputs.suffix }}
restore-keys: |
${{ runner.os }}-${{ inputs.prefix }}-
- uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-
- uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-${{ inputs.suffix }}
restore-keys: |
${{ runner.os }}-${{ inputs.prefix }}-
${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-
+1 -1
View File
@@ -16,7 +16,7 @@ runs:
nix_conf: |-
always-allow-substitutes = true
max-jobs = auto
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
- uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17
with:
name: postgrest
authToken: ${{ inputs.authToken }}
+3
View File
@@ -4,6 +4,9 @@ codecov:
comment: false
github_checks:
annotations: true
coverage:
status:
project:
-41
View File
@@ -13,9 +13,6 @@
},
"packageRules": [
{
"matchBaseBranches": [
"/^v[0-9]+/"
],
"matchManagers": [
"haskell-cabal"
],
@@ -26,44 +23,6 @@
"/^v[0-9]+/"
],
"groupName": "all dependencies"
},
{
"matchManagers": [
"haskell-cabal"
],
"matchPackageNames": [
"base",
"bytestring",
"containers",
"directory",
"mtl",
"parsec",
"process",
"text"
],
"groupName": "GHC dependencies"
},
{
"matchManagers": [
"haskell-cabal"
],
"matchPackageNames": [
"hasql",
"hasql-dynamic-statements",
"hasql-notifications",
"hasql-transaction",
"hasql-pool"
],
"groupName": "hasql"
},
{
"matchManagers": [
"haskell-cabal"
],
"matchPackageNames": [
"fuzzyset"
],
"allowedVersions": "<0.3"
}
]
}
+3 -3
View File
@@ -28,7 +28,7 @@ jobs:
# This actions creates the github token using the postgrest app secrets
- name: Create Github App Token
id: app-token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
app-id: ${{ vars.POSTGREST_CI_APP_ID }}
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
@@ -38,14 +38,14 @@ jobs:
# This is required for backport action to cherry-pick the PR
- name: Fetch PR ref
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
token: ${{ steps.app-token.outputs.token }}
# Backport action that creates the PR with given settings
- name: Create backport PR
uses: korthout/backport-action@d07416681cab29bf2661702f925f020aaa962997 # v3.4.1
uses: korthout/backport-action@7c3f6cd5843cac11bc59a04a1b7699af93261670 # v4.5
with:
github_token: ${{ steps.app-token.outputs.token }}
pull_description: 'Backport for #${pull_number}.'
+37 -40
View File
@@ -16,6 +16,7 @@ on:
- .github/*
- '*.nix'
- nix/**
- flake.lock
- .cirrus.yml
- cabal.project*
- postgrest.cabal
@@ -30,10 +31,20 @@ concurrency:
jobs:
static:
name: Nix - Linux x86-64 static
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- name: Linux aarch64
runs-on: ubuntu-24.04-arm
artifact: aarch64
- name: Linux x86-64
runs-on: ubuntu-24.04
artifact: x86-64
name: Nix - ${{ matrix.name }} static
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -42,19 +53,19 @@ jobs:
- name: Build static executable
run: nix-build -A postgrestStatic -A postgrestStatic.tests
- name: Save built executable as artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: postgrest-linux-static-x86-64
name: postgrest-linux-static-${{ matrix.artifact }}
path: result/bin/postgrest
if-no-files-found: error
- name: Build Docker image
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
run: nix-build -A docker.image --out-link postgrest-docker-${{ matrix.artifact }}.tar.gz
- name: Save built Docker image as artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: postgrest-docker-x86-64
path: postgrest-docker.tar.gz
name: postgrest-docker-${{ matrix.artifact }}
path: postgrest-docker-${{ matrix.artifact }}.tar.gz
if-no-files-found: error
@@ -62,24 +73,19 @@ jobs:
name: Nix - MacOS
runs-on: macos-15
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Install gnu sed
run: brew install gnu-sed
- name: Install nix-build-uncached
run: nix-env -f default.nix -iA nix-build-uncached
- name: Build everything
run: |
# The --dry-run will give us a list of derivations to download from cachix and
# derivations to build. We only take those that would have to be built and then build
# those explicitly. This has the advantage that pure verification will not include
# a download anymore, making it much faster. If something needs to be built, only
# the dependencies required to do so will be downloaded, but not everything.
nix-build --dry-run 2>&1 \
| gsed -e '1,/derivations will be built:$/d' -e '/paths will be fetched/Q' \
| xargs nix-build
- name: Build everything (default.nix)
run: nix-build-uncached
- name: Build everything (shell.nix)
run: nix-build-uncached shell.nix
stack:
@@ -87,15 +93,6 @@ jobs:
fail-fast: false
matrix:
include:
- name: Linux aarch64
runs-on: ubuntu-24.04-arm
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-ubuntu-aarch64
deps: sudo apt-get update && sudo apt-get install libpq-dev
- name: MacOS aarch64
runs-on: macos-14
cache: |
@@ -106,7 +103,7 @@ jobs:
deps: brew link --force libpq
- name: MacOS x86-64
runs-on: macos-13
runs-on: macos-15-intel
cache: |
~/.stack/pantry
~/.stack/snapshots
@@ -126,11 +123,11 @@ jobs:
name: Stack - ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: haskell-actions/setup@82e8b5066385702e477d9dc98f287070493e8abd # v2.8.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
with:
# This must match the version in stack.yaml's resolver
ghc-version: 9.6.7
ghc-version: 9.10.3
enable-stack: true
stack-no-global: true
stack-setup-ghc: true
@@ -155,7 +152,7 @@ jobs:
- name: Strip Executable
run: strip result/postgrest*
- name: Save built executable as artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact }}
path: |
@@ -168,7 +165,7 @@ jobs:
name: Stack - FreeBSD from CirrusCI
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/artifact-from-cirrus
with:
token: ${{ github.token }}
@@ -180,13 +177,13 @@ jobs:
cabal:
strategy:
matrix:
ghc: ['9.6.7', '9.8.4']
ghc: ['9.10.3', '9.12.3']
fail-fast: false
name: Cabal - Linux x86-64 - GHC ${{ matrix.ghc }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: haskell-actions/setup@82e8b5066385702e477d9dc98f287070493e8abd # v2.8.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
with:
ghc-version: ${{ matrix.ghc }}
- name: Cache .cabal
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
name: Lint & Style
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -36,7 +36,7 @@ jobs:
name: Commit
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 100 # fetch history (last 100 commits) instead of default shallow clone history, this is deemed enough for a PR history
- name: Setup Nix Environment
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
- test
- build
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
- name: Tag latest commit
+3 -2
View File
@@ -14,6 +14,7 @@ on:
- .github/actions/setup-nix/**
- default.nix
- nix/**
- flake.lock
- docs/**
- '!**.md'
@@ -27,7 +28,7 @@ jobs:
name: Build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -41,7 +42,7 @@ jobs:
name: Spellcheck
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
linkcheck:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
+32 -40
View File
@@ -26,7 +26,7 @@ jobs:
needs:
- build
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check the version to be released
run: |
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
@@ -49,7 +49,7 @@ jobs:
echo "Relevant extract from CHANGELOG.md:"
cat CHANGES.md
- name: Save CHANGES.md as artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-changes
path: CHANGES.md
@@ -64,9 +64,9 @@ jobs:
needs:
- prepare
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download all artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
- name: Create release bundle with archives for all builds
@@ -75,6 +75,9 @@ jobs:
mkdir -p release-bundle
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-aarch64.tar.xz" \
-C artifacts/postgrest-linux-static-aarch64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x86-64.tar.xz" \
-C artifacts/postgrest-linux-static-x86-64 postgrest
@@ -87,14 +90,11 @@ jobs:
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
-C artifacts/postgrest-freebsd-x86-64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
-C artifacts/postgrest-ubuntu-aarch64 postgrest
zip --junk-paths "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x86-64.zip" \
artifacts/postgrest-windows-x86-64/postgrest.exe
- name: Save release bundle
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-bundle
path: release-bundle
@@ -138,50 +138,42 @@ jobs:
env:
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download aarch64 Docker image
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: postgrest-docker-aarch64
- name: Download x86-64 Docker image
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: postgrest-docker-x86-64
- name: Download aarch64 binary
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: postgrest-ubuntu-aarch64
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build aarch64 Docker image
run: |
# This only pushes the image via digest, not a tag. This will not appear
# in the image list on Docker Hub, yet. It will be later added to the main
# tag's manifest.
docker buildx build \
-t "$DOCKER_REPO/postgrest" \
--platform linux/arm64 \
--output push-by-digest=true,type=image,push=true \
--metadata-file metadata.json \
.
echo "SHA256_ARM=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_ENV"
- name: Publish images on Docker Hub
run: |
docker load -i postgrest-docker.tar.gz
docker load -i postgrest-docker-aarch64.tar.gz
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64"
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64"
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
docker buildx imagetools create --append \
-t "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
"$DOCKER_REPO/postgrest@$SHA256_ARM"
docker load -i postgrest-docker-x86-64.tar.gz
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
docker manifest create "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64" \
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
docker manifest push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
# Only tag 'latest' for full releases
if [ "${GITHUB_REF_NAME}" != "devel" ]; then
echo "Pushing to 'latest' tag for full release of ${GITHUB_REF_NAME} ..."
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
docker push "$DOCKER_REPO"/postgrest:latest
docker buildx imagetools create --append \
-t "$DOCKER_REPO/postgrest:latest" \
"$DOCKER_REPO/postgrest@$SHA256_ARM"
docker manifest create "$DOCKER_REPO/postgrest:latest" \
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64" \
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
docker manifest push "$DOCKER_REPO/postgrest:latest"
else
echo "Skipping push to 'latest' tag for pre-release..."
fi
@@ -194,7 +186,7 @@ jobs:
vars.DOCKER_REPO && vars.DOCKER_USER &&
github.ref == 'refs/tags/devel'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
with:
username: ${{ vars.DOCKER_USER }}
+19 -14
View File
@@ -17,6 +17,7 @@ on:
- .github/actions/setup-nix/**
- default.nix
- nix/**
- flake.lock
- .stylish-haskell.yaml
- cabal.project
- postgrest.cabal
@@ -39,7 +40,7 @@ jobs:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -48,10 +49,10 @@ jobs:
- run: postgrest-cabal-update
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
- name: Run coverage (IO tests and Spec tests against latest supported PostgreSQL)
run: postgrest-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: ./coverage/codecov.json
token: ${{ secrets.CODECOV_TOKEN }}
@@ -69,7 +70,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pgVersion: [13, 14, 15, 16, 17]
pgVersion: [14, 15, 16, 17, 18]
name: PG ${{ matrix.pgVersion }}
runs-on: ubuntu-24.04
defaults:
@@ -78,33 +79,37 @@ jobs:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.testSpec.bin tests.testIO.bin tests.testBigSchema.bin withTools.postgresql-${{ matrix.pgVersion }}.bin cabalTools.update.bin
tools: tests.testSpec.bin tests.testObservability.bin tests.testIO.bin tests.testBigSchema.bin withTools.pg-${{ matrix.pgVersion }}.bin cabalTools.update.bin
- run: postgrest-cabal-update
- name: Run spec tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-spec
- name: Run observability tests
if: always()
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-observability
- name: Run IO tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io -vv
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-io -vv
- name: Run IO tests on a big schema
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
memory:
name: Memory
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -120,11 +125,11 @@ jobs:
loadtest:
strategy:
matrix:
kind: ['mixed', 'jwt-hs', 'jwt-hs-cache', 'jwt-hs-cache-worst', 'jwt-rsa', 'jwt-rsa-cache', 'jwt-rsa-cache-worst']
kind: ['mixed', 'errors', 'jwt-hs', 'jwt-hs-cache', 'jwt-hs-cache-worst', 'jwt-rsa', 'jwt-rsa-cache', 'jwt-rsa-cache-worst']
name: Loadtest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Nix Environment
@@ -152,14 +157,14 @@ jobs:
fail-fast: false
matrix:
runs-on:
- macos-13 # x86_64-darwin
- macos-15-intel # x86_64-darwin
- macos-14 # aarch64-darwin
- ubuntu-24.04 # x86_64-linux
- ubuntu-24.04-arm # aarch64-linux
name: Flake Check
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Nix Environment
+1
View File
@@ -26,3 +26,4 @@ loadtest
.docs-build
gen_targets.http
gen_jwk.json
gen_private.json
+1 -1
View File
@@ -7,4 +7,4 @@ python:
build:
os: ubuntu-24.04
tools:
python: "3.11"
python: "3.12"
+13 -8
View File
@@ -13,26 +13,26 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
</a>
</td>
<td align="center" valign="middle">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/neon.jpg">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.svg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/tembo.png">
<a href="https://www.euronodes.com/postgrest" target="_blank">
<img width="296px" src="static/euronodes.svg">
</a>
</td>
</tr>
<tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://www.euronodes.com/postgrest" target="_blank">
<img width="296px" src="static/euronodes.svg">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/neon.jpg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.png">
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/bytebase.svg">
</a>
</td>
</tr>
@@ -93,6 +93,11 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
<img width="222px" src="static/code-build.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/tembo.png">
</a>
</td>
</tr>
</tbody>
</table>
+106 -2
View File
@@ -4,6 +4,110 @@ All notable changes to this project will be documented in this file. From versio
## Unreleased
### Added
- Log error when `db-schemas` config contains schema `pg_catalog` or `information_schema` by @taimoorzaeem in #4359
- Add string slicing operator for `jwt-role-claim-key` by @taimoorzaeem in #4599
- Optimize requests with `Prefer: count=exact` that do not use ranges or `db-max-rows` by @laurenceisla in #3957
+ Removed unnecessary double count when building the `Content-Range`.
- Add config `client-error-verbosity` to customize error verbosity by @taimoorzaeem in #4088, #3980, #3824
- Add `Vary` header to responses by @develop7 in #4609
- Add config `db-timezone-enabled` for optional querying of timezones by @taimoorzaeem in #4751
- Log schema cache queries timings on `log-level=debug` by @steve-chavez in #4805
### Fixed
- Shutdown should wait for in flight requests by @mkleczek in #4702
- Fix login with uppercase and mixed case role names by @taimoorzaeem in #4678
- Remove automatic transaction retries on `40001 (serialization_failure)` errors to prevent replication lag by @laurenceisla in #3673
- Fix unexpected results when embedding and filtering the same table more than once by @laurenceisla in #4075
### Changed
- Drop support for PostgreSQL EOL version 13 by @wolfgangwalther in #4193
- All responses now include a `Vary` header by @develop7 in #4609
- Log error when `db-schemas` config contains schema `pg_catalog` or `information_schema` by @taimoorzaeem in #4359
+ Now fails at startup. Prior to this, it failed with `PGRST205` on requests related to these schemas.
- Build a static executable for aarch64-linux by @wolfgangwalther in #4193
- Build the minimal docker image for aarch64-linux by @wolfgangwalther in #4193
- The name of an embedded table can no longer be used in filters if it has an alias by @laurenceisla in #4075
+ e.g. `?select=alias:table(*)&table.id=eq.1` is not possible anymore, use `?select=alias:table(*)&alias.id=eq.1` instead.
## [14.10] - 2026-04-16
### Added
- Log when the pool is released during schema cache reload on `log-level=debug` by @mkleczek in #4668
### Fixed
- Fix unnecessary connection pool flushes during schema cache reloading by @mkleczek in #4645
## [14.9] - 2026-04-10
### Added
- Log host, port and pg version of listener database connection by @mkleczek in #4617 #4618
### Fixed
- Remove red herring warp logs on default log-level, only emit them on `log-level=debug` by @steve-chavez in #4799
## [14.8] - 2026-04-03
### Added
- Log a `HINT` when the LISTEN channel stops working due to a PostgreSQL bug by @laurenceisla in #4581
### Fixed
- Fix invalid OpenAPI 2.0 format for integer types (`smallint`, `integer`, `bigint`) by @arturbent0 in #4641
## [14.7] - 2026-03-20
### Fixed
- Fix not logging SIGTERM and SIGINT by @steve-chavez in #4728
## [14.6] - 2026-03-06
### Fixed
- Fix leaking table and function names when calculating error hint by @taimoorzaeem in #4675
## [14.5] - 2026-02-12
### Fixed
- Don't hide async exceptions in logs by @stevechavez in #4646
## [14.4] - 2026-01-29
### Fixed
- Ensure Listener connections are released by @mkleczek in #4614
- Fix incorrectly filtering the returned representation for PATCH requests when using `or/and` filters by @laurenceisla in #3707
- Fix listener running with exception masked after first failure by @mkleczek in #4615
## [14.3] - 2026-01-03
### Fixed
- Fix performance and high memory usage of relation hint calculation by @mkleczek in #4462, #4463
## [14.2] - 2025-12-18
### Fixed
- Fix `hasSingleUnnamedParam` incorrectly matching functions with named parameters by @joelonsql in #4553
+ Functions with a single named parameter (e.g., `foo(data json)`) no longer incorrectly match the single-param fallback, returning a clean `PGRST202` error instead of a confusing PostgreSQL `42883` error.
- Fix misleading logs on unsupported PostgreSQL versions by @taimoorzaeem in #4519
- Fix regression where the `PGRST103` error response was truncated by @laurenceisla in #4455
+ Happened when an `offset` was greater than the rows requested and `Prefer: count=exact` was sent.
- Fix not returning `Content-Length` on empty HTTP `201` responses by @laurenceisla in #4518
- Fix inaccurate Server-Timing header durations by @steve-chavez in #4522
- Fix inaccurate "Schema cache queried" logs by @steve-chavez in #4522
## [14.1] - 2025-11-05
## Fixed
@@ -587,7 +691,7 @@ All notable changes to this project will be documented in this file. From versio
### Added
- #1933, #2109, Add a minimal health check endpoint - @steve-chavez
+ For enabling this, the `admin-server-port` config must be set explictly
+ For enabling this, the `admin-server-port` config must be set explicitly
+ A `<host>:<admin_server_port>/live` endpoint is available for checking if postgrest is running on its port/socket. 200 OK = alive, 503 = dead.
+ A `<host>:<admin_server_port>/ready` endpoint is available for checking a correct internal state(the database connection plus the schema cache). 200 OK = ready, 503 = not ready.
- #1988, Add the current user to the request log on stdout - @DavidLindbom, @wolfgangwalther
@@ -1070,7 +1174,7 @@ All notable changes to this project will be documented in this file. From versio
- Customize content negotiation per route - @begriffs
- Allow using nulls order without explicit order direction - @steve-chavez
- Fatal error on postgres unsupported version, format supported version in error message - @steve-chavez
- Prevent database memory cosumption by prepared statements caches - @ruslantalpa
- Prevent database memory consumption by prepared statements caches - @ruslantalpa
- Use specific columns in the RETURNING section - @ruslantalpa
- Fix columns alias for RETURNING - @steve-chavez
+21 -14
View File
@@ -1,17 +1,12 @@
# Contributing to PostgREST
**First:** if you're unsure or afraid of _anything_, just ask or
submit the issue or pull request anyways. You won't be yelled at
for giving your best effort. The worst that can happen is that
you'll be politely asked to change something. We appreciate any
sort of contributions, and don't want a wall of rules to get in the
way of that.
## AI Policy
However, for those individuals who want a bit more guidance on the
best way to contribute to the project, read on. This document will
cover what we're looking for. By addressing all the points we're
looking for, it raises the chances we can quickly merge or address
your contributions.
We adhere to [Gentoo's AI policy](https://wiki.gentoo.org/wiki/Project:Council/AI_policy):
> It is expressly forbidden to contribute [...] any content that has been created with the assistance of Natural Language Processing artificial intelligence tools. This motion can be revisited, should a case been made over such a tool that does not pose copyright, ethical and quality concerns.
You can find more about its rationale [here](https://wiki.gentoo.org/wiki/Project:Council/AI_policy#Rationale).
## Issues
@@ -40,12 +35,12 @@ For questions on how to use PostgREST, please use
We have a fully nix-based development environment with many tools for a smooth development workflow available.
Check the [development docs](https://github.com/PostgREST/postgrest/blob/main/nix/README.md) on how to set it up and use it.
### Haskell Conventions
* All contributions must pass the tests before being merged. When
you create a pull request your code will automatically be tested.
* All code must also pass [hlint](http://community.haskell.org/~ndm/hlint/) and [stylish-haskell](https://github.com/jaspervdj/stylish-haskell)
* All fixes or features must have a test proving the improvement.
* All code must also pass a [linter](http://community.haskell.org/~ndm/hlint/) and [styler](https://github.com/jaspervdj/stylish-haskell)
with no warnings. This helps enforce a uniform style for all committers. Continuous integration will check this as well on every
pull request. There are useful tools in the nix-shell that help with checking this locally. You can run `postgrest-check` to do this manually but
we recommend adding it to `.git/hooks/pre-commit` as `nix-shell --run postgrest-check` to automatically check this before doing a commit.
@@ -53,3 +48,15 @@ Check the [development docs](https://github.com/PostgREST/postgrest/blob/main/ni
### Running Tests
For instructions on running tests, see the [development docs](https://github.com/PostgREST/postgrest/blob/main/nix/README.md#testing).
### Structuring commits in pull requests
To simplify reviews, make it easy to split pull requests if deemed necessary, and to maintain clean and meaningful history of changes, you will be asked to update your PR if it does not follow the below rules:
* It must be possible to merge the PR branch into target using `git merge --ff-only`, ie. the source branch must be rebased on top of target.
* No merge commits in the source branch.
* All commits in the source branch must be self contained, meaning: it should be possible to treat each commit as a separate PR.
* Commits in the source branch must contain only related changes (related means the changes target a single problem/goal). For example, any refactorings should be isolated from the actual change implementation into separate commits.
* Tests, documentation, and changelog updates should be contained in the same commits as the actual code changes they relate to. An exception to this rule is when test or documentation changes are made in separate PR.
* Commit messages must be prefixed with one of the prefixes defined in [the list used by commit verification scripts](https://github.com/PostgREST/postgrest/blob/main/nix/tools/gitTools.nix#L11).
* Commit messages should contain a longer description of the purpose of the changes contained in the commit and, for non-trivial changes, a description of the changes themselves.
-21
View File
@@ -1,21 +0,0 @@
# PostgREST Docker Hub image for aarch64.
# The x86-64 is a single-static-binary image built via Nix, see:
# nix/tools/docker/README.md
FROM ubuntu:noble@sha256:66460d557b25769b102175144d538d88219c077c678a49af4afca6fbfc1b5252 AS postgrest
RUN apt-get update -y \
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY postgrest /usr/bin/postgrest
RUN chmod +x /usr/bin/postgrest
EXPOSE 3000
USER 1000
# Use the array form to avoid running the command using bash, which does not handle `SIGTERM` properly.
# See https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop
CMD ["postgrest"]
+1 -2
View File
@@ -1,5 +1,4 @@
Copyright (c) 2014 Joe Nelson
Copyright (c) 2019 Steve Chavez
Copyright (c) 2014-2026 The PostgREST contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
+9 -9
View File
@@ -22,26 +22,26 @@ API than you are likely to write from scratch.
</a>
</td>
<td align="center" valign="middle">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/neon.jpg">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.svg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/tembo.png">
<a href="https://www.euronodes.com/postgrest" target="_blank">
<img width="296px" src="static/euronodes.svg">
</a>
</td>
</tr>
<tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://www.euronodes.com/postgrest" target="_blank">
<img width="296px" src="static/euronodes.svg">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/neon.jpg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.png">
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/bytebase.svg">
</a>
</td>
</tr>
@@ -125,7 +125,7 @@ and limited with - range headers. More about
## Data Integrity
Rather than relying on an Object Relational Mapper and custom
imperative coding, this system requires you put declarative constraints
imperative coding, this system requires you to put declarative constraints
directly into your database. Hence no application can corrupt your
data (including your API server).
+2
View File
@@ -1,2 +1,4 @@
packages: postgrest.cabal
tests: true
allow-newer:
hasql:postgresql-libpq
+1 -1
View File
@@ -1 +1 @@
index-state: hackage.haskell.org 2025-10-29T04:02:18Z
index-state: hackage.haskell.org 2026-04-18T18:42:36Z
+9 -7
View File
@@ -1,6 +1,6 @@
{ system ? builtins.currentSystem
, compiler ? "ghc948"
, compiler ? "ghc9123"
, # Commit of the Nixpkgs repository that we want to use.
# It defaults to reading the inputs from flake.lock, which serves
@@ -44,7 +44,6 @@ let
allOverlays.checked-shell-script
allOverlays.gitignore
(allOverlays.haskell-packages { inherit compiler; })
allOverlays.slocat
];
# Evaluated expression of the Nixpkgs repository.
@@ -53,11 +52,11 @@ let
postgresqlVersions =
[
{ name = "postgresql-17"; postgresql = pkgs.postgresql_17.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-16"; postgresql = pkgs.postgresql_16.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "pg-18"; postgresql = pkgs.postgresql_18.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "pg-17"; postgresql = pkgs.postgresql_17.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "pg-16"; postgresql = pkgs.postgresql_16.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "pg-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "pg-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
];
haskellPackages = pkgs.haskell.packages."${compiler}";
@@ -108,6 +107,9 @@ rec {
inherit (pkgs.haskell.packages."${compiler}") ghcWithPackages;
};
# Used by CI on MacOS
inherit (pkgs) nix-build-uncached;
### Tools
cabalTools =
+8 -10
View File
@@ -19,26 +19,26 @@ write from scratch.
</a>
</td>
<td align="center" valign="middle">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/neon.jpg">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.svg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/tembo.png">
<a href="https://www.euronodes.com/postgrest" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/euronodes.svg">
</a>
</td>
</tr>
<tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://www.euronodes.com/postgrest" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/euronodes.svg">
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/neon.jpg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.png">
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/bytebase.svg">
</a>
</td>
</tr>
@@ -61,5 +61,3 @@ The image is built from scratch using
no commands are listed in the image history. See the [PostgREST
repository](https://github.com/PostgREST/postgrest/tree/main/nix/tools/docker) for
details on the build process and how to inspect the image.
This does not apply to the arm64 variant, which is based on Ubuntu.
+4 -3
View File
@@ -48,14 +48,14 @@ source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# This is overriden by readthedocs with the version tag anyway
version = "14"
# This is overridden by readthedocs with the version tag anyway
version = "devel"
# To avoid repetition in <title> we set this to an empty string.
release = ""
# General information about the project.
project = "PostgREST " + version
author = "Joe Nelson, Steve Chavez"
author = "The PostgREST contributors"
copyright = "2017, " + author
# The language for content autogenerated by Sphinx. Refer to documentation
@@ -302,6 +302,7 @@ linkcheck_ignore = [
r"https://www.cybertec-postgresql.com/.*",
# Odd SSL error
r"https://www.dripdepot.com",
r"https://www.euronodes.com",
# New GitHub UI delays comment load, so anchor fails
r"https://github.com/.*#issuecomment",
# Random 500 Internal Server Error
+2 -1
View File
@@ -6,7 +6,7 @@ Community Tutorials
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
* `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
* `PostgREST + Auth0: Create REST API in minutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest-1-introduction/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
@@ -37,6 +37,7 @@ Example Apps
* `archtika <https://github.com/thiloho/archtika>`_ - self-hosted CMS
* `delibrium-postgrest <https://gitlab.com/delibrium/delibrium-postgrest/>`_ - example school API and front-end in Vue.js
* `ETH-transactions-storage <https://github.com/Adamant-im/ETH-transactions-storage>`_ - indexer for Ethereum to get transaction list by ETH address
* `fullstack template <https://github.com/jenstroeger/fullstack-webapp-template>`_ - a complete fullstack webapp template using PG as db and message queue, Python and Dramatiq to implement async jobs, db migrations, test runners, and more.
* `general <https://github.com/PierreRochard/general>`_ - example auth back-end
* `guild-operators <https://github.com/cardano-community/koios-artifacts/tree/main/files/grest>`_ - example queries and functions that the Cardano Community uses for their Guild Operators' Repository
* `PostGUI <https://github.com/priyank-purohit/PostGUI>`_ - React Material UI admin panel
+1 -1
View File
@@ -163,7 +163,7 @@ Another option is to define the function with the :code:`SECURITY DEFINER` optio
.. code-block:: postgres
-- login as a user wich has privileges on the private schemas
-- login as a user which has privileges on the private schemas
-- create a sample function
create or replace function login(email text, pass text, out token text) as $$
+19 -2
View File
@@ -16,7 +16,7 @@ Supported PostgreSQL versions
=============================
=============== =================================
**Supported** PostgreSQL >= 12
**Supported** PostgreSQL >= 14
=============== =================================
PostgREST works with all PostgreSQL versions still `officially supported <https://www.postgresql.org/support/versioning/>`_.
@@ -146,7 +146,7 @@ To avoid having to install the database at all, you can run both it and the serv
ports:
- "3000:3000"
environment:
PGRST_SERVER_HOST: localhost # necessary for `postgrest --ready` flag to work
PGRST_SERVER_HOST: 0.0.0.0 # necessary for `postgrest --ready` flag to work
PGRST_DB_URI: postgres://app_user:password@db:5432/app_db
PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
depends_on:
@@ -181,6 +181,23 @@ If you want to have a visual overview of your API in your browser you can add sw
With this you can see the swagger-ui in your browser on port 8080.
.. _docker_cpu_contraint:
Docker Resource Constraints
---------------------------
PostgREST does not support ``--cpus`` `constraint option <https://docs.docker.com/engine/containers/resource_constraints/#configure-the-default-cfs-scheduler>`_.
As a workaround, you may use the `GHC RTS <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/runtime_control.html#runtime-system-rts-options>`_ ``-N`` option. For instance, to limit it to 2 CPU cores, do:
.. code::
# Set environment variable GHCRTS set to "-N2"
docker run --rm -p 3000:3000 \
-e PGRST_DB_URI="postgres://app_user:password@10.0.0.10/postgres" \
-e GHCRTS="-N2"
postgrest/postgrest
.. _build_source:
Building from Source
+26 -20
View File
@@ -38,6 +38,28 @@ Sponsors
.. image:: ../static/cybertec.svg
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
.. container:: img-dark
.. image:: ../static/supabase-dark.svg
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. container:: img-light
.. image:: ../static/supabase.svg
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. container:: img-dark
.. image:: ../static/euronodes.svg
:target: https://www.euronodes.com/postgrest
.. container:: img-light
.. image:: ../static/euronodes.svg
:target: https://www.euronodes.com/postgrest
|
.. container:: img-dark
.. image:: ../static/neon-dark.jpg
@@ -48,30 +70,15 @@ Sponsors
.. image:: ../static/neon.jpg
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
.. image:: ../static/tembo.png
:target: https://www.tembo.io/?utm_source=sponsor&utm_campaign=postgrest
|
.. container:: img-dark
.. image:: ../static/euronodes.svg
:target: https://www.euronodes.com/postgrest
.. image:: ../static/bytebase-dark.svg
:target: https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest
.. container:: img-light
.. image:: ../static/euronodes.svg
:target: https://www.euronodes.com/postgrest
.. container:: img-dark
.. image:: ../static/supabase-dark.png
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. container:: img-light
.. image:: ../static/supabase.png
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. image:: ../static/bytebase.svg
:target: https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest
.. The static/empty.png(created with `convert -size 320x95 xc:#fcfcfc empty.png`) is an ugly workaround
to create space and center the logos. It's not easy to layout with restructuredText.
@@ -213,7 +220,6 @@ In Production
Here are some companies that use PostgREST in production.
* `Catarse <https://www.catarse.me>`_
* `Datrium <https://www.datrium.com>`_
* `Drip Depot <https://www.dripdepot.com>`_
* `Image-charts <https://www.image-charts.com>`_
* `Netwo <https://www.netwo.io>`_
+7
View File
@@ -4,6 +4,7 @@ API's
APIs
APISIX
AST
async
aud
Auth
auth
@@ -14,6 +15,7 @@ BOM
Bytea
Cardano
cd
CDNs
centric
CLI
CMS
@@ -30,6 +32,7 @@ DDL
DOM
DSL
DevOps
Dramatiq
dockerize
enum
Enums
@@ -41,6 +44,7 @@ EveryLayout
filename
FreeBSD
fts
fullstack
GeoJSON
Github
Google
@@ -188,9 +192,12 @@ verifier
versioning
Vondra
Vue
webapp
webhooks
websearch
Websockets
webuser
wfts
www
debouncing
deduplicates
+1
View File
@@ -21,6 +21,7 @@ PostgREST exposes three database objects of a schema as resources: tables, views
api/aggregate_functions.rst
api/openapi.rst
api/preferences.rst
api/vary_header.rst
api/*
.. raw:: html
+20
View File
@@ -69,6 +69,26 @@ If the function doesn't modify the database, it will also run under the GET meth
The function parameter names match the JSON object keys in the POST case, for the GET case they match the query parameters ``?a=1&b=2``.
If the function is defined to have default values for the parameters then arguments for these parameters can be omitted in the request. For instance:
.. code-block:: postgres
CREATE FUNCTION greet_user(username TEXT DEFAULT 'guest')
RETURNS TEXT AS $$
SELECT 'Hello ' || username || '!';
$$ LANGUAGE SQL IMMUTABLE;
.. code-block:: bash
curl -i "http://localhost:3000/rpc/greet_user"
.. code-block:: http
HTTP/1.1 200 OK
Context-Type: application/json; charset=utf-8
"Hello guest!"
.. _function_single_json:
Functions with an array of JSON objects
+1 -1
View File
@@ -15,7 +15,7 @@ Using these domains, :ref:`functions <functions>` can become handlers and `user-
.. important::
- PostgREST vendor media types (``application/vnd.pgrst.plan``, ``application/vnd.pgrst.object`` and ``application/vnd.pgrst.array``) cannot be overriden.
- PostgREST vendor media types (``application/vnd.pgrst.plan``, ``application/vnd.pgrst.object`` and ``application/vnd.pgrst.array``) cannot be overridden.
- Long media types like ``application/vnd.openxmlformats-officedocument.wordprocessingml.document`` cannot be expressed as domains since they surpass `PostgreSQL identifier length <https://www.postgresql.org/docs/current/limits.html#LIMITS-TABLE>`_.
For these you can use the :ref:`any_handler`.
+4
View File
@@ -117,6 +117,10 @@ However, with ``handling=strict``, an invalid time zone preference will throw an
HTTP/1.1 400 Bad Request
.. note::
This feature requires querying `pg_timezone_names <https://www.postgresql.org/docs/current/view-pg-timezone-names.html>`_ during :ref:`schema_cache` load. If this is not desired, you can disable the feature with :ref:`db-timezone-enabled`.
.. _prefer_return:
Return Representation
+1 -1
View File
@@ -1244,7 +1244,7 @@ You can order the correlated arrays explicitly. For example, to order by the fil
.. warning::
Aliasing spreaded columns is recommended since JSON allows duplicate keys. Example:
Aliasing spread columns is recommended since JSON allows duplicate keys. Example:
.. code-block:: bash
+4
View File
@@ -5,6 +5,10 @@ Schemas
PostgREST can expose a single or multiple schema's tables, views and functions. The :ref:`active database role <roles>` must have the usage privilege on the schemas to access them.
.. important::
``pg_catalog`` and ``information_schema`` are not allowed in :ref:`db-schemas`. This is done to prevent leaking sensitive information and hence they cannot be accessed directly. If you wish to expose objects of these schemas, expose another schema that contains wrapper views or functions over ``pg_catalog`` or ``information_schema`` objects.
Single schema
-------------
+1 -1
View File
@@ -639,7 +639,7 @@ However, it can work with surrogate primary keys (e.g. ``id serial primary key``
.. code-block:: bash
curl "http://localhost:3000/employees?colums=id,name,salary" \
curl "http://localhost:3000/employees?columns=id,name,salary" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: resolution=merge-duplicates, missing=default" \
-d @- << EOF
+2 -2
View File
@@ -14,7 +14,7 @@ Custom Queries
The PostgREST URL grammar limits the kinds of queries clients can perform. It prevents arbitrary, potentially poorly constructed and slow client queries. It's good for quality of service, but means database administrators must create custom views and functions to provide richer endpoints. The most common causes for custom endpoints are
* Table unions
* SET operators like `UNION, INTERSECT and EXCEPT <https://www.postgresql.org/docs/current/queries-union.html>`_.
* More complicated joins than those provided by :ref:`resource_embedding`.
* Geo-spatial queries that require an argument, like "points near (lat,lon)"
@@ -51,7 +51,7 @@ You can request table/columns with spaces in them by percent encoding the spaces
Reserved characters
~~~~~~~~~~~~~~~~~~~
If filters include PostgREST reserved characters(``,``, ``.``, ``:``, ``()``) you'll have to surround them in percent encoded double quotes ``%22`` for correct processing.
If filters include PostgREST reserved characters(``,``, ``.``, ``:``, ``*``, ``(``, ``)``) you'll have to surround them in percent encoded double quotes ``%22`` for correct processing.
Here ``Hebdon,John`` and ``Williams,Mary`` are values.
+16
View File
@@ -0,0 +1,16 @@
.. _vary_header:
Vary Header
===========
In order to assist caching proxies and CDNs, PostgREST includes a ``Vary`` header of value
``Accept, Prefer, Range`` in its responses which should fit most of the bills. As any other
response header, it's available for override
by updating ``response.headers`` GUC variable accordingly, for example:
.. code-block:: postgres
-- Override the Vary header to include Accept, Prefer and X-Test-Vary headers
perform set_config('response.headers', '[{"Vary": "Accept, Prefer, X-Test-Vary"}]', true);
In this case PostgREST will use provided value verbatim.
+17 -1
View File
@@ -217,7 +217,7 @@ It's recommended to leave the JWT cache enabled as our load tests indicate ~20%
- If the ``jwt-secret`` is changed and the config is reloaded, the JWT cache will reset.
- JWTs that pass :ref:`jwt_signature` are cached, regardless if they pass :ref:`jwt_claims_validation`. We do this to ensure responses stays fast under common failure cases (such as expired JWTs).
- You can use the :ref:`server-timing_header` to see the peformance benefit of JWT caching.
- You can use the :ref:`server-timing_header` to see the performance benefit of JWT caching.
.. _jwt_role_extract:
@@ -234,6 +234,17 @@ The DSL follows the `JSONPath <https://goessner.net/articles/JsonPath/>`_ expres
- ``==^`` selects the first array element that ends with the right operand
- ``*==`` selects the first array element that contains the right operand
The selected role value can also be sliced using the slice operator ``[a:b]``. It is similar to `slice operator in python <https://docs.python.org/3/library/functions.html#slice>`_. Negative index values are also supported. The syntax is as:
- ``[a:b]`` take slice from index ``a`` up to ``b``
- ``[a:]`` take slice from index ``a`` to end
- ``[:b]`` take slice from start to index ``b``
- ``[:]`` select everything, no slicing
.. important::
Make sure that you are not taking a slice where the start index comes after the end index like ``[11:2]``. The result of this would be empty string and so no role would get selected.
Usage examples:
.. code:: bash
@@ -255,6 +266,11 @@ Usage examples:
jwt-role-claim-key = ".postgrest.roles[?(@ ==^ \"hor\")]"
jwt-role-claim-key = ".postgrest.roles[?(@ *== \"utho\")]"
# {"postgrest":{"wlcg": ["/groupa", "/groupb/"]}}
# skip the "/" character using slice operator
jwt-role-claim-key = ".postgrest.wlcg[0][1:]"
jwt-role-claim-key = ".postgrest.wlcg[1][1:-1]"
.. note::
The string comparison operators are implemented as a custom extension to the JSPath and does not strictly follow the `RFC 9535 <https://www.rfc-editor.org/rfc/rfc9535.html>`_.
+45 -3
View File
@@ -195,6 +195,33 @@ app.settings.*
The :code:`current_setting` function has `an optional boolean second <https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADMIN-SET>`_ argument to avoid it from raising an error if the value was not defined. Default values to :code:`app.settings` can then be given by combining this argument with :code:`coalesce` and :code:`nullif` : :code:`coalesce(nullif(current_setting('app.settings.my_custom_variable', true), ''), 'default value')`. The use of :code:`nullif` is necessary because if set in a transaction, the setting is sometimes not "rolled back" to :code:`null`. See also :ref:`this section <guc_req_headers_cookies_claims>` for more information on this behaviour.
.. _client-error-verbosity:
client-error-verbosity
----------------------
=============== =======================
**Type** String
**Default** verbose
**Reloadable** Y
**Environment** PGRST_CLIENT_ERROR_VERBOSITY
**In-Database** pgrst.client_error_verbosity
=============== =======================
Specifies the verbosity of PostgREST errors. See :ref:`client_error_verbosity`.
.. code:: bash
# Return error "code", "message", "details" and "hint"
client-error-verbosity = "verbose"
# Return only "code" and "message"
client-error-verbosity = "minimal"
.. note::
This setting only affects client side error messages. Server side logs are not affected by this setting.
.. _db-aggregates-enabled:
db-aggregates-enabled
@@ -264,7 +291,7 @@ db-channel-enabled
When this is set to :code:`true`, the notification channel specified in :ref:`db-channel` is enabled.
You should set this to ``false`` when using PostgresSQL behind an external connection pooler such as PgBouncer working in transaction pooling mode. See :ref:`this section <external_connection_poolers>` for more information.
You should set this to ``false`` when using PostgreSQL behind an external connection pooler such as PgBouncer working in transaction pooling mode. See :ref:`this section <external_connection_poolers>` for more information.
.. _db-config:
@@ -479,7 +506,7 @@ db-prepared-statements
When disabled, the generated queries will be parameterized (invulnerable to SQL injection) but they will not be prepared (cached in the database session). Not using prepared statements will noticeably decrease performance, so it's recommended to always have this setting enabled.
You should only set this to ``false`` when using PostgresSQL behind an external connection pooler such as PgBouncer working in transaction pooling mode. See :ref:`this section <external_connection_poolers>` for more information.
You should only set this to ``false`` when using PostgreSQL behind an external connection pooler such as PgBouncer working in transaction pooling mode. See :ref:`this section <external_connection_poolers>` for more information.
.. _db-root-spec:
@@ -513,6 +540,21 @@ db-schemas
The list of database schemas to expose to clients. See :ref:`schemas`.
.. _db-timezone-enabled:
db-timezone-enabled
-------------------
=============== =================================
**Type** Boolean
**Default** True
**Reloadable** Y
**Environment** PGRST_DB_TIMEZONE_ENABLED
**In-Database** pgrst.db_timezone_enabled
=============== =================================
Enables the use of :ref:`prefer_timezone` preference header. Disabled when set to ``false``.
.. _db-tx-end:
db-tx-end
@@ -526,7 +568,7 @@ db-tx-end
**In-Database** pgrst.db_tx_end
=============== =================================
Specifies how to terminate the database transactions.
Specifies how to terminate the database transactions. See :ref:`prefer_tx`.
.. code:: bash
+35
View File
@@ -473,3 +473,38 @@ For example, doing a request on a table with high count (say 30_000_000), we get
Proxy-Status: PostgREST; error=57014
The PostgreSQL error code ``57014`` (`ref <https://www.postgresql.org/docs/current/errcodes-appendix.html>`_) reveals that the error is due to a short ``statement_timeout`` value.
.. _client_error_verbosity:
Client Error Verbosity
======================
For HTTP clients, the error verbosity can be set via :ref:`client-error-verbosity` config.
With ``verbose``, it returns ``code``, ``message``, ``details`` and ``hint``.
.. code:: bash
curl "localhost:3000/itemsxx"
.. code-block:: json
{
"code": "PGRST205",
"message": "Could not find the table 'public.itemsxx' in the schema cache",
"details": "Perhaps you meant the table 'public.items'",
"hint": null
}
With ``minimal``, just ``code`` and ``message`` is returned.
.. code:: bash
curl "localhost:3000/itemsxx"
.. code-block:: json
{
"code": "PGRST205",
"message": "Could not find the table 'public.itemsxx' in the schema cache"
}
+3 -1
View File
@@ -46,7 +46,9 @@ This will cause the :ref:`connection_pool` to connect to the read replica host a
.. note::
Under the hood, PostgREST forces `target_session_attrs=read-write <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS>`_ for the ``LISTEN`` session.
- Under the hood, PostgREST forces `target_session_attrs=read-write <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS>`_ for the ``LISTEN`` session.
So if you specify ``target_session_attrs=read-only`` as mentioned above, PostgREST will override it for the ``LISTEN``.
- ``read-only`` is only available on libpq >= 14, if you use a lower version you will get an error like ``invalid target_session_attrs value: \"read-only\"``.
.. _listener_automatic_recovery:
+1 -1
View File
@@ -120,7 +120,7 @@ Restart the database and watch the log file in real-time to understand how HTTP
Metrics
=======
The ``metrics`` endpoint on the :ref:`admin_server` endpoint provides metrics in `Prometheus text format <https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format>`_.
The ``metrics`` endpoint on the :ref:`admin_server` endpoint provides metrics in `Prometheus text format <https://prometheus.io/docs/instrumenting/exposition_formats/#prometheus-text-format>`_.
.. code-block:: bash
+20 -1
View File
@@ -3,10 +3,16 @@
Schema Cache
============
PostgREST requires metadata from the database schema to provide a REST API that abstracts SQL details. One example of this is the interface for :ref:`resource_embedding`.
PostgREST requires metadata from the database to provide a REST API that abstracts SQL details. One example of this is the interface for :ref:`resource_embedding`.
Getting this metadata requires expensive queries. To avoid repeating this work, PostgREST uses a schema cache.
.. note::
- Schema cache queries have been optimized over time to stay fast, even on complex databases. You can see a summary of their execution time in :ref:`pgrst_logging` and :ref:`metrics`.
- If the schema cache queries are slow, the most likely cause is *system catalog bloat*, see `issue#3212 <https://github.com/PostgREST/postgrest/issues/3212>`_ for more details.
- You can turn the :ref:`log-level` to ``debug`` to see the time of each schema cache query.
.. _schema_reloading:
Schema Cache Reloading
@@ -53,6 +59,19 @@ To reload the schema cache from within the database, you can use the ``NOTIFY``
NOTIFY pgrst, 'reload schema'
Debouncing
~~~~~~~~~~
PostgREST does not reload the schema cache for each notification when several ``NOTIFY pgrst`` events are generated quickly after one another.
There are two cases to consider: when notifications are sent within a single transaction and when they are sent across multiple transactions.
In the first case, PostgreSQL deduplicates identical ``NOTIFY`` events within the same transaction. This means that even if multiple ``NOTIFY pgrst`` statements are executed before a ``COMMIT``, only a single notification is delivered to PostgREST.
In the second case, when notifications are sent from separate transactions in a short time span, PostgREST applies a debouncing mechanism to avoid excessive schema cache reloads.
Instead of reloading the schema cache for each notification, events are grouped within a small time window of 100 milliseconds. The reload function is executed once immediately when the first notification is received and once more after the burst of events settles, resulting in at most two executions within that time window.
.. _auto_schema_reloading:
Automatic Schema Cache Reloading
+1 -1
View File
@@ -221,7 +221,7 @@ Notice that the ``response.headers`` should be set to an *array* of single-key o
.. note::
PostgREST provided headers such as ``Content-Type``, ``Location``, etc. can be overriden this way. Note that irrespective of overridden ``Content-Type`` response header, the content will still be converted to JSON, unless you use :ref:`custom_media`.
PostgREST provided headers such as ``Content-Type``, ``Location``, etc. can be overridden this way. Note that irrespective of overridden ``Content-Type`` response header, the content will still be converted to JSON, unless you use :ref:`custom_media`.
.. _guc_resp_status:
+4 -4
View File
@@ -1,7 +1,7 @@
# This file is auto-generated by postgrest-nixpkgs-upgrade
sphinx==8.2.3
sphinx==9.1.0
sphinx-copybutton==0.5.2
sphinx-rtd-dark-mode==1.3.0
sphinx-rtd-theme==3.0.2
sphinx-tabs==3.4.7
sphinxext-opengraph==0.9.1
sphinx-rtd-theme==3.1.0
sphinx-tabs==3.5.0
sphinxext-opengraph==0.13.0
+1 -1
View File
@@ -22,7 +22,7 @@ Step 1. Install PostgreSQL
If you're already familiar with using PostgreSQL and have it installed on your system you can use the existing installation (see :ref:`pg-dependency` for minimum requirements). For this tutorial we'll describe how to use the database in Docker because database configuration is otherwise too complicated for a simple tutorial.
If Docker is not installed, you can get it `here <https://www.docker.com/get-started>`_. Next, let's pull and start the database image:
If Docker is not installed, you can get it `here <https://www.docker.com/get-started>`_. Make sure that Docker service is `started <https://docs.docker.com/engine/daemon/start/#start-the-daemon-using-operating-system-utilities>`_. Next, let's pull and start the database image:
.. code-block:: bash
Generated
+4 -4
View File
@@ -2,16 +2,16 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1752006229,
"narHash": "sha256-BeuAPwNM2RBc5bvUTb0j4GRs2yBkDeRCw/8Y3v9Xesc=",
"lastModified": 1776949667,
"narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c80edd02003fe3d8af527215a3ac069be9cfd47f",
"rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-25.05-darwin",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
+5 -1
View File
@@ -2,7 +2,7 @@
description = "REST API for any Postgres database";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-25.05-darwin";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
nixConfig = {
@@ -46,5 +46,9 @@
meta.description = "REST API for any Postgres database";
};
});
devShells = genSystems (postgrest: {
default = import ./shell.nix { inherit postgrest; };
});
};
}
+7 -7
View File
@@ -91,11 +91,11 @@ postgrest-gen-ctags postgrest-watch
postgrest-gen-jwt postgrest-with-all
postgrest-gen-secret postgrest-with-git
postgrest-git-hooks postgrest-with-pgrst
postgrest-hsie-graph-modules postgrest-with-postgresql-13
postgrest-hsie-graph-symbols postgrest-with-postgresql-14
postgrest-hsie-minimal-imports postgrest-with-postgresql-15
postgrest-lint postgrest-with-postgresql-16
postgrest-loadtest postgrest-with-postgresql-17
postgrest-hsie-graph-modules postgrest-with-pg-14
postgrest-hsie-graph-symbols postgrest-with-pg-15
postgrest-hsie-minimal-imports postgrest-with-pg-16
postgrest-lint postgrest-with-pg-17
postgrest-loadtest postgrest-with-pg-18
postgrest-loadtest-against postgrest-with-slow-pg
postgrest-loadtest-report postgrest-with-slow-postgrest
postgrest-nixpkgs-upgrade
@@ -174,7 +174,7 @@ $ nix-shell --run "postgrest-with-all postgrest-test-spec"
# Run the tests against a specific version of PostgreSQL (use tab-completion in
# nix-shell to see all available versions):
$ nix-shell --run "postgrest-with-postgresql-13 postgrest-test-spec"
$ nix-shell --run "postgrest-with-pg-17 postgrest-test-spec"
```
@@ -284,7 +284,7 @@ Tools like `postgrest-build`, `postgrest-run`, `postgrest-repl` etc. are simple
also run in CI, with the exception of the IO and Memory checks that need to be run
separately.
`postgrest-with-postgresql-*` take a command as an argument and will run it
`postgrest-with-pg-*` take a command as an argument and will run it
with a temporary database. `postgrest-with-all` will run the command against
all supported PostgreSQL versions. Tests run without `postgrest-with-*` are
run against the latest PostgreSQL version by default.
+6 -24
View File
@@ -16,11 +16,8 @@ The following checklist guides you through the complete process in more detail.
## Upgrade the pinned version of `nixpkgs`
The pinned version of [`nixpkgs`](https://github.com/NixOS/nixpkgs) is defined
in [`nix/nixpkgs-version.nix`](nixpkgs-version.nix). The pin refers directly to
a GitHub tarball for the given revision, which is more efficient than pulling
the complete Git repository. To upgrade it to the current `main` of
`nixpkgs`, you can use a small utility script defined in
[`nix/nixpkgs-update.nix`](nixpkgs-update.nix):
in [`flake.nix`](../flake.nix). To upgrade it, you can use a small utility
script defined in [`nix/tools/nixpkgsTools.nix`](tools/nixpkgsTools.nix):
```bash
# From the root of the repository, enter nix-shell
@@ -30,21 +27,12 @@ nix-shell
postgrest-nixpkgs-upgrade
# Exit the nix-shell with Ctrl-d
```
## Review overlays
Check whether the individual [overlays](overlays) are still required.
## Check if patches are still required and update them as needed
We track a number of PostgREST-specific patches in [`nix/patches`](patches).
Check whether the pull-requests/issues linked in the
[`default.nix`](patches/default.nix) have progressed and remove/modify the
patches if they did. If conflicting changes occurred, you might have to rebase
the respective patches.
## Build everything
Using the PostgREST binary Nix cache is recommended. Install
@@ -58,25 +46,19 @@ errors, this is probably due to one of our patches. Try to fix them and re-run
## Update the PostgREST binary cache
If you have access to the PostgREST cachix signing key, you can push the
If you have access to the PostgREST cachix project, you can push the
artifacts that you built locally to the binary cache. This will accelerate the
CI builds and tests, sometimes dramatically. This might sometimes even be
required to avoid build timeouts in CI.
You'll need to set the `CACHIX_SIGNING_KEY` before proceeding, e.g. by creating
a file containing `export CACHIX_SIGNING_KEY=...` and sourcing that file, which
avoids having the secret in your shell history.
You'll need to login with your token with `cachix authtoken <token>`.
To push all new artifacts to Cachix, run:
```
nix-store -qR --include-outputs $$(nix-instantiate) | cachix push postgrest
# Or, equivalently
nix-shell --run postgrest-push-cachix
```
The `nix-store` command will query the nix-store to list all dependencies and
build artifacts of PostgREST. The `cachix` command will efficiently push
The `postgrest-push-cachix` command will query the nix-store to list all
dependencies and build artifacts of PostgREST. It will then push
everything that is not yet cached to the binary cache.
+20 -7
View File
@@ -4,6 +4,7 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- | Haskell Imports and Exports tool
@@ -33,13 +34,16 @@ import Data.Function ((&))
import Data.List (intercalate)
import Data.Maybe (catMaybes, mapMaybe)
import Data.Text (Text)
import GHC.Driver.Errors.Types (GhcMessage)
import GHC.Generics (Generic)
import GHC.Hs.Extension (GhcPs)
import GHC.Types.Error (getMessages)
import GHC.Types.Error (Messages, defaultDiagnosticOpts,
getMessages)
import GHC.Types.Name.Occurrence (occNameString)
import GHC.Types.Name.Reader (rdrNameOcc)
import GHC.Unit.Module.Name (moduleNameString)
import GHC.Unit.Module (moduleNameString)
import GHC.Utils.Error (pprMsgEnvelopeBagWithLoc)
import GHC.Utils.Outputable (showSDocUnsafe)
import System.Directory.Recursive (getFilesRecursive)
import System.Exit (exitFailure)
@@ -198,7 +202,7 @@ sourceSymbols source = do
return $ concatMap (importSymbols source filepath . GHC.unLoc) hsmodImports
-- | Parse a Haskell module
parseModule :: FilePath -> IO GHC.HsModule
parseModule :: FilePath -> IO (GHC.HsModule GhcPs)
parseModule filepath = do
result <- ExactPrint.parseModule GHC.Paths.libdir filepath
case result of
@@ -206,7 +210,13 @@ parseModule filepath = do
return $ GHC.unLoc hsmod
Left errs ->
fail $ "Errors with " <> show filepath <> ":\n "
<> show (pprMsgEnvelopeBagWithLoc $ getMessages errs)
<> formatParseErrors errs
formatParseErrors :: Messages GhcMessage -> String
formatParseErrors errs =
intercalate "\n "
. fmap showSDocUnsafe
$ pprMsgEnvelopeBagWithLoc (defaultDiagnosticOpts @GhcMessage) (getMessages errs)
-- | Symbols imported in an import declaration.
--
@@ -214,9 +224,12 @@ parseModule filepath = do
-- only one item is returned.
importSymbols :: FilePath -> FilePath -> GHC.ImportDecl GhcPs -> [ImportedSymbol]
importSymbols source filepath GHC.ImportDecl{..} =
case ideclHiding of
Just (hiding, syms) ->
symbol (if hiding then Hiding else Explicit) . Just . GHC.unLoc <$> GHC.unLoc syms
case ideclImportList of
Just (importListInterpretation, syms) ->
symbol (if importListInterpretation == GHC.EverythingBut then Hiding else Explicit)
. Just
. GHC.unLoc
<$> GHC.unLoc syms
Nothing ->
[ symbol Wildcard Nothing ]
where
@@ -104,8 +104,7 @@ let
''
+ lib.optionalString withTmpDir ''
mkdir -p "''${TMPDIR:-/tmp}/postgrest"
tmpdir="$(${coreutils}/bin/mktemp -d --tmpdir postgrest/${name}-XXX)"
tmpdir="$(${coreutils}/bin/mktemp -d --tmpdir=/tmp ${name}-XXX)"
# we keep the tmpdir when an error occurs for debugging
trap 'echo Temporary directory kept at: $tmpdir' ERR
-1
View File
@@ -3,5 +3,4 @@
checked-shell-script = import ./checked-shell-script;
gitignore = import ./gitignore.nix;
haskell-packages = import ./haskell-packages.nix;
slocat = import ./slocat.nix;
}
+25 -19
View File
@@ -47,37 +47,43 @@ let
# - To modify and try packages locally, see "Working with locally modified Haskell packages" in the Nix README.
# Before upgrading fuzzyset to 0.3, check: https://github.com/PostgREST/postgrest/issues/3329
# jailbreak, because hspec limit for tests
fuzzyset = prev.fuzzyset_0_2_4;
# TODO: Remove once available in nixpkgs haskellPackages
configurator-pg =
http2 =
prev.callHackageDirect
{
pkg = "configurator-pg";
ver = "0.2.11";
sha256 = "sha256-mtGtNawDJgz2ZIEVca+IYXVu4oNw9xsfJiYWAqAbbgc=";
pkg = "http2";
ver = "5.4.0";
sha256 = "sha256-PeEWVd61bQ8G7LvfLeXklzXqNJFaAjE2ecRMWJZESPE=";
}
{ };
# TODO: Remove once available in nixpkgs haskellPackages
streaming-commons =
http-semantics =
prev.callHackageDirect
{
pkg = "streaming-commons";
ver = "0.2.3.1";
sha256 = "sha256-Gl2eaJcWe1sxmcE/octWlH9uSnERguf+5H66K4fV87s=";
pkg = "http-semantics";
ver = "0.4.0";
sha256 = "sha256-rh0z51EKvsu5rQd5n2z3fSRjjEObouNZSBPO9NFYOF0=";
}
{ };
# Downgrade hasql and related packages while we are still on GHC 9.4 for the static build.
hasql = lib.dontCheck (lib.doJailbreak prev.hasql_1_6_4_4);
hasql-dynamic-statements = lib.dontCheck prev.hasql-dynamic-statements_0_3_1_5;
hasql-implicits = lib.dontCheck prev.hasql-implicits_0_1_1_3;
hasql-notifications = lib.dontCheck prev.hasql-notifications_0_2_2_2;
hasql-pool = lib.dontCheck prev.hasql-pool_1_0_1;
hasql-transaction = lib.dontCheck prev.hasql-transaction_1_1_0_1;
postgresql-binary = lib.dontCheck (lib.doJailbreak prev.postgresql-binary_0_13_1_3);
network-run =
prev.callHackageDirect
{
pkg = "network-run";
ver = "0.5.0";
sha256 = "sha256-vbXh+CzxDsGApjqHxCYf/ijpZtUCApFbkcF5gyN0THU=";
}
{ };
warp =
lib.dontCheck (prev.callHackageDirect
{
pkg = "warp";
ver = "3.4.13";
sha256 = "sha256-jmr8kpeSPDkOhT0i9PhozZapX4nUs92cOX7POAGb7/M=";
}
{ });
};
in
{
-13
View File
@@ -1,13 +0,0 @@
_: prev:
{
slocat = prev.buildGoModule {
name = "slocat";
src = prev.fetchFromGitHub {
owner = "robx";
repo = "slocat";
rev = "52e7512c6029fd00483e41ccce260a3b4b9b3b64";
sha256 = "sha256-qn6luuh5wqREu3s8RfuMCP5PKdS2WdwPrujRYTpfzQ8=";
};
vendorHash = null;
};
}
+4 -3
View File
@@ -82,6 +82,7 @@ let
}
''
${tests}/bin/postgrest-test-spec
${tests}/bin/postgrest-test-observability
${tests}/bin/postgrest-test-doctests
${tests}/bin/postgrest-test-io
${tests}/bin/postgrest-test-big-schema
@@ -171,7 +172,7 @@ let
# The following unsets all GIT_ variables.
unset "''${!GIT_@}"
# shellcheck disable=SC2317
# shellcheck disable=SC2329
function restore () {
ref="$(git stash list --format=format:%gD --grep "$1" -n1)"
# this will avoid merge conflicts when applying the stash
@@ -204,7 +205,7 @@ let
${git}/bin/git add .
;;
pre-push)
# Create a clean working tree without any uncomitted changes.
# Create a clean working tree without any uncommitted changes.
${withTools.withGit} HEAD ${style}/bin/postgrest-lint
;;
esac
@@ -231,7 +232,7 @@ let
${style}/bin/postgrest-lint
;;
pre-push)
# Create a clean working tree without any uncomitted changes.
# Create a clean working tree without any uncommitted changes.
${withTools.withGit} HEAD ${check}
;;
esac
+2 -2
View File
@@ -43,7 +43,7 @@ let
}
if [ "$_arg_language" == "" ]; then
# clean previous build, otherwise some errors might be supressed
# clean previous build, otherwise some errors might be suppressed
rm -rf "../.docs-build/html/default"
if [ -d languages ]; then
@@ -54,7 +54,7 @@ let
build html "../.docs-build/html/default"
else
# clean previous build, otherwise some errors might be supressed
# clean previous build, otherwise some errors might be suppressed
rm -rf "../.docs-build/html/$_arg_language"
# update and build specific locale, can be used to create new locale
+52
View File
@@ -0,0 +1,52 @@
# Generate RSA JWK/public material for loadtests.
import argparse
import sys
from pathlib import Path
import jwcrypto.jwk as jwk
def main():
parser = argparse.ArgumentParser(
description="Generate RSA JWK/private key pair for loadtests"
)
parser.add_argument(
"--rsa",
dest="jwk_path",
metavar="JWK_PATH",
type=Path,
required=True,
help="Path to write the RSA JWK file",
)
parser.add_argument(
"--private-key",
dest="private_key_path",
metavar="PRIVATE_KEY_PATH",
type=Path,
required=True,
help="Path to write the RSA private key file",
)
args = parser.parse_args()
key = jwk.JWK.generate(kty="RSA", size=4096)
private_jwk, public_jwk = key.export_private(), key.export_public()
try:
args.jwk_path.write_text(public_jwk)
print(f"Created RSA JWK on {args.jwk_path}")
except OSError as e:
print(f"Error writing to {args.jwk_path}:{e}", file=sys.stderr)
sys.exit(1)
try:
args.private_key_path.write_text(private_jwk)
print(f"Created private key on {args.private_key_path}")
except OSError as e:
print(f"Error writing to {args.private_key_path}:{e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+106 -25
View File
@@ -12,23 +12,24 @@
# from an array
import time
import argparse
import subprocess
import sys
import random
import jwt
import jwcrypto.jwk as jwk
from typing import Optional
from pathlib import Path
from enum import Enum
URL = "http://postgrest"
secret_key = b"reallyreallyreallyreallyverysafe"
key = jwk.JWK.generate(kty="RSA", size=4096)
private_key = jwt.algorithms.RSAAlgorithm.from_jwk(key.export_private())
public_key = key.export_public()
def generate_jwt(now: int, exp_inc: Optional[int], is_hs: bool) -> str:
def generate_jwt(
now: int,
exp_inc: Optional[int],
rsa_private_key: Optional[jwt.algorithms.RSAAlgorithm],
) -> str:
"""Generate an HS256 or RS256 JWT"""
payload = {
"sub": f"user_{random.getrandbits(32)}",
@@ -39,25 +40,72 @@ def generate_jwt(now: int, exp_inc: Optional[int], is_hs: bool) -> str:
if exp_inc is not None:
payload["exp"] = now + exp_inc
k = secret_key if is_hs else private_key
alg = "HS256" if is_hs else "RS256"
return jwt.encode(payload, k, alg)
if rsa_private_key is None:
key = secret_key
alg = "HS256"
else:
key = rsa_private_key
alg = "RS256"
return jwt.encode(payload, key, alg)
def append_targets(lines: list[str], token: str):
lines.append(f"OPTIONS {URL}/authors_only")
HTTP_METHODS = (
"GET",
"OPTIONS",
)
HttpMethod = Enum(
"HttpMethod",
{method: method for method in HTTP_METHODS},
type=str,
module=__name__,
)
def append_targets(lines: list[str], token: str, http_method: HttpMethod):
lines.append(f"{http_method.value} {URL}/authors_only")
lines.append(f"Authorization: Bearer {token}")
lines.append("") # blank line to separate requests
# we use this to chain commands on loadtest.nix
def run_command(command: list[str]):
if not command:
return
if command[0] == "--":
command = command[1:]
if not command:
return
try:
subprocess.run(command, check=True)
except subprocess.CalledProcessError as exc:
print(
f"Error executing command {' '.join(command)}: {exc}",
file=sys.stderr,
)
sys.exit(exc.returncode)
def main():
parser = argparse.ArgumentParser(
description="Generate Vegeta targets with unique JWTs"
)
parser.add_argument(
"output",
"targets_path",
metavar="TARGETS_PATH",
help="Path to write the generated targets file",
)
parser.add_argument(
"--private-key",
dest="private_key_path",
metavar="PRIVATE_KEY_PATH",
type=Path,
default=None,
help="Path to the RSA private key file (required when --rsa is used)",
)
parser.add_argument(
"--worst",
dest="worst",
@@ -71,14 +119,31 @@ def main():
metavar="JWK_PATH",
type=Path,
default=None,
help="Path for generating a RSA JWK file to sign tokens with",
help="Path to an existing RSA JWK file used for signing tokens",
)
parser.add_argument(
"--method",
dest="http_method",
choices=list(HTTP_METHODS),
required=True,
help="HTTP method for the vegeta targets",
)
parser.add_argument(
"command",
nargs=argparse.REMAINDER,
help="Command (and arguments) to run after generating the targets",
)
args = parser.parse_args()
rsa_private_key: Optional[jwt.algorithms.RSAAlgorithm] = None
is_hs = args.jwk_path is None
http_method = HttpMethod(args.http_method)
nsamples = 1000
if is_hs:
ntargets = 200000
else:
@@ -86,12 +151,25 @@ def main():
ntargets = 50000
if not is_hs:
if args.private_key_path is None:
parser.error("--rsa requires the --private-key option")
try:
with open(args.jwk_path, "w") as jwk:
jwk.write(public_key)
print(f"Created {args.jwk_path} file containing the RSA JWK")
except IOError as e:
print(f"Error writing to {args.jwk_path}: {e}", file=sys.stderr)
private_key_data = args.private_key_path.read_text()
except OSError as e:
err = (
f"Error reading RSA private key from {args.private_key_path}: "
f"{e}. Generate RSA materials first with gen_rsa_materials.py."
)
print(err, file=sys.stderr)
sys.exit(1)
try:
rsa_private_key = jwt.algorithms.RSAAlgorithm.from_jwk(private_key_data)
except Exception as exc: # broad exception to capture parsing errors
err = (
f"Error loading RSA private key from {args.private_key_path}: " f"{exc}"
)
print(err, file=sys.stderr)
sys.exit(1)
print(f"Generating {ntargets} targets...")
@@ -110,6 +188,7 @@ def main():
if args.worst:
# estimated time takes to build and run postgrest itself
build_run_postgrest_time = 2
# estimated time it takes to generate the targets file
# the division numbers are tuned by hand
if is_hs: # hs generation is much faster
@@ -121,25 +200,27 @@ def main():
inc = build_run_postgrest_time + gen_time
for i in range(ntargets):
token = generate_jwt(now, inc + i // 1000, is_hs)
append_targets(lines, token)
token = generate_jwt(now, inc + i // 1000, rsa_private_key)
append_targets(lines, token, http_method)
else:
tokens = [generate_jwt(now, None, is_hs) for _ in range(nsamples)]
tokens = [generate_jwt(now, None, rsa_private_key) for _ in range(nsamples)]
for i in range(ntargets):
token = random.choice(tokens)
append_targets(lines, token)
append_targets(lines, token, http_method)
try:
with open(args.output, "w") as f:
with open(args.targets_path, "w") as f:
f.write("\n".join(lines))
except IOError as e:
print(f"Error writing to {args.output}: {e}", file=sys.stderr)
print(f"Error writing to {args.targets_path}: {e}", file=sys.stderr)
sys.exit(1)
elapsed = time.time() - start_time
print(f"Created {ntargets} targets", end=" ")
print(f"in {args.output} ({elapsed:.2f}s)")
print(f"in {args.targets_path} ({elapsed:.2f}s)")
run_command(args.command)
if __name__ == "__main__":
+105 -32
View File
@@ -18,6 +18,8 @@ let
];
}
''
echo "Starting vegeta loadtest..."
# 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
@@ -42,7 +44,9 @@ let
"ARG_OPTIONAL_SINGLE([output], [o], [Filename to dump json output to], [./loadtest/result.bin])"
"ARG_OPTIONAL_SINGLE([testdir], [t], [Directory to load tests and fixtures from], [./test/load])"
"ARG_OPTIONAL_SINGLE([kind], [k], [Kind of loadtest], [mixed])"
"ARG_TYPE_GROUP_SET([KIND], [KIND], [kind], [mixed,jwt-hs,jwt-hs-cache,jwt-hs-cache-worst,jwt-rsa,jwt-rsa-cache,jwt-rsa-cache-worst])"
"ARG_OPTIONAL_SINGLE([method],, [HTTP method used for the jwt loadtests], [OPTIONS])"
"ARG_TYPE_GROUP_SET([KIND], [KIND], [kind], [mixed,errors,jwt-hs,jwt-hs-cache,jwt-hs-cache-worst,jwt-rsa,jwt-rsa-cache,jwt-rsa-cache-worst])"
"ARG_TYPE_GROUP_SET([METHOD], [METHOD], [method], [OPTIONS,GET])"
"ARG_OPTIONAL_SINGLE([monitor], [m], [Monitoring file], [./loadtest/result.csv])"
"ARG_LEFTOVERS([additional vegeta arguments])"
];
@@ -59,67 +63,127 @@ let
export PGRST_DB_TX_END="rollback-allow-override"
export PGRST_LOG_LEVEL="crit"
export PGRST_JWT_SECRET="reallyreallyreallyreallyverysafe"
# set previous PGRST_JWT_CACHE_MAX_LIFETIME configuration so that
# load test works across branches
# TODO clean once PGRST_JWT_CACHE_MAX_ENTRIES merged and released
export PGRST_JWT_CACHE_MAX_LIFETIME="86400"
mkdir -p "$(dirname "$_arg_output")"
abs_output="$(realpath "$_arg_output")"
case "$_arg_kind" in
jwt-hs)
${genTargetsHS} "$_arg_testdir"/gen_targets.http
export PGRST_JWT_CACHE_MAX_ENTRIES="0"
export PGRST_JWT_CACHE_MAX_LIFETIME="0"
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
${withGenTargets} --method "$_arg_method" "$_arg_testdir"/gen_targets.http \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
jwt-hs-cache)
${genTargetsHS} "$_arg_testdir"/gen_targets.http
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
${withGenTargets} --method "$_arg_method" "$_arg_testdir"/gen_targets.http \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
jwt-hs-cache-worst)
${genTargetsHS} --worst "$_arg_testdir"/gen_targets.http
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
${withGenTargets} --method "$_arg_method" --worst "$_arg_testdir"/gen_targets.http \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
jwt-rsa)
${genTargetsHS} --rsa="$_arg_testdir"/gen_jwk.json "$_arg_testdir"/gen_targets.http
export PGRST_JWT_CACHE_MAX_ENTRIES="0"
export PGRST_JWT_CACHE_MAX_LIFETIME="0"
${genRsaMaterials} --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json
export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json"
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
${withGenTargets} --method "$_arg_method" --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json "$_arg_testdir"/gen_targets.http \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
jwt-rsa-cache)
${genTargetsHS} --rsa="$_arg_testdir"/gen_jwk.json "$_arg_testdir"/gen_targets.http
${genRsaMaterials} --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json
export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json"
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
${withGenTargets} --method "$_arg_method" --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json "$_arg_testdir"/gen_targets.http \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
jwt-rsa-cache-worst)
${genTargetsHS} --worst --rsa="$_arg_testdir"/gen_jwk.json "$_arg_testdir"/gen_targets.http
export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json"
${genRsaMaterials} --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json
export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json"
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
${withGenTargets} --method "$_arg_method" --worst --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json "$_arg_testdir"/gen_targets.http \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
*)
mixed)
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
sh -c "cd \"$_arg_testdir\" && \
${runner} -targets targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
# here we sleep purposefully to check how much memory does the schema cache consume in the final report
errors)
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/errors.sql \
${withTools.withPgrst} --timeout 2 --sleep 5 -m "$_arg_monitor" \
sh -c "cd \"$_arg_testdir\" && \
${runner} -targets errors.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
;;
esac
if [ "$_arg_kind" == "mixed" ]; then
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withSlowPg} \
${withTools.withPgrst} -m "$_arg_monitor" \
${withTools.withSlowPgrst} \
sh -c "cd \"$_arg_testdir\" && \
${runner} -targets targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
else
# shellcheck disable=SC2145
${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \
${withTools.withPgrst} -m "$_arg_monitor" \
sh -c "cd \"$_arg_testdir\" && \
${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\""
fi
${vegeta}/bin/vegeta report -type=text "$_arg_output"
if [ "$_arg_kind" != "errors" ]; then
# fail in case 401 happened on jwt loadtests
unauthorized_count="$(${vegeta}/bin/vegeta report -type=json "$_arg_output" \
| ${jq}/bin/jq -r '.status_codes["401"] // 0')"
if [ "$unauthorized_count" -gt 0 ]; then
last_unauthorized_body="$(${vegeta}/bin/vegeta encode "$_arg_output" \
| ${jq}/bin/jq -rn '
reduce inputs as $item (null;
if $item.code == 401 then $item else . end
)
| if . == null then
empty
else
(.body | @base64d)
end
')"
echo "loadtest failed: found $unauthorized_count 401 Unauthorized responses" >&2
if [ -n "$last_unauthorized_body" ]; then
printf '%s\n' "Last 401 response body:" >&2
printf '%s\n' "$last_unauthorized_body" >&2
fi
exit 1
fi
fi
'';
loadtestAgainst =
@@ -250,13 +314,22 @@ let
| ${mergeMonitorResults}
'';
genTargetsHS =
writers.writePython3 "postgrest-gen-loadtest-targets-hs"
withGenTargets =
writers.writePython3 "postgrest-with-gen-loadtest-targets"
{
libraries = [ python3Packages.pyjwt python3Packages.jwcrypto ];
doCheck = false; # postgrest-style conflicts with this
}
(builtins.readFile ./generate_targets.py);
genRsaMaterials =
writers.writePython3 "postgrest-gen-rsa-materials"
{
libraries = [ python3Packages.jwcrypto ];
doCheck = false; # postgrest-style conflicts with this
}
(builtins.readFile ./gen_rsa_materials.py);
mergeMonitorResults =
writers.writePython3 "postgrest-merge-monitor-results"
{
+1 -1
View File
@@ -68,7 +68,7 @@ let
bump devel
# The order of operations is important here:
# - bump devel is run and $A is upated to the new version
# - bump devel is run and $A is updated to the new version
# - the branch is created with the new A, but the commit before the devel bump
# - the devel bump is committed
git branch "v$A"
+24 -7
View File
@@ -12,6 +12,7 @@
, silver-searcher
, statix
, stylish-haskell
, writeText
}:
let
style =
@@ -28,19 +29,20 @@ let
# Format Haskell files
# --vimgrep fixes a bug in ag: https://github.com/ggreer/the_silver_searcher/issues/753
${silver-searcher}/bin/ag -l --vimgrep -g '\.l?hs$' . \
# TODO: fix style issues in src/protolude and include it
${silver-searcher}/bin/ag -l --vimgrep -g '\.l?hs$' --ignore-dir=src/protolude . \
| xargs ${stylish-haskell}/bin/stylish-haskell -i
# Format Python files
${black}/bin/black . 2> /dev/null
'';
# Script to check whether any uncommited changes result from postgrest-style
# Script to check whether any uncommitted changes result from postgrest-style
styleCheck =
checkedShellScript
{
name = "postgrest-style-check";
docs = "Check whether postgrest-style results in any uncommited changes.";
docs = "Check whether postgrest-style results in any uncommitted changes.";
workingDir = "/";
}
''
@@ -51,6 +53,20 @@ let
${git}/bin/git diff-index --exit-code HEAD -- '*.hs' '*.lhs' '*.nix' '*.py'
'';
hlintConfig = writeText "hlintConfig.yml" ''
# Arguments passed to hlint
- arguments: [-j, -XQuasiQuotes, -XNoPatternSynonyms]
# Warnings
- warn: { lhs: "a == a", rhs: "True", note: "This comparison always evaluates to True" }
- warn: { lhs: "a /= a", rhs: "False", note: "This comparison always evaluates to False" }
- warn: { lhs: "a < a", rhs: "False", note: "This comparison always evaluates to False" }
- warn: { lhs: "a > a", rhs: "False", note: "This comparison always evaluates to False" }
- warn: { lhs: "a <= a", rhs: "True", note: "This comparison always evaluates to True" }
- warn: { lhs: "a >= a", rhs: "True", note: "This comparison always evaluates to True" }
'';
lint =
checkedShellScript
{
@@ -68,18 +84,19 @@ let
# ruff has gaps in scanning for unused code, so we use vulture
echo "Scanning python files for unused code..."
${silver-searcher}/bin/ag -l --vimgrep -g '\.l?py$' . \
| xargs ${python3Packages.vulture}/bin/vulture --exclude docs/conf.py
| xargs ${python3Packages.vulture}/bin/vulture --exclude docs/conf.py --min-confidence 80
echo "Linting python files..."
${ruff}/bin/ruff check .
echo "Checking consistency of import aliases in Haskell code..."
${hsie} check-aliases main src
${hsie} check-aliases main src/PostgREST
echo "Linting Haskell files..."
# --vimgrep fixes a bug in ag: https://github.com/ggreer/the_silver_searcher/issues/753
${silver-searcher}/bin/ag -l --vimgrep -g '\.l?hs$' . \
| xargs ${hlint}/bin/hlint -j -X QuasiQuotes -X NoPatternSynonyms
# TODO: fix lint issues in src/protolude and include it
${silver-searcher}/bin/ag -l --vimgrep -g '\.l?hs$' --ignore-dir=src/protolude . \
| xargs ${hlint}/bin/hlint --hint=${hlintConfig}
'';
in
+29 -10
View File
@@ -7,7 +7,6 @@
, glibcLocales ? null
, gnugrep
, hpc-codecov
, hostPlatform
, jq
, lib
, postgrest
@@ -32,6 +31,20 @@ let
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec -- "''${_arg_leftovers[@]}"
'';
testObservability =
checkedShellScript
{
name = "postgrest-test-observability";
docs = "Run the Haskell observability test suite.";
args = [ "ARG_LEFTOVERS([hspec arguments])" ];
workingDir = "/";
withEnv = postgrest.env;
}
''
${withTools.withPg} -f test/observability/fixtures/load.sql \
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:observability -- "''${_arg_leftovers[@]}"
'';
testDoctests =
checkedShellScript
{
@@ -83,7 +96,7 @@ let
}
''
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures.sql \
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures/load.sql \
${ioTestPython}/bin/pytest --ignore=test/io/test_big_schema.py --ignore=test/io/test_replica.py -v test/io "''${_arg_leftovers[@]}"
'';
@@ -98,7 +111,7 @@ let
}
''
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/big_schema.sql \
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures/big_schema.sql \
${ioTestPython}/bin/pytest -v test/io/test_big_schema.py "''${_arg_leftovers[@]}"
'';
@@ -113,7 +126,7 @@ let
}
''
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} --replica -f test/io/replica.sql \
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} --replica -f test/io/fixtures/replica.sql \
${ioTestPython}/bin/pytest -v test/io/test_replica.py "''${_arg_leftovers[@]}"
'';
@@ -145,7 +158,7 @@ let
}
(
# required for `hpc markup` in CI; glibcLocales is not available e.g. on Darwin
lib.optionalString (stdenv.isLinux && hostPlatform.libc == "glibc") ''
lib.optionalString (stdenv.isLinux && stdenv.hostPlatform.libc == "glibc") ''
export LOCALE_ARCHIVE="${glibcLocales}/lib/locale/locale-archive"
'' +
@@ -155,7 +168,7 @@ let
rm -rf coverage/*
# build once before running all the tests
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest lib:postgrest test:spec
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest lib:postgrest test:spec test:observability
(
trap 'echo Found dead code: Check file list above.' ERR ;
@@ -164,26 +177,31 @@ let
# collect all tests
HPCTIXFILE="$tmpdir"/io.tix \
${withTools.withPg} -f test/io/fixtures.sql \
${withTools.withPg} -f test/io/fixtures/load.sql \
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest --ignore=test/io/test_big_schema.py --ignore=test/io/test_replica.py -v test/io
HPCTIXFILE="$tmpdir"/big_schema.tix \
${withTools.withPg} -f test/io/big_schema.sql \
${withTools.withPg} -f test/io/fixtures/big_schema.sql \
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest -v test/io/test_big_schema.py
HPCTIXFILE="$tmpdir"/replica.tix \
${withTools.withPg} --replica -f test/io/replica.sql \
${withTools.withPg} --replica -f test/io/fixtures/replica.sql \
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest -v test/io/test_replica.py
HPCTIXFILE="$tmpdir"/spec.tix \
${withTools.withPg} -f test/spec/fixtures/load.sql \
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec
HPCTIXFILE="$tmpdir"/observability.tix \
${withTools.withPg} -f test/observability/fixtures/load.sql \
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:observability
# Note: No coverage for doctests, as doctests leverage GHCi and GHCi does not support hpc
# collect all the tix files
${ghc}/bin/hpc sum --union --exclude=Paths_postgrest --output="$tmpdir"/tests.tix \
"$tmpdir"/io*.tix "$tmpdir"/big_schema*.tix "$tmpdir"/replica*.tix "$tmpdir"/spec.tix
"$tmpdir"/io*.tix "$tmpdir"/big_schema*.tix "$tmpdir"/replica*.tix "$tmpdir"/spec.tix \
"$tmpdir"/observability.tix
# prepare the overlay
${ghc}/bin/hpc overlay --output="$tmpdir"/overlay.tix test/coverage.overlay
@@ -250,6 +268,7 @@ buildToolbox
tools = {
inherit
testSpec
testObservability
testDoctests
testSpecIdempotence
testIO
+55 -106
View File
@@ -6,7 +6,6 @@
, postgresqlVersions
, postgrest
, python3Packages
, slocat
, writeText
, writers
}:
@@ -25,7 +24,7 @@ let
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from])"
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])"
"ARG_USE_ENV([PGUSER], [Postgrest_Test_Authenticator], [Authenticator PG role])" # user is written in mixed case to implicitly test that it is being properly quoted in schema cache queries
"ARG_USE_ENV([PGDATABASE], [postgres], [PG database name])"
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
"ARG_USE_ENV([PGTZ], [utc], [Timezone to use])"
@@ -46,7 +45,7 @@ let
}
# Avoid starting multiple layers of withTmpDb, but make sure to have the last invocation
# load fixtures. Otherwise postgrest-with-postgresql-xx postgrest-test-io would not be possible.
# load fixtures. Otherwise postgrest-with-pg-xx postgrest-test-io would not be possible.
if ! test -v PGHOST; then
mkdir -p "$tmpdir"/{db,socket}
@@ -74,7 +73,13 @@ let
>> "$setuplog"
log "Starting the database cluster..."
# Instead of listening on a local port, we will listen on a unix domain socket.
# NOTE: unix domain socket filename name must remain under max limit.
# On Linux, it's 108 chars (including '\0' terminator)
# On MacOS, it's 104 chars
# See: https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket
pg_ctl -l "$tmpdir/db.log" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $PGHOST -c log_statement=\"all\" " \
>> "$setuplog"
@@ -100,7 +105,8 @@ let
log "Starting replica on $replica_host"
pg_ctl -D "$replica_dir" -l "$replica_dblog" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $replica_host -c log_statement=\"all\" " \
# We set a low max_standby_streaming_delay to make the replication conflict fail faster in tests (otherwise it waits for the default 30s)
pg_ctl -D "$replica_dir" -l "$replica_dblog" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $replica_host -c log_statement=\"all\" -c max_standby_streaming_delay=\"3s\" " \
>> "$setuplog"
>&2 echo "${commandName}: Replica enabled. You can connect to it with: psql 'postgres:///$PGDATABASE?host=$replica_host' -U postgres"
@@ -111,7 +117,7 @@ let
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST"
fi
# shellcheck disable=SC2317
# shellcheck disable=SC2329
stop () {
log "Stopping the database cluster..."
pg_ctl stop --mode=immediate >> "$setuplog"
@@ -126,9 +132,11 @@ let
fi
if test "$_arg_fixtures"; then
log "Loading fixtures under the postgres role..."
load_start=$SECONDS
>&2 printf "${commandName}: Loading fixtures under the postgres role..."
psql -U postgres -v PGUSER="$PGUSER" -v ON_ERROR_STOP=1 -f "$_arg_fixtures" >> "$setuplog"
log "Done. Running command..."
load_end=$((SECONDS - load_start))
>&2 printf " done in %ss. Running command...\n" "$load_end"
fi
("$_arg_command" "''${_arg_leftovers[@]}")
@@ -177,81 +185,6 @@ let
withPg = withTmpDb (builtins.head postgresqlVersions);
withSlowPg =
checkedShellScript
{
name = "postgrest-with-slow-pg";
docs = "Run the given command with simulated high latency postgresql";
args =
[
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_USE_ENV([PGHOST], [], [PG host (socket name)])"
"ARG_USE_ENV([PGDELAY], [0ms], [extra PG latency (duration)])"
];
positionalCompletion = "_command";
workingDir = "/";
redirectTixFiles = false;
withTmpDir = true;
}
''
delay="''${PGDELAY:-0ms}"
echo "delaying data to/from postgres by $delay"
REALPGHOST="$PGHOST"
export PGHOST="$tmpdir/socket"
mkdir -p "$PGHOST"
${slocat}/bin/slocat -delay "$delay" -src "$PGHOST/.s.PGSQL.5432" -dst "$REALPGHOST/.s.PGSQL.5432" &
SLOCAT_PID=$!
# shellcheck disable=SC2317
stop_slocat() {
kill "$SLOCAT_PID" || true
wait "$SLOCAT_PID" || true
}
trap stop_slocat EXIT
sleep 1 # should wait for socket file to appear instead
("$_arg_command" "''${_arg_leftovers[@]}")
'';
withSlowPgrst =
checkedShellScript
{
name = "postgrest-with-slow-postgrest";
docs = "Run the given command with simulated high latency postgrest";
args =
[
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_USE_ENV([PGRST_SERVER_UNIX_SOCKET], [], [PostgREST host (socket name)])"
"ARG_USE_ENV([PGRST_DELAY], [0ms], [extra PostgREST latency (duration)])"
];
positionalCompletion = "_command";
workingDir = "/";
redirectTixFiles = false;
withTmpDir = true;
}
''
delay="''${PGRST_DELAY:-0ms}"
echo "delaying data to/from PostgREST by $delay"
REAL_PGRST_SERVER_UNIX_SOCKET="$PGRST_SERVER_UNIX_SOCKET"
export PGRST_SERVER_UNIX_SOCKET="$tmpdir/postgrest.socket"
${slocat}/bin/slocat -delay "$delay" -src "$PGRST_SERVER_UNIX_SOCKET" -dst "$REAL_PGRST_SERVER_UNIX_SOCKET" &
SLOCAT_PID=$!
# shellcheck disable=SC2317
stop_slocat() {
kill "$SLOCAT_PID" || true
wait "$SLOCAT_PID" || true
}
trap stop_slocat EXIT
sleep 1 # should wait for socket file to appear instead
("$_arg_command" "''${_arg_leftovers[@]}")
'';
withGit =
let
name = "postgrest-with-git";
@@ -346,15 +279,21 @@ let
'';
withPgrst =
let
commandName = "postgrest-with-pgrst";
in
checkedShellScript
{
name = "postgrest-with-pgrst";
name = commandName;
docs = "Build and run PostgREST and run <command> with PGRST_SERVER_UNIX_SOCKET set.";
args =
[
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_OPTIONAL_SINGLE([monitor], [m], [Enable CPU and memory monitoring of the PostgREST process and output to the designated file as markdown])"
"ARG_OPTIONAL_SINGLE([timeout], [t], [Maximum time to wait for PostgREST to be ready], [5])"
"ARG_OPTIONAL_SINGLE([sleep], [s], [Sleep time after PostgREST is ready, this is useful for monitoring])"
"ARG_USE_ENV([PGRST_CMD], [], [PostgREST executable to run])"
];
positionalCompletion = "_command";
workingDir = "/";
@@ -364,30 +303,34 @@ let
''
export PGRST_SERVER_UNIX_SOCKET="$tmpdir"/postgrest.socket
rm -f result
if [ -z "''${PGRST_BUILD_CABAL:-}" ]; then
echo -n "Building postgrest (nix)... "
# Using lib.getBin to also make this work with older checkouts, where .bin was not a thing, yet.
nix-build -E 'with import ./. {}; pkgs.lib.getBin postgrestPackage' > "$tmpdir"/build.log 2>&1 || {
echo "failed, output:"
cat "$tmpdir"/build.log
exit 1
}
PGRST_CMD=$(echo ./result*/bin/postgrest)
else
echo -n "Building postgrest (cabal)... "
postgrest-build
PGRST_CMD=postgrest-run
if [ -z "''${PGRST_CMD:-}" ]; then
rm -f result
build_start=$SECONDS
if [ -z "''${PGRST_BUILD_CABAL:-}" ]; then
echo -n "${commandName}: Building postgrest (nix)... "
# Using lib.getBin to also make this work with older checkouts, where .bin was not a thing, yet.
nix-build -E 'with import ./. {}; pkgs.lib.getBin postgrestPackage' > "$tmpdir"/build.log 2>&1 || {
echo "failed, output:"
cat "$tmpdir"/build.log
exit 1
}
PGRST_CMD=$(echo ./result*/bin/postgrest)
else
echo -n "${commandName}: Building postgrest (cabal)... "
postgrest-build
PGRST_CMD=postgrest-run
fi
build_end=$((SECONDS - build_start))
printf "done in %ss.\n" "$build_end"
fi
echo "done."
ver=$($PGRST_CMD ${legacyConfig} --version)
echo -n "Starting $ver... "
echo -n "${commandName}: Starting $ver... "
$PGRST_CMD ${legacyConfig} > "$tmpdir"/run.log 2>&1 &
pid=$!
# shellcheck disable=SC2317
# shellcheck disable=SC2329
cleanup() {
# Send INT to all postgrest processes.
# Workaround to trigger dumping postgrest.prof for postgrest-profiled-run
@@ -401,17 +344,25 @@ let
}
trap cleanup EXIT
timeout -s TERM 5 ${waitForPgrstReady} || {
wait_start=$SECONDS
timeout -s TERM "$_arg_timeout" ${waitForPgrstReady} || {
echo "timed out, output:"
cat "$tmpdir"/run.log
exit 1
}
echo "done."
wait_duration=$((SECONDS - wait_start))
printf "done in %ss.\n" "$wait_duration"
echo "${commandName}: You can tail the server logs with: tail -f $tmpdir/run.log"
if [[ -n "$_arg_monitor" ]]; then
${monitorPid} "$pid" > "$_arg_monitor" &
fi
if [[ -n "$_arg_sleep" ]]; then
sleep "$_arg_sleep"
fi
("$_arg_command" "''${_arg_leftovers[@]}")
'';
@@ -429,11 +380,9 @@ buildToolbox
inherit
withGit
withPgAll
withPgrst
withSlowPg
withSlowPgrst;
withPgrst;
} // builtins.listToAttrs (
# Create a `postgrest-with-postgresql-` for each PostgreSQL version
# Create a `postgrest-with-pg-` for each PostgreSQL version
builtins.map (pg: { inherit (pg) name; value = withTmpDb pg; }) postgresqlVersions
);
# make latest withPg available for other nix files
+119 -36
View File
@@ -1,28 +1,27 @@
cabal-version: 3.0
name: postgrest
version: 14.1
version: 15
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 methods that security
permits.
license: MIT
license-file: LICENSE
author: Joe Nelson, Adam Baker, Steve Chavez
author: Joe Nelson, Adam Baker, Steve Chavez, Wolfgang Walther
maintainer: Steve Chavez <stevechavezast@gmail.com>
category: Executable, PostgreSQL, Network APIs
homepage: https://postgrest.org
bug-reports: https://github.com/PostgREST/postgrest/issues
build-type: Simple
extra-source-files: CHANGELOG.md
cabal-version: >= 1.10
tested-with:
-- nix
GHC == 9.4.8
-- cabal on Ubuntu
-- stack on FreeBSD, MacOS, Ubuntu, Windows
, GHC == 9.6.7
, GHC == 9.10.3
-- cabal on Ubuntu
, GHC == 9.8.4
-- nix
, GHC == 9.12.3
source-repository head
type: git
@@ -55,6 +54,7 @@ library
PostgREST.Client
PostgREST.Config
PostgREST.Config.Database
PostgREST.Debounce
PostgREST.Config.JSPath
PostgREST.Config.PgVersion
PostgREST.Config.Proxy
@@ -66,6 +66,7 @@ library
PostgREST.SchemaCache.Representations
PostgREST.SchemaCache.Table
PostgREST.Error
PostgREST.Error.Types
PostgREST.Listener
PostgREST.Logger
PostgREST.MainTx
@@ -81,6 +82,7 @@ library
PostgREST.Plan
PostgREST.Plan.CallPlan
PostgREST.Plan.MutatePlan
PostgREST.Plan.Negotiate
PostgREST.Plan.ReadPlan
PostgREST.Plan.Types
PostgREST.RangeQuery
@@ -94,35 +96,35 @@ library
PostgREST.Response.OpenAPI
PostgREST.Response.GucHeader
PostgREST.Response.Performance
PostgREST.TimeIt
PostgREST.Version
build-depends: base >= 4.9 && < 4.20
build-depends: base >= 4.9 && < 4.22
, HTTP >= 4000.3.7 && < 4000.5
, Ranged-sets >= 0.3 && < 0.5
, Ranged-sets >= 0.3 && < 0.6
, aeson >= 2.0.3 && < 2.3
, auto-update >= 0.1.4 && < 0.3
, base64-bytestring >= 1 && < 1.3
, bytestring >= 0.10.8 && < 0.13
, cache >= 0.1.3 && < 0.2.0
, case-insensitive >= 1.2 && < 1.3
, cassava >= 0.4.5 && < 0.6
, clock >= 0.8.3 && < 0.9.0
, configurator-pg >= 0.2.11 && < 0.3
, containers >= 0.5.7 && < 0.7
, containers >= 0.5.7 && < 0.8
, cookie >= 0.4.2 && < 0.6
-- crypton 1.1.0 moved from `memory` to `ram`, which jose-jwt fails to build with right now.
-- should be possible to remove this once jose-jwt had a new release.
, crypton < 1.1.0
, directory >= 1.2.6 && < 1.4
, either >= 4.4.1 && < 5.1
, extra >= 1.7.0 && < 2.0
, fuzzyset >= 0.2.4 && < 0.3
, hasql >= 1.6.1.1 && < 1.7
, hasql-dynamic-statements >= 0.3.1 && < 0.4
, hasql-notifications >= 0.2.2.2 && < 0.2.3
, hasql-pool >= 1.0.1 && < 1.1
, hasql-transaction >= 1.0.1 && < 1.2
, heredoc >= 0.2 && < 0.3
, hasql >= 1.9 && <= 1.9.3.1
, hasql-dynamic-statements >= 0.3.1 && <= 0.3.1.8
, hasql-notifications >= 0.2.4.0 && < 0.3
, hasql-pool >= 1.1 && <= 1.3.0.4
, hasql-transaction >= 1.0.1 && <= 1.2.1
, http-client >= 0.7.19 && < 0.8
, http-types >= 0.12.2 && < 0.13
, insert-ordered-containers >= 0.2.2 && < 0.3
, iproute >= 1.7.0 && < 1.8
, jose-jwt >= 0.9.6 && < 0.11
, lens >= 4.14 && < 5.4
, lens-aeson >= 1.0.1 && < 1.3
@@ -132,18 +134,16 @@ library
, network-uri >= 2.6.1 && < 2.8
, optparse-applicative >= 0.13 && < 0.19
, parsec >= 3.1.11 && < 3.2
-- Technically unused, can be removed after updating to hasql >= 1.7
, postgresql-libpq >= 0.10
, prometheus-client >= 1.1.1 && < 1.2.0
, protolude >= 0.3.1 && < 0.4
, protolude
, regex-tdfa >= 1.2.2 && < 1.4
, retry >= 0.7.4 && < 0.10
, scientific >= 0.3.4 && < 0.4
, streaming-commons >= 0.2.3.1 && < 0.3
, swagger2 >= 2.4 && < 2.9
, text >= 1.2.2 && < 2.2
, time >= 1.6 && < 1.13
, timeit >= 2.0 && < 2.1
, time >= 1.6 && < 1.15
, unordered-containers >= 0.2.8 && < 0.3
, unix-compat >= 0.5.4 && < 0.8
, vault >= 0.3.1.5 && < 0.4
@@ -156,7 +156,7 @@ library
-- for unix sockets; this is tested in test/io/test_io.py. See
-- https://github.com/kazu-yamamoto/logger/commit/3a71ca70afdbb93d4ecf0083eeba1fbbbcab3fc3
, wai-logger >= 2.4.0
, warp >= 3.3.19 && < 3.5
, warp >= 3.4.13 && < 3.5
, stm >= 2.5 && < 3
, stm-hamt >= 1.2 && < 2
, focus >= 1.0 && < 2
@@ -180,16 +180,64 @@ library
build-depends:
unix
library protolude
visibility: private
default-language: Haskell2010
default-extensions: NoImplicitPrelude
FlexibleContexts
MultiParamTypeClasses
OverloadedStrings
hs-source-dirs: src/protolude
exposed-modules: Protolude
Protolude.Applicative
Protolude.Base
Protolude.Bifunctor
Protolude.Bool
Protolude.CallStack
Protolude.Conv
Protolude.ConvertText
Protolude.Debug
Protolude.Either
Protolude.Error
Protolude.Exceptions
Protolude.Functor
Protolude.List
Protolude.Monad
Protolude.Panic
Protolude.Partial
Protolude.Safe
Protolude.Semiring
Protolude.Show
Protolude.Unsafe
build-depends: array >= 0.4 && < 0.6
, async >= 2.0 && < 2.3
, base >= 4.6 && < 4.22
, bytestring >= 0.10.8 && < 0.13
, containers >= 0.5.7 && < 0.8
, deepseq >= 1.3 && < 1.6
, ghc-prim >= 0.3 && < 0.14
, hashable >= 1.2 && < 1.6
, mtl >= 2.1 && < 2.4
, mtl-compat >= 0.2 && < 0.3
, stm >= 2.5 && < 3
, text >= 1.2.2 && < 2.2
, transformers >= 0.2 && < 0.7
, transformers-compat >= 0.4 && < 0.8
-- Protolude has some partial functions, so
-- it is fine to disable that specific warning
ghc-options: -Werror -Wall -fwarn-identities -Wno-x-partial
-fno-spec-constr -optP-Wno-nonportable-include-path
executable postgrest
default-language: Haskell2010
default-extensions: OverloadedStrings
NoImplicitPrelude
hs-source-dirs: main
main-is: Main.hs
build-depends: base >= 4.9 && < 4.20
, containers >= 0.5.7 && < 0.7
build-depends: base >= 4.9 && < 4.22
, containers >= 0.5.7 && < 0.8
, postgrest
, protolude >= 0.3.1 && < 0.4
, protolude
ghc-options: -threaded -rtsopts "-with-rtsopts=-N -I0 -qg"
-O2 -Werror -Wall -fwarn-identities
-fno-spec-constr -optP-Wno-nonportable-include-path
@@ -215,7 +263,6 @@ test-suite spec
Feature.Auth.AudienceJwtSecretSpec
Feature.Auth.AuthSpec
Feature.Auth.BinaryJwtSecretSpec
Feature.Auth.JwtCacheSpec
Feature.Auth.NoAnonSpec
Feature.Auth.NoJwtSecretSpec
Feature.ConcurrentSpec
@@ -245,7 +292,9 @@ test-suite spec
Feature.Query.PgSafeUpdateSpec
Feature.Query.PlanSpec
Feature.Query.PostGISSpec
Feature.Query.PreferencesSpec
Feature.Query.Preferences.HandlingSpec
Feature.Query.Preferences.MaxAffectedSpec
Feature.Query.Preferences.TimezoneSpec
Feature.Query.QueryLimitedSpec
Feature.Query.QuerySpec
Feature.Query.RangeSpec
@@ -261,16 +310,16 @@ test-suite spec
Feature.RollbackSpec
Feature.RpcPreRequestGucsSpec
SpecHelper
build-depends: base >= 4.9 && < 4.20
build-depends: base >= 4.9 && < 4.22
, aeson >= 2.0.3 && < 2.3
, aeson-qq >= 0.8.1 && < 0.9
, async >= 2.1.1 && < 2.3
, base64-bytestring >= 1 && < 1.3
, bytestring >= 0.10.8 && < 0.13
, case-insensitive >= 1.2 && < 1.3
, containers >= 0.5.7 && < 0.7
, hasql-pool >= 1.0.1 && < 1.1
, hasql-transaction >= 1.0.1 && < 1.2
, containers >= 0.5.7 && < 0.8
, hasql-pool >= 1.0.1 && <= 1.3.0.4
, hasql-transaction >= 1.0.1 && <= 1.2.1
, heredoc >= 0.2 && < 0.3
, hspec >= 2.3 && < 2.12
, hspec-expectations >= 0.8.4 && < 0.9
@@ -284,7 +333,7 @@ test-suite spec
, postgrest
, process >= 1.4.2 && < 1.7
, prometheus-client >= 1.1.1 && < 1.2.0
, protolude >= 0.3.1 && < 0.4
, protolude
, regex-tdfa >= 1.2.2 && < 1.4
, scientific >= 0.3.4 && < 0.4
, text >= 1.2.2 && < 2.2
@@ -298,6 +347,40 @@ test-suite spec
-- https://github.com/PostgREST/postgrest/issues/387
-with-rtsopts=-K33K
test-suite observability
type: exitcode-stdio-1.0
default-language: Haskell2010
default-extensions: OverloadedStrings
QuasiQuotes
NoImplicitPrelude
hs-source-dirs: test/observability
main-is: Main.hs
other-modules: ObsHelper
Observation.JwtCache
Observation.MetricsSpec
Observation.SchemaCacheSpec
build-depends: base >= 4.9 && < 4.22
, base64-bytestring >= 1 && < 1.3
, bytestring >= 0.10.8 && < 0.13
, hasql-pool >= 1.0.1 && <= 1.3.0.4
, hasql-transaction >= 1.0.1 && <= 1.2.1
, hspec >= 2.3 && < 2.12
, hspec-expectations >= 0.8.4 && < 0.9
, hspec-wai >= 0.10 && < 0.12
, hspec-wai-json >= 0.10 && < 0.12
, http-types >= 0.12.3 && < 0.13
, jose-jwt >= 0.9.6 && < 0.11
, postgrest
, prometheus-client >= 1.1.1 && < 1.2.0
, protolude
, text >= 1.2.2 && < 2.2
, wai >= 3.2.1 && < 3.3
ghc-options: -threaded -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=-K33K
test-suite doctests
type: exitcode-stdio-1.0
default-language: Haskell2010
@@ -305,10 +388,10 @@ test-suite doctests
NoImplicitPrelude
hs-source-dirs: test/doc
main-is: Main.hs
build-depends: base >= 4.9 && < 4.20
build-depends: base >= 4.9 && < 4.22
, doctest >= 0.8
, postgrest
, pretty-simple
, protolude >= 0.3.1 && < 0.4
, protolude
ghc-options: -threaded -O0 -Werror -Wall -fwarn-identities
-fno-spec-constr -optP-Wno-nonportable-include-path
+5 -6
View File
@@ -7,11 +7,9 @@
# We highly recommend that use the PostgREST binary cache by installing cachix
# (https://app.cachix.org/) and running `cachix use postgrest`.
{ docker ? false
, postgrest ? import ./default.nix { }
}:
let
postgrest =
import ./default.nix { };
inherit (postgrest) pkgs;
inherit (pkgs) lib;
@@ -37,10 +35,7 @@ lib.overrideDerivation postgrest.env (
buildInputs =
base.buildInputs ++ [
pkgs.cabal-install
pkgs.cabal2nix
pkgs.git
pkgs.postgresql
pkgs.update-nix-fetchgit
postgrest.hsie.bin
]
++ toolboxes;
@@ -49,6 +44,10 @@ lib.overrideDerivation postgrest.env (
''
export HISTFILE=.history
# Bypass proxy for all hosts, it prevents HTTP client failures used in test
# suites. See: https://github.com/PostgREST/postgrest/issues/4633 for more info
export NO_PROXY=*
source ${pkgs.bash-completion}/etc/profile.d/bash_completion.sh
source ${pkgs.git}/share/git/contrib/completion/git-completion.bash
source ${postgrest.hsie.bash-completion}
+9 -8
View File
@@ -19,22 +19,23 @@ import PostgREST.Observation (Observation (..))
import qualified PostgREST.AppState as AppState
import Protolude
import qualified Network.Socket as NS
import Protolude
runAdmin :: AppState -> Warp.Settings -> IO ()
runAdmin appState settings = do
whenJust (AppState.getSocketAdmin appState) $ \adminSocket -> do
runAdmin :: AppState -> Maybe NS.Socket -> NS.Socket -> Warp.Settings -> IO ()
runAdmin appState maybeAdminSocket socketREST settings = do
whenJust maybeAdminSocket $ \adminSocket -> do
address <- resolveSocketToAddress adminSocket
observer $ AdminStartObs address
void . forkIO $ Warp.runSettingsSocket settings adminSocket adminApp
where
adminApp = admin appState
adminApp = admin appState socketREST
observer = AppState.getObserver appState
-- | PostgREST admin application
admin :: AppState.AppState -> Wai.Application
admin appState req respond = do
isMainAppReachable <- isRight <$> reachMainApp (AppState.getSocketREST appState)
admin :: AppState.AppState -> NS.Socket -> Wai.Application
admin appState socketREST req respond = do
isMainAppReachable <- isRight <$> reachMainApp socketREST
isLoaded <- AppState.isLoaded appState
isPending <- AppState.isPending appState
+1 -1
View File
@@ -64,7 +64,7 @@ data ApiRequest = ApiRequest {
, iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions
, iPreferences :: Preferences.Preferences -- ^ Prefer header values
, iQueryParams :: QueryParams.QueryParams
, iColumns :: S.Set FieldName -- ^ parsed colums from &columns parameter and payload
, iColumns :: S.Set FieldName -- ^ parsed columns from &columns parameter and payload
, iHeaders :: [(ByteString, ByteString)] -- ^ HTTP request headers
, iCookies :: [(ByteString, ByteString)] -- ^ Request Cookies
, iPath :: ByteString -- ^ Raw request path
+117 -37
View File
@@ -9,18 +9,25 @@ Some of its functionality includes:
- Producing HTTP Headers according to RFCs.
- Content Negotiation
-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}
module PostgREST.App
( postgrest
, run
) where
import GHC.IO.Exception (IOErrorType (..))
import System.IO.Error (ioeGetErrorType)
import Control.Monad.Except (liftEither)
import Control.Monad.Extra (whenJust)
import Data.Either.Combinators (mapLeft, whenLeft)
import Data.Maybe (fromJust)
import Data.String (IsString (..))
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
import Network.Wai.Handler.Warp (defaultSettings, setHost,
setOnException, setPort,
setServerName)
import qualified Data.Text.Encoding as T
@@ -51,35 +58,61 @@ import PostgREST.Observation (Observation (..))
import PostgREST.Response.Performance (ServerTiming (..),
serverTimingHeader)
import PostgREST.SchemaCache (SchemaCache (..))
import PostgREST.TimeIt (timeItT)
import PostgREST.Version (docsVersion, prettyVersion)
import qualified Data.ByteString.Char8 as BS
import qualified Data.List as L
import qualified Network.HTTP.Types as HTTP
import Protolude hiding (Handler)
import System.TimeIt (timeItT)
import qualified Data.ByteString.Char8 as BS
import qualified Data.List as L
import Data.Streaming.Network (bindPortTCP,
bindRandomPortTCP)
import qualified Data.Text as T
import qualified Network.HTTP.Types as HTTP
import qualified Network.HTTP.Types.Header as HTTP (hVary)
import qualified Network.Socket as NS
import PostgREST.Unix (createAndBindDomainSocket)
import Protolude hiding (Handler)
type Handler = ExceptT Error
run :: AppState -> IO ()
run appState = do
let observer = AppState.getObserver appState
conf@AppConfig{..} <- AppState.getConfig appState
AppState.schemaCacheLoader appState -- Loads the initial SchemaCache
Unix.installSignalHandlers (AppState.getMainThreadId appState) (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState)
(mainSocket, adminSocket) <- initSockets conf
let closeSockets = do
whenJust adminSocket NS.close
NS.close mainSocket
Unix.installSignalHandlers observer closeSockets (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState)
Listener.runListener appState
Admin.runAdmin appState (serverSettings conf)
Admin.runAdmin appState adminSocket mainSocket (serverSettings conf)
let app = postgrest configLogLevel appState (AppState.schemaCacheLoader appState)
do
address <- resolveSocketToAddress (AppState.getSocketREST appState)
address <- resolveSocketToAddress mainSocket
observer $ AppServerAddressObs address
Warp.runSettingsSocket (serverSettings conf) (AppState.getSocketREST appState) app
Warp.runSettingsSocket (serverSettings conf & setOnException onWarpException) mainSocket app
where
observer = AppState.getObserver appState
onWarpException :: Maybe Wai.Request -> SomeException -> IO ()
onWarpException _ ex =
when (shouldDisplayException ex) $
observer $ WarpServerObs $ show ex
-- Similar to wai defaultShouldDisplayException in
-- https://github.com/yesodweb/wai//blob/8c3882c60f6abe043889fc20c7efd3fa9747fa4a/warp/Network/Wai/Handler/Warp/Settings.hs#L251-L258
-- but without omitting AsyncException since it's important to log for ThreadKilled, StackOverflow and other cases.
-- We want to reuse this to avoid flooding the logs for some transient failure cases.
shouldDisplayException :: SomeException -> Bool
shouldDisplayException se
| Just (_ :: Warp.InvalidRequest) <- fromException se = False
| Just (ioeGetErrorType -> et) <- fromException se, et == ResourceVanished || et == InvalidArgument = False
| otherwise = True
serverSettings :: AppConfig -> Warp.Settings
serverSettings AppConfig{..} =
@@ -97,30 +130,31 @@ postgrest logLevel appState connWorker =
Logger.middleware logLevel Auth.getRole $
-- fromJust can be used, because the auth middleware will **always** add
-- some AuthResult to the vault.
\req respond -> case fromJust $ Auth.getResult req of
Left err -> respond $ Error.errorResponseFor err
Right authResult -> do
appConf <- AppState.getConfig appState -- the config must be read again because it can reload
maybeSchemaCache <- AppState.getSchemaCache appState
\req respond -> do
appConf@AppConfig{..} <- AppState.getConfig appState -- the config must be read again because it can reload
case fromJust $ Auth.getResult req of
Left err -> respond $ Error.errorResponseFor configClientErrorVerbosity err
Right authResult -> do
maybeSchemaCache <- AppState.getSchemaCache appState
let
eitherResponse :: IO (Either Error Wai.Response)
eitherResponse =
runExceptT $ postgrestResponse appState appConf maybeSchemaCache authResult req
let
eitherResponse :: IO (Either Error Wai.Response)
eitherResponse =
runExceptT $ postgrestResponse appState appConf maybeSchemaCache authResult req
response <- either Error.errorResponseFor identity <$> eitherResponse
-- Launch the connWorker when the connection is down. The postgrest
-- function can respond successfully (with a stale schema cache) before
-- the connWorker is done. However, when there's an empty schema cache
-- postgrest responds with the error `PGRST002`; this means that the schema
-- cache is still loading, so we don't launch the connWorker here because
-- it would duplicate the loading process, e.g. https://github.com/PostgREST/postgrest/issues/3704
-- TODO: this process may be unnecessary when the Listener is enabled. Revisit once https://github.com/PostgREST/postgrest/issues/1766 is done
when (isServiceUnavailable response && isJust maybeSchemaCache) connWorker
resp <- do
delay <- AppState.getNextDelay appState
return $ addRetryHint delay response
respond resp
response <- either (Error.errorResponseFor configClientErrorVerbosity) identity <$> eitherResponse
-- Launch the connWorker when the connection is down. The postgrest
-- function can respond successfully (with a stale schema cache) before
-- the connWorker is done. However, when there's an empty schema cache
-- postgrest responds with the error `PGRST002`; this means that the schema
-- cache is still loading, so we don't launch the connWorker here because
-- it would duplicate the loading process, e.g. https://github.com/PostgREST/postgrest/issues/3704
-- TODO: this process may be unnecessary when the Listener is enabled. Revisit once https://github.com/PostgREST/postgrest/issues/1766 is done
when (isServiceUnavailable response && isJust maybeSchemaCache) connWorker
resp <- do
delay <- AppState.getNextDelay appState
return $ addRetryHint delay response
respond resp
postgrestResponse
:: AppState.AppState
@@ -146,7 +180,7 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache authResult@AuthRe
timezones = dbTimezones sCache
prefs = ApiRequest.userPreferences conf req timezones
(parseTime, apiReq@ApiRequest{..}) <- withTiming $ liftEither . mapLeft Error.ApiRequestError $ ApiRequest.userApiRequest conf prefs req body
(parseTime, apiReq@ApiRequest{..}) <- withTiming $ liftEither . mapLeft Error.ApiRequestErr $ ApiRequest.userApiRequest conf prefs req body
(planTime, plan) <- withTiming $ liftEither $ Plan.actionPlan iAction conf apiReq sCache
let mainQ = Query.mainQuery plan conf apiReq authResult configDbPreRequest
@@ -167,7 +201,7 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache authResult@AuthRe
liftEither eitherResp
(respTime, resp) <- withTiming $ do
let response = Response.actionResponse txResult apiReq (T.decodeUtf8 prettyVersion, docsVersion) conf sCache iSchema iNegotiatedByProfile
let response = Response.actionResponse txResult apiReq (T.decodeUtf8 prettyVersion, docsVersion) conf sCache
status' = either Error.status Response.pgrstStatus response
-- TODO: see above obsQuery, only this obsQuery should remain after refactoring (because the QueryObs depends on the status)
@@ -178,7 +212,17 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache authResult@AuthRe
where
toWaiResponse :: ServerTiming -> Response.PgrstResponse -> Wai.Response
toWaiResponse timing (Response.PgrstResponse st hdrs bod) = Wai.responseLBS st (hdrs ++ ([serverTimingHeader timing | configServerTimingEnabled])) bod
toWaiResponse timing (Response.PgrstResponse st hdrs bod) =
Wai.responseLBS st (hdrs ++ serverTimingHeaders timing ++ [varyHeader | not $ varyHeaderPresent hdrs]) bod
serverTimingHeaders :: ServerTiming -> [HTTP.Header]
serverTimingHeaders timing = [serverTimingHeader timing | configServerTimingEnabled]
varyHeader :: HTTP.Header
varyHeader = (HTTP.hVary, "Accept, Prefer, Range")
varyHeaderPresent :: [HTTP.Header] -> Bool
varyHeaderPresent = any (\(h, _v) -> h == HTTP.hVary)
withTiming :: Handler IO a -> Handler IO (Maybe Double, a)
withTiming f = if configServerTimingEnabled
@@ -206,3 +250,39 @@ addRetryHint delay response = do
isServiceUnavailable :: Wai.Response -> Bool
isServiceUnavailable response = Wai.responseStatus response == HTTP.status503
type AppSockets = (NS.Socket, Maybe NS.Socket)
initSockets :: AppConfig -> IO AppSockets
initSockets AppConfig{..} = do
let
cfg'usp = configServerUnixSocket
cfg'uspm = configServerUnixSocketMode
cfg'host = configServerHost
cfg'port = configServerPort
cfg'adminHost = configAdminServerHost
cfg'adminPort = configAdminServerPort
sock <- case cfg'usp of
-- I'm not using `streaming-commons`' bindPath function here because it's not defined for Windows,
-- but we need to have runtime error if we try to use it in Windows, not compile time error
Just path -> createAndBindDomainSocket path cfg'uspm
Nothing -> do
(_, sock) <-
if cfg'port /= 0
then do
sock <- bindPortTCP cfg'port (fromString $ T.unpack cfg'host)
pure (cfg'port, sock)
else do
-- explicitly bind to a random port, returning bound port number
(num, sock) <- bindRandomPortTCP (fromString $ T.unpack cfg'host)
pure (num, sock)
pure sock
adminSock <- case cfg'adminPort of
Just adminPort -> do
adminSock <- bindPortTCP adminPort (fromString $ T.unpack cfg'adminHost)
pure $ Just adminSock
Nothing -> pure Nothing
pure (sock, adminSock)
+89 -131
View File
@@ -1,6 +1,7 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
module PostgREST.AppState
( AppState
@@ -13,11 +14,9 @@ module PostgREST.AppState
, getNextListenerDelay
, getTime
, getJwtCacheState
, getSocketREST
, getSocketAdmin
, init
, initSockets
, initWithPool
, putConfig -- For tests TODO refactoring
, putNextListenerDelay
, putSchemaCache
, putPgVersion
@@ -32,24 +31,21 @@ module PostgREST.AppState
import qualified Data.ByteString.Char8 as BS
import Data.Either.Combinators (whenLeft)
import qualified Data.Text as T (unpack)
import qualified Hasql.Pool as SQL
import qualified Hasql.Pool.Config as SQL
import qualified Hasql.Session as SQL
import qualified Hasql.Transaction.Sessions as SQL
import qualified Network.HTTP.Types.Status as HTTP
import qualified Network.Socket as NS
import qualified PostgREST.Auth.JwtCache as JwtCache
import qualified PostgREST.Error as Error
import qualified PostgREST.Logger as Logger
import qualified PostgREST.Metrics as Metrics
import PostgREST.Observation
import PostgREST.TimeIt (timeItT)
import PostgREST.Version (prettyVersion)
import System.TimeIt (timeItT)
import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate,
updateAction)
import Control.Debounce
import Control.Retry (RetryPolicy, RetryStatus (..), capDelay,
exponentialBackoff, retrying,
rsPreviousDelay)
@@ -59,21 +55,19 @@ import Data.Time.Clock (UTCTime, getCurrentTime)
import PostgREST.Auth.JwtCache (JwtCacheState, update)
import PostgREST.Config (AppConfig (..),
addFallbackAppName,
readAppConfig)
readAppConfig,
toConnectionSettings)
import PostgREST.Config.Database (queryDbSettings,
queryPgVersion,
queryRoleSettings)
import PostgREST.Config.PgVersion (PgVersion (..),
minimumPgVersion)
import PostgREST.Debounce (makeDebouncer)
import PostgREST.SchemaCache (SchemaCache (..),
querySchemaCache,
showSummary)
import PostgREST.SchemaCache.Identifiers (quoteQi)
import PostgREST.Unix (createAndBindDomainSocket)
import Data.Streaming.Network (bindPortTCP, bindRandomPortTCP)
import Data.String (IsString (..))
import Protolude
data AppState = AppState
@@ -84,7 +78,7 @@ data AppState = AppState
-- | Schema cache
, stateSchemaCache :: IORef (Maybe SchemaCache)
-- | The schema cache status
, stateSCacheStatus :: IORef SchemaCacheStatus
, stateSCacheStatus :: SchemaCacheStatus
-- | State of the LISTEN channel
, stateIsListenerOn :: IORef Bool
-- | starts the connection worker with a debounce
@@ -99,10 +93,6 @@ data AppState = AppState
, stateNextDelay :: IORef Int
-- | Keeps track of the next delay for the listener
, stateNextListenerDelay :: IORef Int
-- | Network socket for REST API
, stateSocketREST :: NS.Socket
-- | Network socket for the admin UI
, stateSocketAdmin :: Maybe NS.Socket
-- | Observation handler
, stateObserver :: ObservationHandler
-- | JWT Cache
@@ -111,13 +101,11 @@ data AppState = AppState
, stateMetrics :: Metrics.MetricsState
}
-- | Schema cache status
data SchemaCacheStatus
= SCLoaded
| SCPending
deriving Eq
type AppSockets = (NS.Socket, Maybe NS.Socket)
-- | Schema cache status.
-- Empty means pending and full means loaded.
newtype SchemaCacheStatus = SchemaCacheStatus
{ getSCStatusMVar :: MVar ()
}
init :: AppConfig -> IO AppState
init conf@AppConfig{configLogLevel, configDbPoolSize} = do
@@ -128,107 +116,74 @@ init conf@AppConfig{configLogLevel, configDbPoolSize} = do
observer $ AppStartObs prettyVersion
pool <- initPool conf observer
(sock, adminSock) <- initSockets conf
state' <- initWithPool (sock, adminSock) pool conf loggerState metricsState observer
pure state' { stateSocketREST = sock, stateSocketAdmin = adminSock}
initWithPool pool conf loggerState metricsState observer
initWithPool :: AppSockets -> SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO AppState
initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
initWithPool :: SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO AppState
initWithPool pool conf loggerState metricsState observer = mdo
appState <- AppState pool
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
<*> newIORef Nothing
<*> newIORef SCPending
<*> newSchemaCacheStatus
<*> newIORef False
<*> pure (pure ())
<*> makeDebouncer (retryingSchemaCacheLoad appState *> threadDelay 100000) -- 100ms cooldown
<*> newIORef conf
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
<*> myThreadId
<*> newIORef 0
<*> newIORef 1
<*> pure sock
<*> pure adminSock
<*> pure observer
<*> JwtCache.init conf observer
<*> pure loggerState
<*> pure metricsState
deb <-
let decisecond = 100000 in
mkDebounce defaultDebounceSettings
{ debounceAction = retryingSchemaCacheLoad appState
, debounceFreq = decisecond
, debounceEdge = leadingEdge -- runs the worker at the start and the end
}
return appState { debouncedSCacheLoader = deb}
return appState
destroy :: AppState -> IO ()
destroy = destroyPool
initSockets :: AppConfig -> IO AppSockets
initSockets AppConfig{..} = do
let
cfg'usp = configServerUnixSocket
cfg'uspm = configServerUnixSocketMode
cfg'host = configServerHost
cfg'port = configServerPort
cfg'adminHost = configAdminServerHost
cfg'adminPort = configAdminServerPort
sock <- case cfg'usp of
-- I'm not using `streaming-commons`' bindPath function here because it's not defined for Windows,
-- but we need to have runtime error if we try to use it in Windows, not compile time error
Just path -> createAndBindDomainSocket path cfg'uspm
Nothing -> do
(_, sock) <-
if cfg'port /= 0
then do
sock <- bindPortTCP cfg'port (fromString $ T.unpack cfg'host)
pure (cfg'port, sock)
else do
-- explicitly bind to a random port, returning bound port number
(num, sock) <- bindRandomPortTCP (fromString $ T.unpack cfg'host)
pure (num, sock)
pure sock
adminSock <- case cfg'adminPort of
Just adminPort -> do
adminSock <- bindPortTCP adminPort (fromString $ T.unpack cfg'adminHost)
pure $ Just adminSock
Nothing -> pure Nothing
pure (sock, adminSock)
initPool :: AppConfig -> ObservationHandler -> IO SQL.Pool
initPool AppConfig{..} observer = do
initPool cfg@AppConfig{..} observer = do
SQL.acquire $ SQL.settings
[ SQL.size configDbPoolSize
, SQL.acquisitionTimeout $ fromIntegral configDbPoolAcquisitionTimeout
, SQL.agingTimeout $ fromIntegral configDbPoolMaxLifetime
, SQL.idlenessTimeout $ fromIntegral configDbPoolMaxIdletime
, SQL.staticConnectionSettings (toUtf8 $ addFallbackAppName prettyVersion configDbUri)
, SQL.staticConnectionSettings $ toConnectionSettings identity cfg
, SQL.observationHandler $ observer . HasqlPoolObs
]
-- | Run an action with a database connection.
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} sess = do
observer PoolRequest
observer PoolRequest
res <- SQL.use statePool sess
res <- SQL.use statePool sess
observer PoolRequestFullfilled
observer PoolRequestFullfilled
whenLeft res (\case
SQL.AcquisitionTimeoutUsageError ->
observer $ PoolAcqTimeoutObs SQL.AcquisitionTimeoutUsageError
err@(SQL.ConnectionUsageError e) ->
let failureMessage = BS.unpack $ fromMaybe mempty e in
when (("FATAL: password authentication failed" `isInfixOf` failureMessage) || ("no password supplied" `isInfixOf` failureMessage)) $ do
observer $ ExitDBFatalError ServerAuthError err
killThread mainThreadId
err@(SQL.SessionUsageError (SQL.QueryError tpl _ (SQL.ResultError resultErr))) -> do
whenLeft res (\case
SQL.AcquisitionTimeoutUsageError ->
observer PoolAcqTimeoutObs
err@(SQL.ConnectionUsageError e) ->
let failureMessage = BS.unpack $ fromMaybe mempty e in
when (("FATAL: password authentication failed" `isInfixOf` failureMessage) || ("no password supplied" `isInfixOf` failureMessage)) $ do
observer $ ExitDBFatalError ServerAuthError err
killThread mainThreadId
err@(SQL.SessionUsageError (SQL.QueryError tpl _ (SQL.ResultError resultErr))) ->
handleResultError err tpl resultErr
err@(SQL.SessionUsageError (SQL.PipelineError (SQL.ResultError resultErr))) ->
-- Passing the empty template will not work for schema cache queries, see TODO further below.
handleResultError err mempty resultErr
err@(SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _))) ->
-- An error on the client-side, usually indicates problems with connection
observer $ QueryErrorCodeHighObs err
SQL.SessionUsageError (SQL.PipelineError (SQL.ClientError _)) -> pure ()
)
return res
where
handleResultError err tpl resultErr = do
case resultErr of
SQL.UnexpectedResult{} -> do
observer $ ExitDBFatalError ServerPgrstBug err
@@ -261,19 +216,17 @@ usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} ses
SQL.ServerError{} ->
when (Error.status (Error.PgError False err) >= HTTP.status500) $
observer $ QueryErrorCodeHighObs err
err@(SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _))) ->
-- An error on the client-side, usually indicates problems wth connection
observer $ QueryErrorCodeHighObs err
)
return res
-- | Flush the connection pool so that any future use of the pool will
-- use connections freshly established after this call.
-- | Emits PoolFlushed observation
flushPool :: AppState -> IO ()
flushPool AppState{..} = SQL.release statePool
flushPool AppState{..} = do
SQL.release statePool
stateObserver PoolFlushed
-- | Destroy the pool on shutdown.
-- | Differs from flushPool in not emiting PoolFlushed observation.
destroyPool :: AppState -> IO ()
destroyPool AppState{..} = SQL.release statePool
@@ -313,12 +266,6 @@ getTime = stateGetTime
getJwtCacheState :: AppState -> JwtCacheState
getJwtCacheState = stateJwtCache
getSocketREST :: AppState -> NS.Socket
getSocketREST = stateSocketREST
getSocketAdmin :: AppState -> Maybe NS.Socket
getSocketAdmin = stateSocketAdmin
getMainThreadId :: AppState -> ThreadId
getMainThreadId = stateMainThreadId
@@ -335,18 +282,15 @@ putIsListenerOn = atomicWriteIORef . stateIsListenerOn
isLoaded :: AppState -> IO Bool
isLoaded x = do
scacheStatus <- readIORef $ stateSCacheStatus x
scacheLoaded <- isSchemaCacheLoaded x
connEstablished <- isConnEstablished x
return $ scacheStatus == SCLoaded && connEstablished
return $ scacheLoaded && connEstablished
isPending :: AppState -> IO Bool
isPending x = do
scacheStatus <- readIORef $ stateSCacheStatus x
scacheLoaded <- isSchemaCacheLoaded x
connEstablished <- isConnEstablished x
return $ scacheStatus == SCPending || not connEstablished
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
return $ not scacheLoaded || not connEstablished
getObserver :: AppState -> ObservationHandler
getObserver = stateObserver
@@ -364,9 +308,6 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
when (rsIterNumber > 0) $ do
let delay = fromMaybe 0 rsPreviousDelay `div` oneSecondInUs
observer $ ConnectionRetryObs delay
putNextListenerDelay appState delay
flushPool appState
(,) <$> qPgVersion <*> (qInDbConfig *> qSchemaCache)
)
@@ -374,7 +315,7 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
qPgVersion :: IO (Maybe PgVersion)
qPgVersion = do
AppConfig{..} <- getConfig appState
pgVersion <- usePool appState (queryPgVersion False) -- No need to prepare the query here, as the connection might not be established
pgVersion <- usePool appState queryPgVersion
case pgVersion of
Left e -> do
observer $ QueryPgVersionError e
@@ -382,14 +323,16 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
observer ExitDBNoRecoveryObs
killThread mainThreadId
return Nothing
Right actualPgVersion -> do
when (actualPgVersion < minimumPgVersion) $ do
Right actualPgVersion ->
if actualPgVersion < minimumPgVersion then do
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
killThread mainThreadId
observer $ DBConnectedObs $ pgvFullName actualPgVersion
observer $ PoolInit configDbPoolSize
putPgVersion appState actualPgVersion
return $ Just actualPgVersion
return Nothing
else do
observer $ DBConnectedObs $ pgvFullName actualPgVersion
observer $ PoolInit configDbPoolSize
putPgVersion appState actualPgVersion
return $ Just actualPgVersion
qInDbConfig :: IO ()
qInDbConfig = do
@@ -400,24 +343,27 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
qSchemaCache = do
conf@AppConfig{..} <- getConfig appState
(resultTime, result) <-
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
timeItT $ usePool appState (SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
case result of
Left e -> do
putSCacheStatus appState SCPending
markSchemaCachePending appState
putSchemaCache appState Nothing
observer $ SchemaCacheErrorObs configDbSchemas configDbExtraSearchPath e
return Nothing
Right sCache -> do
-- IMPORTANT: While the pending schema cache state starts from running the above querySchemaCache, only at this stage we block API requests due to the usage of an
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
putSCacheStatus appState SCPending
-- IORef on putSchemaCache. This is why schema cache status is marked as pending here to signal the Admin server (using isPending) that we're on a recovery state.
markSchemaCachePending appState
putSchemaCache appState $ Just sCache
observer $ SchemaCacheQueriedObs resultTime
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
observer $ SchemaCacheLoadedObs t
putSCacheStatus appState SCLoaded
(loadTime, summary) <- timeItT (evaluate $ showSummary sCache)
-- Flush the pool after loading the schema cache to reset any stale session cache entries
-- We do it after successfully querying the schema cache (because this can fail and during retries we would flush the pool repeatedly unnecessarily)
-- and after marking sCacheStatus as pending,
flushPool appState
observer $ SchemaCacheQueriedObs resultTime $ dbQueryTimings sCache
observer $ SchemaCacheLoadedObs loadTime summary
markSchemaCacheLoaded appState
return $ Just sCache
shouldRetry :: RetryStatus -> (Maybe PgVersion, Maybe SchemaCache) -> IO Bool
@@ -433,6 +379,18 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
oneSecondInUs = 1000000 -- one second in microseconds
newSchemaCacheStatus :: IO SchemaCacheStatus
newSchemaCacheStatus = SchemaCacheStatus <$> newEmptyMVar
markSchemaCachePending :: AppState -> IO ()
markSchemaCachePending = void . tryTakeMVar . getSCStatusMVar . stateSCacheStatus
markSchemaCacheLoaded :: AppState -> IO ()
markSchemaCacheLoaded = void . (`tryPutMVar` ()) . getSCStatusMVar . stateSCacheStatus
isSchemaCacheLoaded :: AppState -> IO Bool
isSchemaCacheLoaded = fmap not . isEmptyMVar . getSCStatusMVar . stateSCacheStatus
-- | Reads the in-db config and reads the config file again
-- | We don't retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue.
readInDbConfig :: Bool -> AppState -> IO ()
@@ -441,7 +399,7 @@ readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
pgVer <- getPgVersion appState
dbSettings <-
if configDbConfig conf then do
qDbSettings <- usePool appState (queryDbSettings (quoteQi <$> configDbPreConfig conf) (configDbPreparedStatements conf))
qDbSettings <- usePool appState (queryDbSettings (quoteQi <$> configDbPreConfig conf))
case qDbSettings of
Left e -> do
observer $ ConfigReadErrorObs e
@@ -451,7 +409,7 @@ readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
pure mempty
(roleSettings, roleIsolationLvl) <-
if configDbConfig conf then do
rSettings <- usePool appState (queryRoleSettings pgVer (configDbPreparedStatements conf))
rSettings <- usePool appState (queryRoleSettings pgVer)
case rSettings of
Left e -> do
observer $ QueryRoleSettingsErrorObs e
+1 -1
View File
@@ -25,8 +25,8 @@ import qualified Network.Wai as Wai
import qualified Network.Wai.Middleware.HttpAuth as Wai
import Data.List (lookup)
import PostgREST.TimeIt (timeItT)
import System.IO.Unsafe (unsafePerformIO)
import System.TimeIt (timeItT)
import PostgREST.AppState (AppState, getConfig, getJwtCacheState,
getTime)
+6 -32
View File
@@ -16,14 +16,10 @@ module PostgREST.Auth.Jwt
, parseClaims) where
import qualified Data.Aeson as JSON
import qualified Data.Aeson.Key as K
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.Scientific as Sci
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Jose.Jwk as JWT
import qualified Jose.Jwt as JWT
@@ -33,12 +29,11 @@ import Data.Text ()
import Data.Time.Clock (UTCTime, nominalDiffTimeToSeconds)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import PostgREST.Auth.Types (AuthResult (..))
import PostgREST.Config (AppConfig (..), FilterExp (..), JSPath,
JSPathExp (..), audMatchesCfg)
import PostgREST.Error (Error (..),
JwtClaimsError (AudClaimNotStringOrArray, ExpClaimNotNumber, IatClaimNotNumber, JWTExpired, JWTIssuedAtFuture, JWTNotInAudience, JWTNotYetValid, NbfClaimNotNumber, ParsingClaimsFailed),
JwtDecodeError (..), JwtError (..))
import PostgREST.Auth.Types (AuthResult (..))
import PostgREST.Config (AppConfig (..), audMatchesCfg)
import PostgREST.Config.JSPath (walkJSPath)
import PostgREST.Error (Error (..), JwtClaimsError (..),
JwtDecodeError (..), JwtError (..))
import Data.Aeson ((.:?))
import Data.Aeson.Types (parseMaybe)
@@ -95,13 +90,10 @@ checkForErrors time audMatches = mconcat
parseToken :: (MonadError Error m, MonadIO m) => JwkSet -> ByteString -> m JWT.JwtContent
parseToken _ "" = throwError $ JwtErr $ JwtDecodeErr EmptyAuthHeader
parseToken secret tkn = do
-- secret <- liftEither . maybeToRight (JwtErr JwtSecretMissing) $ configJWKS
tknWith3Parts <- hasThreeParts tkn
eitherContent <- liftIO $ JWT.decode (JWT.keys secret) Nothing tknWith3Parts
liftEither . mapLeft (JwtErr . jwtDecodeError) $ eitherContent
--liftEither $ mapLeft JwtErr $ verifyClaims content
where
--hasThreeParts :: ByteString -> Either Error ByteString
hasThreeParts token = case length $ BS.split (BS.c2w '.') token of
3 -> pure token
n -> throwError $ JwtErr $ JwtDecodeErr $ UnexpectedParts n
@@ -124,28 +116,10 @@ parseClaims cfg@AppConfig{configJwtRoleClaimKey, configDbAnonRole} time mclaims
role <- liftEither . maybeToRight (JwtErr JwtTokenRequired) $
unquoted <$> walkJSPath (Just $ JSON.Object mclaims) configJwtRoleClaimKey <|> configDbAnonRole
pure AuthResult
{ authClaims = mclaims & KM.insert "role" (JSON.toJSON $ decodeUtf8 role)
{ authClaims = mclaims
, authRole = role
}
where
walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value
walkJSPath x [] = x
walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (KM.lookup (K.fromText key) o) rest
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
walkJSPath (Just (JSON.Array ar)) [JSPFilter (EqualsCond txt)] = findFirstMatch (==) txt ar
walkJSPath (Just (JSON.Array ar)) [JSPFilter (NotEqualsCond txt)] = findFirstMatch (/=) txt ar
walkJSPath (Just (JSON.Array ar)) [JSPFilter (StartsWithCond txt)] = findFirstMatch T.isPrefixOf txt ar
walkJSPath (Just (JSON.Array ar)) [JSPFilter (EndsWithCond txt)] = findFirstMatch T.isSuffixOf txt ar
walkJSPath (Just (JSON.Array ar)) [JSPFilter (ContainsCond txt)] = findFirstMatch T.isInfixOf txt ar
walkJSPath _ _ = Nothing
findFirstMatch matchWith pattern = foldr checkMatch Nothing
where
checkMatch (JSON.String txt) acc
| pattern `matchWith` txt = Just $ JSON.String txt
| otherwise = acc
checkMatch _ acc = acc
unquoted :: JSON.Value -> BS.ByteString
unquoted (JSON.String t) = encodeUtf8 t
unquoted v = LBS.toStrict $ JSON.encode v
+3 -1
View File
@@ -6,7 +6,9 @@ import qualified Data.Aeson as JSON
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString as BS
-- | Parse result for JWT Claims
-- |
-- Parse and store result for JWT Claims. Can be accessed in
-- db through GUCs (for RLS etc)
data AuthResult = AuthResult
{ authClaims :: KM.KeyMap JSON.Value
, authRole :: BS.ByteString
+1 -3
View File
@@ -62,9 +62,7 @@ dumpSchema :: AppState -> IO LBS.ByteString
dumpSchema appState = do
conf@AppConfig{..} <- AppState.getConfig appState
result <-
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
AppState.usePool appState
(transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
AppState.usePool appState (SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
case result of
Left e -> do
let observer = AppState.getObserver appState
+78 -22
View File
@@ -9,6 +9,7 @@ Description : Manages PostgREST configuration type and parser.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE NamedFieldPuns #-}
module PostgREST.Config
( AppConfig (..)
@@ -27,21 +28,25 @@ module PostgREST.Config
, parseSecret
, addFallbackAppName
, addTargetSessionAttrs
, toConnectionSettings
, exampleConfigFile
, audMatchesCfg
, Verbosity (..)
) where
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as B64
import qualified Data.CaseInsensitive as CI
import qualified Data.Configurator as C
import qualified Data.Map.Strict as M
import qualified Data.String as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Jose.Jwa as JWT
import qualified Jose.Jwk as JWT
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as B64
import qualified Data.CaseInsensitive as CI
import qualified Data.Configurator as C
import qualified Data.Map.Strict as M
import qualified Data.String as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Hasql.Connection.Setting as SQL
import qualified Hasql.Connection.Setting.Connection as SQL
import qualified Jose.Jwa as JWT
import qualified Jose.Jwk as JWT
import Control.Monad (fail)
import Data.Either.Combinators (mapLeft)
@@ -63,16 +68,18 @@ import PostgREST.Config.JSPath (FilterExp (..), JSPath,
pRoleClaimKey)
import PostgREST.Config.Proxy (Proxy (..),
isMalformedProxyUri, toURI)
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier, dumpQi,
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
toQi)
import Protolude hiding (Proxy, toList)
import PostgREST.Version (prettyVersion)
import Protolude hiding (Proxy, toList)
audMatchesCfg :: AppConfig -> Text -> Bool
audMatchesCfg = maybe (const True) (==) . configJwtAudience
data AppConfig = AppConfig
{ configAppSettings :: [(Text, Text)]
, configClientErrorVerbosity :: Verbosity
, configDbAggregates :: Bool
, configDbAnonRole :: Maybe BS.ByteString
, configDbChannel :: Text
@@ -92,6 +99,7 @@ data AppConfig = AppConfig
, configDbSchemas :: NonEmpty Text
, configDbConfig :: Bool
, configDbPreConfig :: Maybe QualifiedIdentifier
, configDbTimezoneEnabled :: Bool
, configDbTxAllowOverride :: Bool
, configDbTxRollbackAll :: Bool
, configDbUri :: Text
@@ -134,6 +142,15 @@ dumpLogLevel = \case
LogInfo -> "info"
LogDebug -> "debug"
data Verbosity
= Minimal
| Verbose
dumpClientErrorVerbosity :: Verbosity -> Text
dumpClientErrorVerbosity = \case
Minimal -> "minimal"
Verbose -> "verbose"
data OpenAPIMode = OAFollowPriv | OAIgnorePriv | OADisabled
deriving Eq
@@ -150,7 +167,8 @@ toText conf =
where
-- apply conf to all pgrst settings
pgrstSettings = (\(k, v) -> (k, v conf)) <$>
[("db-aggregates-enabled", T.toLower . show . configDbAggregates)
[("client-error-verbosity", q . dumpClientErrorVerbosity . configClientErrorVerbosity)
,("db-aggregates-enabled", T.toLower . show . configDbAggregates)
,("db-anon-role", q . T.decodeUtf8 . fromMaybe "" . configDbAnonRole)
,("db-channel", q . configDbChannel)
,("db-channel-enabled", T.toLower . show . configDbChannelEnabled)
@@ -169,6 +187,7 @@ toText conf =
,("db-schemas", q . T.intercalate "," . toList . configDbSchemas)
,("db-config", T.toLower . show . configDbConfig)
,("db-pre-config", q . maybe mempty dumpQi . configDbPreConfig)
,("db-timezone-enabled", T.toLower . show . configDbTimezoneEnabled)
,("db-tx-end", q . showTxEnd)
,("db-uri", q . configDbUri)
,("jwt-aud", q . fromMaybe mempty . configJwtAudience)
@@ -198,6 +217,10 @@ toText conf =
-- quote strings and replace " with \"
q s = "\"" <> T.replace "\"" "\\\"" s <> "\""
dumpQi :: QualifiedIdentifier -> Text
dumpQi (QualifiedIdentifier s i) =
(if T.null s then mempty else s <> ".") <> i
showTxEnd c = case (configDbTxRollbackAll c, configDbTxAllowOverride c) of
( False, False ) -> "commit"
( False, True ) -> "commit-allow-override"
@@ -250,6 +273,7 @@ parser :: Maybe FilePath -> Environment -> [(Text, Text)] -> RoleSettings -> Rol
parser optPath env dbSettings roleSettings roleIsolationLvl =
AppConfig
<$> parseAppSettings "app.settings"
<*> parseErrorVerbosity "client-error-verbosity"
<*> (fromMaybe False <$> optBool "db-aggregates-enabled")
<*> (fmap encodeUtf8 <$> optString "db-anon-role")
<*> (fromMaybe "pgrst" <$> optString "db-channel")
@@ -270,10 +294,10 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> (fromMaybe True <$> optBool "db-prepared-statements")
<*> (fmap toQi <$> optWithAlias (optString "db-root-spec")
(optString "root-spec"))
<*> (fromList . maybe ["public"] splitOnCommas <$> optWithAlias (optString "db-schemas")
(optString "db-schema"))
<*> parseDbSchemas "db-schemas" "db-schema"
<*> (fromMaybe True <$> optBool "db-config")
<*> (fmap toQi <$> optString "db-pre-config")
<*> (fromMaybe True <$> optBool "db-timezone-enabled")
<*> parseTxEnd "db-tx-end" snd
<*> parseTxEnd "db-tx-end" fst
<*> (fromMaybe "postgresql://" <$> optString "db-uri")
@@ -307,6 +331,14 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> optInt "internal-schema-cache-load-sleep"
<*> optInt "internal-schema-cache-relationship-load-sleep"
where
parseErrorVerbosity :: C.Key -> C.Parser C.Config Verbosity
parseErrorVerbosity k =
optString k >>= \case
Nothing -> pure Verbose -- default
Just "minimal" -> pure Minimal
Just "verbose" -> pure Verbose
Just _ -> fail "Invalid client-error-verbosity. Check your configuration."
parseAppSettings :: C.Key -> C.Parser C.Config [(Text, Text)]
parseAppSettings key = addFromEnv . fmap (fmap coerceText) <$> C.subassocs key C.value
where
@@ -325,6 +357,18 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
Just asp | asp == serverPort -> fail "admin-server-port cannot be the same as server-port"
| otherwise -> pure $ Just asp
parseDbSchemas :: C.Key -> C.Key -> C.Parser C.Config (NonEmpty Text)
parseDbSchemas k al =
optWithAlias (optString k) (optString al) >>= \case
Nothing -> pure $ fromList ["public"]
Just s
| "pg_catalog" `elem` schemas -> fail (errMsg "pg_catalog")
| "information_schema" `elem` schemas -> fail (errMsg "information_schema")
| otherwise -> pure $ fromList schemas
where
schemas = splitOnCommas s
errMsg x = "db-schemas does not allow schema: '" <> x <> "'"
parseSocketFileMode :: C.Key -> C.Parser C.Config FileMode
parseSocketFileMode k =
optString k >>= \case
@@ -577,10 +621,10 @@ pgConnString conn | uriDesignator `T.isPrefixOf` conn || shortUriDesignator `T.i
-- >>> addFallbackAppName ver "postgres://admin2:?pass?special?@localhost:5432/postgres"
-- "postgres://admin2:?pass?special?@localhost:5432/postgres?fallback_application_name=PostgREST%2011.1.0%20%285a04ec7%29"
--
-- addFallbackAppName ver "postgresql://?dbname=postgres&host=/run/user/1000/postgrest/postgrest-with-postgresql-16-BuR/socket&user=some_protected_user&password=invalid_pass"
-- >>> addFallbackAppName ver "postgresql://?dbname=postgres&host=/run/user/1000/postgrest/postgrest-with-postgresql-16-BuR/socket&user=some_protected_user&password=invalid_pass"
-- "postgresql://?dbname=postgres&host=/run/user/1000/postgrest/postgrest-with-postgresql-16-BuR/socket&user=some_protected_user&password=invalid_pass&fallback_application_name=PostgREST%2011.1.0%20%285a04ec7%29"
--
-- addFallbackAppName ver "postgresql:///postgres?host=/run/user/1000/postgrest/postgrest-with-postgresql-16-BuR/socket&user=some_protected_user&password=invalid_pass"
-- >>> addFallbackAppName ver "postgresql:///postgres?host=/run/user/1000/postgrest/postgrest-with-postgresql-16-BuR/socket&user=some_protected_user&password=invalid_pass"
-- "postgresql:///postgres?host=/run/user/1000/postgrest/postgrest-with-postgresql-16-BuR/socket&user=some_protected_user&password=invalid_pass&fallback_application_name=PostgREST%2011.1.0%20%285a04ec7%29"
addFallbackAppName :: ByteString -> Text -> Text
addFallbackAppName version dbUri = addConnStringOption dbUri "fallback_application_name" pgrstVer
@@ -607,6 +651,12 @@ addFallbackAppName version dbUri = addConnStringOption dbUri "fallback_applicati
addTargetSessionAttrs :: Text -> Text
addTargetSessionAttrs dbUri = addConnStringOption dbUri "target_session_attrs" "read-write"
toConnectionSettings :: (Text -> Text) -> AppConfig -> [SQL.Setting]
toConnectionSettings transformUri AppConfig{configDbUri, configDbPreparedStatements} =
[ SQL.connection $ SQL.string $ transformUri . addFallbackAppName prettyVersion $ configDbUri
, SQL.usePreparedStatements configDbPreparedStatements
]
addConnStringOption :: Text -> Text -> Text -> Text
addConnStringOption dbUri key val = dbUri <>
case pgConnString dbUri of
@@ -627,6 +677,9 @@ exampleConfigFile = S.unlines
[ "## Admin server used for checks. It's disabled by default unless a port is specified."
, "# admin-server-port = 3001"
, ""
, "# PostgREST error json verbosity config"
, "# client-error-verbosity = \"verbose\""
, ""
, "## The database role to use when no client authentication is provided"
, "# db-anon-role = \"anon\""
, ""
@@ -676,16 +729,19 @@ exampleConfigFile = S.unlines
, "## The name of which database schema to expose to REST clients"
, "db-schemas = \"public\""
, ""
, "## Enable quering pg_timezone_names from db"
, "# db-timezone-enabled = true"
, ""
, "## How to terminate database transactions"
, "## Possible values are:"
, "## commit (default)"
, "## Transaction is always committed, this can not be overriden"
, "## Transaction is always committed, this can not be overridden"
, "## commit-allow-override"
, "## Transaction is committed, but can be overriden with Prefer tx=rollback header"
, "## Transaction is committed, but can be overridden with Prefer tx=rollback header"
, "## rollback"
, "## Transaction is always rolled back, this can not be overriden"
, "## Transaction is always rolled back, this can not be overridden"
, "## rollback-allow-override"
, "## Transaction is rolled back, but can be overriden with Prefer tx=commit header"
, "## Transaction is rolled back, but can be overridden with Prefer tx=commit header"
, "db-tx-end = \"commit\""
, ""
, "## The standard connection URI format, documented at"
+13 -13
View File
@@ -46,6 +46,7 @@ dbSettingsNames :: [Text]
dbSettingsNames =
(prefix <>) <$>
["db_aggregates_enabled"
,"client_error_verbosity"
,"db_anon_role"
,"db_pre_config"
,"db_extra_search_path"
@@ -55,6 +56,7 @@ dbSettingsNames =
,"db_prepared_statements"
,"db_root_spec"
,"db_schemas"
,"db_timezone_enabled"
,"db_tx_end"
,"db_hoisted_tx_settings"
,"jwt_aud"
@@ -70,8 +72,8 @@ dbSettingsNames =
,"server_timing_enabled"
]
queryPgVersion :: Bool -> Session PgVersion
queryPgVersion prepared = statement mempty $ pgVersionStatement prepared
queryPgVersion :: Session PgVersion
queryPgVersion = statement mempty $ pgVersionStatement False
pgVersionStatement :: Bool -> SQL.Statement () PgVersion
pgVersionStatement = SQL.Statement sql HE.noParams versionRow
@@ -90,10 +92,9 @@ pgVersionStatement = SQL.Statement sql HE.noParams versionRow
--
-- The example above will result in <prefix>jwt_aud = 'val'
-- A setting on the database only will have no effect: ALTER DATABASE postgres SET <prefix>jwt_aud = 'xx'
queryDbSettings :: Maybe Text -> Bool -> Session [(Text, Text)]
queryDbSettings preConfFunc prepared =
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
transaction SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ SQL.Statement sql (arrayParam HE.text) decodeSettings prepared
queryDbSettings :: Maybe Text -> Session [(Text, Text)]
queryDbSettings preConfFunc =
SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ SQL.Statement sql (arrayParam HE.text) decodeSettings True
where
sql = encodeUtf8 [trimming|
WITH
@@ -101,7 +102,7 @@ queryDbSettings preConfFunc prepared =
SELECT setdatabase as database,
unnest(setconfig) as setting
FROM pg_catalog.pg_db_role_setting
WHERE setrole = CURRENT_USER::regrole::oid
WHERE setrole = quote_ident(CURRENT_USER)::regrole::oid
AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG))
),
kv_settings AS (
@@ -131,10 +132,9 @@ queryDbSettings preConfFunc prepared =
|]::Text
decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text
queryRoleSettings :: PgVersion -> Bool -> Session (RoleSettings, RoleIsolationLvl)
queryRoleSettings pgVer prepared =
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
transaction SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ SQL.Statement sql HE.noParams (processRows <$> rows) prepared
queryRoleSettings :: PgVersion -> Session (RoleSettings, RoleIsolationLvl)
queryRoleSettings pgVer =
SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ SQL.Statement sql HE.noParams (processRows <$> rows) True
where
sql = encodeUtf8 [trimming|
with
@@ -142,7 +142,7 @@ queryRoleSettings pgVer prepared =
select r.rolname, unnest(r.rolconfig) as setting
from pg_auth_members m
join pg_roles r on r.oid = m.roleid
where member = current_user::regrole::oid
where member = quote_ident(current_user)::regrole::oid
),
kv_settings AS (
SELECT
@@ -167,7 +167,7 @@ queryRoleSettings pgVer prepared =
|]
hasParameterPrivilege
| pgVer >= pgVersion150 = "or has_parameter_privilege(current_user::regrole::oid, ps.name, 'set')"
| pgVer >= pgVersion150 = "or has_parameter_privilege(quote_ident(current_user)::regrole::oid, ps.name, 'set')"
| otherwise = ""
processRows :: [(Text, Maybe Text, [(Text, Text)])] -> (RoleSettings, RoleIsolationLvl)
+55 -5
View File
@@ -1,12 +1,19 @@
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# LANGUAGE LambdaCase #-}
module PostgREST.Config.JSPath
( JSPath
, JSPathExp(..)
, FilterExp(..)
, dumpJSPath
, pRoleClaimKey
, walkJSPath
) where
import qualified Data.Aeson as JSON
import qualified Data.Aeson.Key as K
import qualified Data.Aeson.KeyMap as KM
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Text.ParserCombinators.Parsec as P
import Data.Either.Combinators (mapLeft)
@@ -22,9 +29,10 @@ type JSPath = [JSPathExp]
-- NOTE: We only accept one JSPFilter expr (at the end of input)
-- | jspath expression
data JSPathExp
= JSPKey Text -- .property or ."property-dash"
| JSPIdx Int -- [0]
| JSPFilter FilterExp -- [?(@ == "match")]
= JSPKey Text -- .property or ."property-dash"
| JSPIdx Int -- [0]
| JSPSlice (Maybe Int) (Maybe Int) -- [0:5] or [0:] or [:5] or [:]
| JSPFilter FilterExp -- [?(@ == "match")]
data FilterExp
= EqualsCond Text
@@ -37,6 +45,7 @@ dumpJSPath :: JSPathExp -> Text
-- TODO: this needs to be quoted properly for special chars
dumpJSPath (JSPKey k) = "." <> show k
dumpJSPath (JSPIdx i) = "[" <> show i <> "]"
dumpJSPath (JSPSlice s e) = "[" <> maybe "" show s <> ":" <> maybe "" show e <> "]"
dumpJSPath (JSPFilter cond) = "[?(@" <> expr <> ")]"
where
expr =
@@ -47,6 +56,35 @@ dumpJSPath (JSPFilter cond) = "[?(@" <> expr <> ")]"
EndsWithCond text -> " ==^ " <> show text
ContainsCond text -> " *== " <> show text
-- | Evaluate JSPath on a JSON
walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value
walkJSPath x [] = x
walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (KM.lookup (K.fromText key) o) rest
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
walkJSPath (Just (JSON.String str)) (JSPSlice start end:rest) =
let
len = T.length str
norm :: Maybe Int -> Maybe Int -- Normalize negative indices to positive
norm = fmap (\i -> max 0 $ min len $ if i < 0 then len + i else i)
s = fromMaybe 0 $ norm start -- normalized start index
e = fromMaybe len $ norm end -- normalized end index
slicedString = if s >= e then T.empty else T.take (e-s) $ T.drop s str
in
walkJSPath (Just $ JSON.String slicedString) rest
walkJSPath (Just (JSON.Array ar)) (JSPFilter jspFilter:rest) = case jspFilter of
EqualsCond txt -> walkJSPath (findFirstMatch (==) txt ar) rest
NotEqualsCond txt -> walkJSPath (findFirstMatch (/=) txt ar) rest
StartsWithCond txt -> walkJSPath (findFirstMatch T.isPrefixOf txt ar) rest
EndsWithCond txt -> walkJSPath (findFirstMatch T.isSuffixOf txt ar) rest
ContainsCond txt -> walkJSPath (findFirstMatch T.isInfixOf txt ar) rest
where
findFirstMatch matchWith pattern = find (\case
JSON.String txt -> pattern `matchWith` txt
_ -> False)
walkJSPath _ _ = Nothing
-- Used for the config value "role-claim-key"
pRoleClaimKey :: Text -> Either Text JSPath
@@ -57,7 +95,7 @@ pJSPath :: P.Parser JSPath
pJSPath = P.many1 pJSPathExp <* P.eof
pJSPathExp :: P.Parser JSPathExp
pJSPathExp = pJSPKey <|> pJSPFilter <|> pJSPIdx
pJSPathExp = P.try pJSPKey <|> P.try pJSPFilter <|> P.try pJSPIdx <|> pJSPSlice
pJSPKey :: P.Parser JSPathExp
pJSPKey = do
@@ -72,13 +110,25 @@ pJSPIdx = do
P.char ']'
return (JSPIdx num) <?> "pJSPIdx: JSPath array index"
pJSPSlice :: P.Parser JSPathExp
pJSPSlice = do
P.char '['
startSign <- P.optionMaybe $ P.char '-'
startIndex <- P.optionMaybe (read <$> P.many1 P.digit)
P.char ':'
endSign <- P.optionMaybe $ P.char '-'
endIndex <- P.optionMaybe (read <$> P.many1 P.digit)
P.char ']'
let start' = if isJust startSign then ((-1) *) <$> startIndex else startIndex
end' = if isJust endSign then ((-1) *) <$> endIndex else endIndex
return (JSPSlice start' end') <?> "pJSPSlice: JSPath string slice"
pJSPFilter :: P.Parser JSPathExp
pJSPFilter = do
P.try $ P.string "[?("
condition <- pFilterConditionParser
P.char ')'
P.char ']'
P.eof -- this should be the last jspath expression
return (JSPFilter condition) <?> "pJSPFilter: JSPath filter exp"
pFilterConditionParser :: P.Parser FilterExp
+5 -5
View File
@@ -3,9 +3,9 @@
module PostgREST.Config.PgVersion
( PgVersion(..)
, minimumPgVersion
, pgVersion140
, pgVersion150
, pgVersion170
, pgVersion180
) where
import qualified Data.Aeson as JSON
@@ -25,10 +25,7 @@ instance Ord PgVersion where
-- | Tells the minimum PostgreSQL version required by this version of PostgREST
minimumPgVersion :: PgVersion
minimumPgVersion = pgVersion130
pgVersion130 :: PgVersion
pgVersion130 = PgVersion 130000 "13.0" "13.0"
minimumPgVersion = pgVersion140
pgVersion140 :: PgVersion
pgVersion140 = PgVersion 140000 "14.0" "14.0"
@@ -38,3 +35,6 @@ pgVersion150 = PgVersion 150000 "15.0" "15.0"
pgVersion170 :: PgVersion
pgVersion170 = PgVersion 170000 "17.0" "17.0"
pgVersion180 :: PgVersion
pgVersion180 = PgVersion 180000 "18.0" "18.0"
+19
View File
@@ -0,0 +1,19 @@
module PostgREST.Debounce
( makeDebouncer) where
import Protolude
-- | Make a new debouncer action. An internal "worker" thread runs forever
-- ensuring "action" runs when the "trigger" is called. The "action" is only
-- executed once over a burst of calls.
makeDebouncer :: IO () -> IO (IO ())
makeDebouncer action = do
flag <- newEmptyMVar
let worker = forever $ do
takeMVar flag
action
trigger = void $ tryPutMVar flag ()
void $ forkIO worker
pure trigger
+94 -201
View File
@@ -3,6 +3,7 @@ Module : PostgREST.Error
Description : PostgREST error HTTP responses
-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module PostgREST.Error
@@ -41,6 +42,8 @@ import Network.HTTP.Types.Header (Header)
import PostgREST.MediaType (MediaType (..))
import qualified PostgREST.MediaType as MediaType
import PostgREST.Config (Verbosity (..))
import PostgREST.SchemaCache (SchemaCache (SchemaCache, dbTablesFuzzyIndex))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
Schema)
import PostgREST.SchemaCache.Relationship (Cardinality (..),
@@ -49,24 +52,40 @@ import PostgREST.SchemaCache.Relationship (Cardinality (..),
RelationshipsMap)
import PostgREST.SchemaCache.Routine (Routine (..),
RoutineParam (..))
import PostgREST.SchemaCache.Table (Table (..))
import PostgREST.Error.Types
import Protolude
-- | Encode Error to ByteString
errorPayload :: (ErrorBody a, ErrorHeaders a) => Verbosity -> a -> LByteString
errorPayload verb = JSON.encode . toJsonPgrstError verb
where
toJsonPgrstError :: (ErrorBody a, ErrorHeaders a) => Verbosity -> a -> JSON.Value
toJsonPgrstError Verbose err = JSON.object [
"code" .= code err
, "message" .= message err
, "details" .= details err
, "hint" .= hint err
]
toJsonPgrstError Minimal err = JSON.object [
"code" .= code err
, "message" .= message err
]
class (ErrorBody a, JSON.ToJSON a) => PgrstError a where
status :: a -> HTTP.Status
headers :: a -> [Header]
-- | Create HTTP response from Error
errorResponseFor :: (ErrorBody a, ErrorHeaders a) => Verbosity -> a -> Response
errorResponseFor verb err =
let
baseHeader = MediaType.toContentType MTApplicationJSON
cLHeader body = (,) "Content-Length" (show $ LBS.length body) :: Header
pSHeader code' = ("Proxy-Status", "PostgREST; error=" <> T.encodeUtf8 code')
in
responseLBS (status err) (baseHeader : cLHeader (errorPayload verb err) : pSHeader (code err) : headers err) $ errorPayload verb err
errorPayload :: a -> LByteString
errorPayload = JSON.encode
errorResponseFor :: a -> Response
errorResponseFor err =
let
baseHeader = MediaType.toContentType MTApplicationJSON
cLHeader body = (,) "Content-Length" (show $ LBS.length body) :: Header
in
responseLBS (status err) (baseHeader : cLHeader (errorPayload err) : headers err) $ errorPayload err
class ErrorHeaders a where
status :: a -> HTTP.Status
headers :: a -> [Header]
class ErrorBody a where
code :: a -> Text
@@ -74,49 +93,7 @@ class ErrorBody a where
details :: a -> Maybe JSON.Value
hint :: a -> Maybe JSON.Value
data ApiRequestError
= AggregatesNotAllowed
| MediaTypeError [ByteString]
| InvalidBody ByteString
| InvalidFilters
| InvalidPreferences [ByteString]
| InvalidRange RangeError
| InvalidRpcMethod ByteString
| NotEmbedded Text
| NotImplemented Text
| PutLimitNotAllowedError
| QueryParamError QPError
| RelatedOrderNotToOne Text Text
| UnacceptableFilter Text
| UnacceptableSchema Text [Text]
| UnsupportedMethod ByteString
| GucHeadersError
| GucStatusError
| PutMatchingPkError
| SingularityError Integer
| PGRSTParseError RaiseError
| MaxAffectedViolationError Integer
| InvalidResourcePath
| OpenAPIDisabled
| MaxAffectedRpcViolation
deriving Show
data QPError = QPError Text Text
deriving Show
data RaiseError
= MsgParseError ByteString
| DetParseError ByteString
| NoDetail
deriving Show
data RangeError
= NegativeLimit
| LowerGTUpper
| OutOfBounds Text Text
deriving Show
instance PgrstError ApiRequestError where
instance ErrorHeaders ApiRequestError where
status AggregatesNotAllowed{} = HTTP.status400
status MediaTypeError{} = HTTP.status406
status InvalidBody{} = HTTP.status400
@@ -240,20 +217,7 @@ instance ErrorBody ApiRequestError where
hint _ = Nothing
instance JSON.ToJSON ApiRequestError where
toJSON err = toJsonPgrstError
(code err) (message err) (details err) (hint err)
data SchemaCacheError
= AmbiguousRelBetween Text Text [Relationship]
| AmbiguousRpc [Routine]
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
| NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine]
| ColumnNotFound Text Text
| TableNotFound Text Text [Table]
deriving Show
instance PgrstError SchemaCacheError where
instance ErrorHeaders SchemaCacheError where
status AmbiguousRelBetween{} = HTTP.status300
status AmbiguousRpc{} = HTTP.status300
status NoRelBetween{} = HTTP.status400
@@ -313,22 +277,10 @@ instance ErrorBody SchemaCacheError where
where
onlySingleParams = isInvPost && contentType `elem` [MTTextPlain, MTTextXML, MTOctetStream]
hint (AmbiguousRpc _) = Just "Try renaming the parameters or the function itself in the database so function overloading can be resolved"
hint (TableNotFound schemaName relName tbls) = JSON.String <$> tableNotFoundHint schemaName relName tbls
hint (TableNotFound schemaName relName schemaCache) = JSON.String <$> tableNotFoundHint schemaName relName schemaCache
hint _ = Nothing
instance JSON.ToJSON SchemaCacheError where
toJSON err = toJsonPgrstError
(code err) (message err) (details err) (hint err)
toJsonPgrstError :: Text -> Text -> Maybe JSON.Value -> Maybe JSON.Value -> JSON.Value
toJsonPgrstError code' message' details' hint' = JSON.object [
"code" .= code'
, "message" .= message'
, "details" .= details'
, "hint" .= hint'
]
-- |
-- If no relationship is found then:
--
@@ -347,9 +299,6 @@ toJsonPgrstError code' message' details' hint' = JSON.object [
-- >>> noRelBetweenHint "films" "role" "api" rels
-- Just "Perhaps you meant 'roles' instead of 'role'."
--
-- >>> noRelBetweenHint "films" "role" "api" rels
-- Just "Perhaps you meant 'roles' instead of 'role'."
--
-- >>> noRelBetweenHint "films" "actors" "api" rels
-- Nothing
--
@@ -386,7 +335,7 @@ noRelBetweenHint parent child schema allRels = ("Perhaps you meant '" <>) <$>
-- Just "Perhaps you meant to call the function api.test"
--
-- >>> noRpcHint "api" "other" [] procs []
-- Just "Perhaps you meant to call the function api.another"
-- Nothing
--
-- >>> noRpcHint "api" "noclosealternative" [] procs []
-- Nothing
@@ -423,18 +372,30 @@ noRpcHint schema procName params allProcs overloadedProcs =
-- E.g. ["val", "param", "name"] into "(name, param, val)"
listToText = ("(" <>) . (<> ")") . T.intercalate ", " . sort
possibleProcs
| null overloadedProcs = Fuzzy.getOne fuzzySetOfProcs procName
| otherwise = (procName <>) <$> Fuzzy.getOne fuzzySetOfParams (listToText params)
| null overloadedProcs = getFuzzyHint HintProcedure fuzzySetOfProcs procName
| otherwise = (procName <>) <$> getFuzzyHint HintParams fuzzySetOfParams (listToText params)
-- |
-- Do a fuzzy search in all tables in the same schema and return closest result
tableNotFoundHint :: Text -> Text -> [Table] -> Maybe Text
tableNotFoundHint schema tblName tblList
tableNotFoundHint :: Text -> Text -> SchemaCache -> Maybe Text
tableNotFoundHint schema tblName SchemaCache{dbTablesFuzzyIndex}
= fmap (\tbl -> "Perhaps you meant the table '" <> schema <> "." <> tbl <> "'") perhapsTable
where
perhapsTable = Fuzzy.getOne fuzzyTableSet tblName
fuzzyTableSet = Fuzzy.fromList [ tableName tbl | tbl <- tblList, tableSchema tbl == schema]
perhapsTable = (\fuzzySet -> getFuzzyHint HintTable fuzzySet tblName) =<< HM.lookup schema dbTablesFuzzyIndex
data HintType
= HintTable
| HintProcedure
| HintParams
-- | Get hint using Fuzzy Search with at least 0.75 similarity score
getFuzzyHint :: HintType -> Fuzzy.FuzzySet -> Text -> Maybe Text
getFuzzyHint hintType =
let minScore = 0.75 :: Double -- used for table and procedure name hints
in case hintType of
HintTable -> Fuzzy.getOneWithMinScore minScore
HintProcedure -> Fuzzy.getOneWithMinScore minScore
HintParams -> Fuzzy.getOne -- For params, we stick to `getOne` which defaults to 0.33 min score, not a security risk to reveal params
compressedRel :: Relationship -> JSON.Value
-- An ambiguousness error cannot happen for computed relationships TODO refactor so this mempty is not needed
@@ -487,12 +448,7 @@ pgrstParseErrorHint err = case err of
MsgParseError _ -> "MESSAGE must be a JSON object with obligatory keys: 'code', 'message' and optional keys: 'details', 'hint'."
_ -> "DETAIL must be a JSON object with obligatory keys: 'status', 'headers' and optional key: 'status_text'."
data PgError = PgError Authenticated SQL.UsageError
deriving Show
type Authenticated = Bool
instance PgrstError PgError where
instance ErrorHeaders PgError where
status (PgError authed usageError) = pgErrorStatus authed usageError
headers (PgError _ (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ResultError (SQL.ServerError "PGRST" m d _ _p))))) =
@@ -507,44 +463,33 @@ instance PgrstError PgError where
then [("WWW-Authenticate", "Bearer") :: Header]
else mempty
proxyStatusHeader :: Text -> Header
proxyStatusHeader code' = ("Proxy-Status", "PostgREST; error=" <> T.encodeUtf8 code')
instance JSON.ToJSON PgError where
toJSON (PgError _ usageError) = toJsonPgrstError
(code usageError) (message usageError) (details usageError) (hint usageError)
instance ErrorBody PgError where
code (PgError _ usageError) = code usageError
message (PgError _ usageError) = message usageError
details (PgError _ usageError) = details usageError
hint (PgError _ usageError) = hint usageError
instance JSON.ToJSON SQL.UsageError where
toJSON err = toJsonPgrstError
(code err) (message err) (details err) (hint err)
instance ErrorBody SQL.UsageError where
code (SQL.ConnectionUsageError _) = "PGRST000"
code (SQL.SessionUsageError (SQL.PipelineError e)) = code e
code (SQL.SessionUsageError (SQL.QueryError _ _ e)) = code e
code SQL.AcquisitionTimeoutUsageError = "PGRST003"
message (SQL.ConnectionUsageError _) = "Database connection error. Retrying the connection."
message (SQL.SessionUsageError (SQL.PipelineError e)) = message e
message (SQL.SessionUsageError (SQL.QueryError _ _ e)) = message e
message SQL.AcquisitionTimeoutUsageError = "Timed out acquiring connection from connection pool."
details (SQL.ConnectionUsageError e) = JSON.String . T.decodeUtf8 <$> e
details (SQL.SessionUsageError (SQL.PipelineError e)) = details e
details (SQL.SessionUsageError (SQL.QueryError _ _ e)) = details e
details SQL.AcquisitionTimeoutUsageError = Nothing
hint (SQL.ConnectionUsageError _) = Nothing
hint (SQL.SessionUsageError (SQL.PipelineError e)) = hint e
hint (SQL.SessionUsageError (SQL.QueryError _ _ e)) = hint e
hint SQL.AcquisitionTimeoutUsageError = Nothing
instance JSON.ToJSON SQL.CommandError where
toJSON err = toJsonPgrstError
(code err) (message err) (details err) (hint err)
instance ErrorBody SQL.CommandError where
-- Special error raised with code PGRST, to allow full response control
code (SQL.ResultError (SQL.ServerError "PGRST" m d _ _)) =
@@ -586,8 +531,13 @@ instance ErrorBody SQL.CommandError where
pgErrorStatus :: Bool -> SQL.UsageError -> HTTP.Status
pgErrorStatus _ (SQL.ConnectionUsageError _) = HTTP.status503
pgErrorStatus _ SQL.AcquisitionTimeoutUsageError = HTTP.status504
pgErrorStatus _ (SQL.SessionUsageError (SQL.PipelineError (SQL.ClientError _))) = HTTP.status503
pgErrorStatus _ (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _))) = HTTP.status503
pgErrorStatus authed (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ResultError rError))) =
pgErrorStatus authed (SQL.SessionUsageError (SQL.PipelineError (SQL.ResultError rError))) = mapSQLtoHTTP authed rError
pgErrorStatus authed (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ResultError rError))) = mapSQLtoHTTP authed rError
mapSQLtoHTTP :: Bool -> SQL.ResultError -> HTTP.Status
mapSQLtoHTTP authed rError =
case rError of
(SQL.ServerError c m d _ _) ->
case BS.unpack c of
@@ -641,86 +591,45 @@ pgErrorStatus authed (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ResultError
_ -> HTTP.status500
data Error
= ApiRequestError ApiRequestError
| SchemaCacheErr SchemaCacheError
| JwtErr JwtError
| NoSchemaCacheError
| PgErr PgError
deriving Show
instance ErrorHeaders Error where
status (ApiRequestErr err) = status err
status (SchemaCacheErr err) = status err
status (JwtErr err) = status err
status NoSchemaCacheError = HTTP.status503
status (PgErr err) = status err
data JwtError
= JwtDecodeErr JwtDecodeError
| JwtSecretMissing
| JwtTokenRequired
| JwtClaimsErr JwtClaimsError
deriving Show
data JwtDecodeError
= EmptyAuthHeader
| UnexpectedParts Int
| KeyError Text
| BadAlgorithm Text
| BadCrypto
| UnsupportedTokenType
| UnreachableDecodeError
deriving Show
data JwtClaimsError
= JWTExpired
| JWTNotYetValid
| JWTIssuedAtFuture
| JWTNotInAudience
| ParsingClaimsFailed
| ExpClaimNotNumber
| NbfClaimNotNumber
| IatClaimNotNumber
| AudClaimNotStringOrArray
deriving Show
instance PgrstError Error where
status (ApiRequestError err) = status err
status (SchemaCacheErr err) = status err
status (JwtErr err) = status err
status NoSchemaCacheError = HTTP.status503
status (PgErr err) = status err
headers (ApiRequestError err) = proxyStatusHeader (code err) : headers err
headers (SchemaCacheErr err) = proxyStatusHeader (code err) : headers err
headers (JwtErr err) = proxyStatusHeader (code err) : headers err
headers (PgErr err) = proxyStatusHeader (code err) : headers err
headers err@NoSchemaCacheError = proxyStatusHeader (code err) : mempty
instance JSON.ToJSON Error where
toJSON err = toJsonPgrstError
(code err) (message err) (details err) (hint err)
headers (ApiRequestErr err) = headers err
headers (SchemaCacheErr err) = headers err
headers (JwtErr err) = headers err
headers (PgErr err) = headers err
headers NoSchemaCacheError = mempty
instance ErrorBody Error where
code (ApiRequestError err) = code err
code (SchemaCacheErr err) = code err
code (JwtErr err) = code err
code NoSchemaCacheError = "PGRST002"
code (PgErr err) = code err
code (ApiRequestErr err) = code err
code (SchemaCacheErr err) = code err
code (JwtErr err) = code err
code NoSchemaCacheError = "PGRST002"
code (PgErr err) = code err
message (ApiRequestError err) = message err
message (ApiRequestErr err) = message err
message (SchemaCacheErr err) = message err
message (JwtErr err) = message err
message NoSchemaCacheError = "Could not query the database for the schema cache. Retrying."
message (PgErr err) = message err
details (ApiRequestError err) = details err
details (SchemaCacheErr err) = details err
details (JwtErr err) = details err
details NoSchemaCacheError = Nothing
details (PgErr err) = details err
details (ApiRequestErr err) = details err
details (SchemaCacheErr err) = details err
details (JwtErr err) = details err
details NoSchemaCacheError = Nothing
details (PgErr err) = details err
hint (ApiRequestError err) = hint err
hint (SchemaCacheErr err) = hint err
hint (JwtErr err) = hint err
hint NoSchemaCacheError = Nothing
hint (PgErr err) = hint err
hint (ApiRequestErr err) = hint err
hint (SchemaCacheErr err) = hint err
hint (JwtErr err) = hint err
hint NoSchemaCacheError = Nothing
hint (PgErr err) = hint err
instance PgrstError JwtError where
instance ErrorHeaders JwtError where
status JwtDecodeErr{} = HTTP.unauthorized401
status JwtSecretMissing = HTTP.status500
status JwtTokenRequired = HTTP.unauthorized401
@@ -731,10 +640,6 @@ instance PgrstError JwtError where
headers e@(JwtClaimsErr _) = [invalidTokenHeader $ message e]
headers _ = mempty
instance JSON.ToJSON JwtError where
toJSON err = toJsonPgrstError
(code err) (message err) (details err) (hint err)
instance ErrorBody JwtError where
code JwtSecretMissing = "PGRST300"
code (JwtDecodeErr _) = "PGRST301"
@@ -778,18 +683,6 @@ requiredTokenHeader :: Header
requiredTokenHeader = ("WWW-Authenticate", "Bearer")
-- For parsing byteString to JSON Object, used for allowing full response control
data PgRaiseErrMessage = PgRaiseErrMessage {
getCode :: Text,
getMessage :: Text,
getDetails :: Maybe Text,
getHint :: Maybe Text
}
data PgRaiseErrDetails = PgRaiseErrDetails {
getStatus :: Int,
getStatusText :: Maybe Text,
getHeaders :: Map Text Text
}
instance JSON.FromJSON PgRaiseErrMessage where
parseJSON (JSON.Object m) =
+138
View File
@@ -0,0 +1,138 @@
{-|
Module : PostgREST.Error.Types
Description : PostgREST Error Data Types
-}
module PostgREST.Error.Types
( ApiRequestError(..)
, QPError(..)
, RangeError(..)
, RaiseError(..)
, SchemaCacheError(..)
, PgError(..)
, Error(..)
, JwtError (..)
, JwtDecodeError(..)
, JwtClaimsError(..)
, PgRaiseErrMessage(..)
, PgRaiseErrDetails(..)
) where
import qualified Hasql.Pool as SQL
import PostgREST.MediaType (MediaType (..))
import PostgREST.SchemaCache (SchemaCache (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.SchemaCache.Relationship (Relationship (..),
RelationshipsMap)
import PostgREST.SchemaCache.Routine (Routine (..))
import Protolude
data Error
= ApiRequestErr ApiRequestError
| SchemaCacheErr SchemaCacheError
| JwtErr JwtError
| NoSchemaCacheError
| PgErr PgError
deriving Show
-- API REQUEST ERRORS: PGRST1XX
data ApiRequestError
= AggregatesNotAllowed
| MediaTypeError [ByteString]
| InvalidBody ByteString
| InvalidFilters
| InvalidPreferences [ByteString]
| InvalidRange RangeError
| InvalidRpcMethod ByteString
| NotEmbedded Text
| NotImplemented Text
| PutLimitNotAllowedError
| QueryParamError QPError
| RelatedOrderNotToOne Text Text
| UnacceptableFilter Text
| UnacceptableSchema Text [Text]
| UnsupportedMethod ByteString
| GucHeadersError
| GucStatusError
| PutMatchingPkError
| SingularityError Integer
| PGRSTParseError RaiseError
| MaxAffectedViolationError Integer
| InvalidResourcePath
| OpenAPIDisabled
| MaxAffectedRpcViolation
deriving Show
data QPError = QPError Text Text
deriving Show
data RaiseError
= MsgParseError ByteString
| DetParseError ByteString
| NoDetail
deriving Show
data RangeError
= NegativeLimit
| LowerGTUpper
| OutOfBounds Text Text
deriving Show
-- SCHEMA CACHE ERRORS: PGRST2XX
data SchemaCacheError
= AmbiguousRelBetween Text Text [Relationship]
| AmbiguousRpc [Routine]
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
| NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine]
| ColumnNotFound Text Text
| TableNotFound Text Text SchemaCache
deriving Show
-- JWT ERRORS: PGRST3XX
data JwtError
= JwtDecodeErr JwtDecodeError
| JwtSecretMissing
| JwtTokenRequired
| JwtClaimsErr JwtClaimsError
deriving Show
data JwtDecodeError
= EmptyAuthHeader
| UnexpectedParts Int
| KeyError Text
| BadAlgorithm Text
| BadCrypto
| UnsupportedTokenType
| UnreachableDecodeError
deriving Show
data JwtClaimsError
= JWTExpired
| JWTNotYetValid
| JWTIssuedAtFuture
| JWTNotInAudience
| ParsingClaimsFailed
| ExpClaimNotNumber
| NbfClaimNotNumber
| IatClaimNotNumber
| AudClaimNotStringOrArray
deriving Show
-- PG ERRORS
type Authenticated = Bool
data PgError = PgError Authenticated SQL.UsageError
deriving Show
-- For parsing byteString to JSON Object, used for allowing full response control
data PgRaiseErrMessage = PgRaiseErrMessage {
getCode :: Text,
getMessage :: Text,
getDetails :: Maybe Text,
getHint :: Maybe Text
}
data PgRaiseErrDetails = PgRaiseErrDetails {
getStatus :: Int,
getStatusText :: Maybe Text,
getHeaders :: Map Text Text
}
+53 -24
View File
@@ -1,3 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE RecordWildCards #-}
@@ -10,29 +11,39 @@ import qualified Hasql.Notifications as SQL
import PostgREST.AppState (AppState, getConfig)
import PostgREST.Config (AppConfig (..))
import PostgREST.Observation (Observation (..))
import PostgREST.Version (prettyVersion)
import qualified PostgREST.AppState as AppState
import qualified PostgREST.Config as Config
import Protolude
import Control.Arrow ((&&&))
import Data.Bitraversable (bisequence)
import Data.Either.Combinators (whenRight)
import qualified Data.Text as T
import qualified Database.PostgreSQL.LibPQ as LibPQ
import qualified Hasql.Session as SQL
import PostgREST.Config.Database (queryPgVersion)
import PostgREST.Config.PgVersion (pgvFullName)
import Protolude
-- | Starts the Listener in a thread
runListener :: AppState -> IO ()
runListener appState = do
AppConfig{..} <- getConfig appState
when configDbChannelEnabled $
void . forkIO $ retryingListen appState
void . forkIO . void $ retryingListen appState
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
retryingListen :: AppState -> IO ()
-- | This function never returns (but can throw) and return type enforces that.
retryingListen :: AppState -> IO Void
retryingListen appState = do
AppConfig{..} <- AppState.getConfig appState
cfg@AppConfig{..} <- AppState.getConfig appState
let
dbChannel = toS configDbChannel
handleFinally err = do
onError err = do
AppState.putIsListenerOn appState False
observer $ DBListenFail dbChannel (Right err)
when (isDbListenerBug err) $
observer DBListenBugHint
unless configDbPoolAutomaticRecovery $
killThread mainThreadId
@@ -42,29 +53,43 @@ retryingListen appState = do
threadDelay (delay * oneSecondInMicro)
unless (delay == maxDelay) $
AppState.putNextListenerDelay appState (delay * 2)
-- loop running the listener
retryingListen appState
-- forkFinally allows to detect if the thread dies
void . flip forkFinally handleFinally $ do
dbOrError <- SQL.acquire $ toUtf8 (Config.addTargetSessionAttrs $ Config.addFallbackAppName prettyVersion configDbUri)
case dbOrError of
Right db -> do
SQL.listen db $ SQL.toPgIdentifier dbChannel
AppState.putIsListenerOn appState True
-- Execute the listener with with error handling
handle onError $ do
-- Make sure we don't leak connections on errors
bracket
-- acquire connection
(SQL.acquire $
Config.toConnectionSettings Config.addTargetSessionAttrs cfg)
-- release connection
(`whenRight` releaseConnection) $
-- use connection
\case
Right db -> do
SQL.listen db $ SQL.toPgIdentifier dbChannel
(pqHost, pqPort) <- SQL.withLibPQConnection db $ bisequence . (LibPQ.host &&& LibPQ.port)
pgFullName <- SQL.run queryPgVersion db >>= either throwIO (pure . pgvFullName)
delay <- AppState.getNextListenerDelay appState
when (delay > 1) $ do -- if we did a retry
-- assume we lost notifications, refresh the schema cache
AppState.schemaCacheLoader appState
-- reset the delay
AppState.putNextListenerDelay appState 1
AppState.putIsListenerOn appState True
observer $ DBListenStart dbChannel
SQL.waitForNotifications handleNotification db
delay <- AppState.getNextListenerDelay appState
when (delay > 1) $ do -- if we did a retry
-- assume we lost notifications, refresh the schema cache
AppState.schemaCacheLoader appState
-- reset the delay
AppState.putNextListenerDelay appState 1
Left err -> do
observer $ DBListenFail dbChannel (Left err)
exitFailure
observer $ DBListenStart pqHost pqPort pgFullName dbChannel
-- wait for notifications
-- this will never return, in case of an error it will throw and be caught by onError
forever $ SQL.waitForNotifications handleNotification db
Left err -> do
observer $ DBListenFail dbChannel (Left err)
exitFailure
where
observer = AppState.getObserver appState
mainThreadId = AppState.getMainThreadId appState
@@ -79,3 +104,7 @@ retryingListen appState = do
cacheReloader =
AppState.schemaCacheLoader appState
releaseConnection = void . forkIO . handle (observer . DBListenerConnectionCleanupFail) . SQL.release
isDbListenerBug e = "could not access status of transaction" `T.isInfixOf` show e
+167 -48
View File
@@ -1,4 +1,6 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
{-|
Module : PostgREST.Logger
Description : Logging based on the Observation.hs module. Access logs get sent to stdout and server diagnostic get sent to stderr.
@@ -14,7 +16,6 @@ module PostgREST.Logger
import Control.AutoUpdate (defaultUpdateSettings,
mkAutoUpdate,
updateAction)
import Control.Debounce
import qualified Data.ByteString.Char8 as BS
import qualified Data.Text.Encoding as T
import qualified Hasql.Decoders as HD
@@ -31,37 +32,36 @@ import qualified Network.Wai.Middleware.RequestLogger as Wai
import Network.HTTP.Types.Status (Status, status400, status500)
import System.IO.Unsafe (unsafePerformIO)
import PostgREST.Config (LogLevel (..))
import PostgREST.Config (LogLevel (..), Verbosity (..))
import PostgREST.Debounce (makeDebouncer)
import PostgREST.Observation
import PostgREST.Query (MainQuery (..))
import PostgREST.SchemaCache (queryTimingsWLabels)
import Protolude
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as T
import qualified Hasql.Connection as SQL
import qualified Hasql.Pool as SQL
import qualified Hasql.Pool.Observation as SQL
import Numeric (showFFloat)
import PostgREST.Config.PgVersion (pgvName)
import qualified PostgREST.Error as Error
import Protolude
data LoggerState = LoggerState
{ stateGetZTime :: IO ZonedTime -- ^ Time with time zone used for logs
, stateLogDebouncePoolTimeout :: MVar (IO ()) -- ^ Logs with a debounce
, stateLogDebouncePoolTimeout :: IO () -- ^ Logs with a debounce
}
init :: IO LoggerState
init = do
init = mdo
let
oneSecond = 1000000
loggerState = LoggerState zTime debouncePoolTimeout
zTime <- mkAutoUpdate defaultUpdateSettings { updateAction = getZonedTime }
LoggerState zTime <$> newEmptyMVar
logWithDebounce :: LoggerState -> IO () -> IO ()
logWithDebounce loggerState action = do
debouncer <- tryReadMVar $ stateLogDebouncePoolTimeout loggerState
case debouncer of
Just d -> d
Nothing -> do
newDebouncer <-
let oneSecond = 1000000 in
mkDebounce defaultDebounceSettings
{ debounceAction = action
, debounceFreq = 5*oneSecond
, debounceEdge = leadingEdge -- logs at the start and the end
}
putMVar (stateLogDebouncePoolTimeout loggerState) newDebouncer
newDebouncer
debouncePoolTimeout <- makeDebouncer $
logWithZTime loggerState (observationMessages PoolAcqTimeoutObs) *> threadDelay (5 * oneSecond)
pure loggerState
-- TODO stop using this middleware to reuse the same "observer" pattern for all our logs
middleware :: LogLevel -> (Wai.Request -> Maybe BS.ByteString) -> Wai.Middleware
@@ -88,55 +88,174 @@ shouldLogResponse logLevel = case logLevel of
-- All observations are logged except some that depend on the log-level
observationLogger :: LoggerState -> LogLevel -> ObservationHandler
observationLogger loggerState logLevel obs = case obs of
o@(PoolAcqTimeoutObs _) -> do
when (logLevel >= LogError) $ do
logWithDebounce loggerState $
logWithZTime loggerState $ observationMessage o
PoolAcqTimeoutObs -> do
when (logLevel >= LogError) $
stateLogDebouncePoolTimeout loggerState
o@(QueryErrorCodeHighObs _) -> do
when (logLevel >= LogError) $ do
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
o@SchemaCacheEmptyObs ->
when (logLevel >= LogError) $ do
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
o@(HasqlPoolObs _) -> do
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessage o
QueryObs gq status -> do
logWithZTime loggerState $ observationMessages o
o@(QueryObs _ status) -> do
when (shouldLogResponse logLevel status) $
logMainQ loggerState gq
logWithZTime loggerState $ observationMessages o
o@PoolRequest ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
o@PoolRequestFullfilled ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
o@PoolFlushed ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessages o
o@JwtCacheEviction ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
o@(JwtCacheLookup _) ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
o@(WarpServerObs _) ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessages o
o ->
logWithZTime loggerState $ observationMessage o
logWithZTime loggerState $ observationMessages o
logWithZTime :: LoggerState -> Text -> IO ()
logWithZTime loggerState txt = do
logWithZTime :: LoggerState -> [Text] -> IO ()
logWithZTime loggerState txts = do
zTime <- stateGetZTime loggerState
hPutStrLn stderr $ toS (formatTime defaultTimeLocale "%d/%b/%Y:%T %z: " zTime) <> txt
logMainQ :: LoggerState -> MainQuery -> IO ()
logMainQ loggerState MainQuery{mqOpenAPI=(x, y, z),..} =
let snipts = renderSnippet <$> [mqTxVars, fromMaybe mempty mqPreReq, mqMain, x, y, z, fromMaybe mempty mqExplain]
-- Does not log SQL when it's empty (happens on OPTIONS requests and when the openapi queries are not generated)
logQ q = when (q /= mempty) $ logWithZTime loggerState $ showOnSingleLine '\n' $ T.decodeUtf8 q in
mapM_ logQ snipts
traverse_ (hPutStrLn stderr . (toS (formatTime defaultTimeLocale "%d/%b/%Y:%T %z: " zTime) <>)) txts
-- TODO: maybe patch upstream hasql-dynamic-statements so we have a less hackish way to convert
-- the SQL.Snippet or maybe don't use hasql-dynamic-statements and resort to plain strings for the queries and use regular hasql
renderSnippet :: SQL.Snippet -> ByteString
renderSnippet snippet =
let SQL.Statement sql _ _ _ = SQL.dynamicallyParameterized snippet decoder prepared
let SQL.Statement sql _ _ _ = SQL.dynamicallyParameterized snippet decoder False
decoder = HD.noResult -- unused
prepared = False -- unused
in
sql
observationMessages :: Observation -> [Text]
observationMessages = \case
AdminStartObs address ->
pure $ "Admin server listening on " <> address
AppStartObs ver ->
pure $ "Starting PostgREST " <> T.decodeUtf8 ver <> "..."
AppServerAddressObs address ->
pure $ "API server listening on " <> address
DBConnectedObs ver ->
pure $ "Successfully connected to " <> ver
ExitUnsupportedPgVersion pgVer minPgVer ->
pure $ "Cannot run in this PostgreSQL version (" <> pgvName pgVer <> "), PostgREST needs at least " <> pgvName minPgVer
ExitDBNoRecoveryObs ->
pure "Automatic recovery disabled, exiting."
ExitDBFatalError ServerAuthError usageErr ->
pure $ "Failed to establish a connection. " <> jsonMessage usageErr
ExitDBFatalError ServerPgrstBug usageErr ->
pure $ "This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues. " <> jsonMessage usageErr
ExitDBFatalError ServerError42P05 usageErr ->
pure $ "If you are using connection poolers in transaction mode, try setting db-prepared-statements to false. " <> jsonMessage usageErr
ExitDBFatalError ServerError08P01 usageErr ->
pure $ "Connection poolers in statement mode are not supported." <> jsonMessage usageErr
SchemaCacheEmptyObs ->
pure $ T.decodeUtf8 . LBS.toStrict . Error.errorPayload Verbose $ Error.NoSchemaCacheError
SchemaCacheErrorObs dbSchemas extraPaths usageErr ->
pure $ "Failed to load the schema cache using "
<> "db-schemas=" <> T.intercalate "," (toList dbSchemas)
<> " and "
<> "db-extra-search-path=" <> T.intercalate "," extraPaths
<> ". " <> jsonMessage usageErr
SchemaCacheQueriedObs resultTime timings ->
[ "Schema cache queried in " <> showMillis resultTime <> " milliseconds " ] <>
let showTimings qt = [ T.intercalate ", " $ (\(l, v) -> T.decodeUtf8 l <> ": " <> v <> " ms") <$> queryTimingsWLabels qt ] in
maybe mempty showTimings timings
SchemaCacheLoadedObs resultTime summary ->
[
"Schema cache loaded " <> summary
, "Schema cache loaded in " <> showMillis resultTime <> " milliseconds"
]
ConnectionRetryObs delay ->
pure $ "Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
QueryPgVersionError usageErr ->
pure $ "Failed to query the PostgreSQL version. " <> jsonMessage usageErr
DBListenStart host port fullName channel -> do
pure $ "Listener connected to " <> fullName <> " on " <> show (fold $ host <> fmap (":" <>) port) <> " and listening for database notifications on the " <> show channel <> " channel"
DBListenFail channel listenErr ->
pure $ "Failed listening for database notifications on the " <> show channel <> " channel. " <>
either showListenerConnError showListenerException listenErr
DBListenRetry delay ->
pure $ "Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
DBListenBugHint ->
pure "HINT: This is likely a bug in the notification queue, try executing the following to solve it: select pg_notification_queue_usage();"
DBListenerGotSCacheMsg channel ->
pure $ "Received a schema cache reload message on the " <> show channel <> " channel"
DBListenerGotConfigMsg channel ->
pure $ "Received a config reload message on the " <> show channel <> " channel"
DBListenerConnectionCleanupFail ex ->
pure $ "Failed during listener connection cleanup: " <> showOnSingleLine '\t' (show ex)
(QueryObs MainQuery{mqOpenAPI=(x, y, z),..} _) ->
let snipts = renderSnippet <$> [mqTxVars, fromMaybe mempty mqPreReq, mqMain, x, y, z, fromMaybe mempty mqExplain]
in
showOnSingleLine '\n' . T.decodeUtf8 <$> filter (/= mempty) snipts
ConfigReadErrorObs usageErr ->
pure $ "Failed to query database settings for the config parameters." <> jsonMessage usageErr
QueryRoleSettingsErrorObs usageErr ->
pure $ "Failed to query the role settings. " <> jsonMessage usageErr
QueryErrorCodeHighObs usageErr ->
pure $ jsonMessage usageErr
ConfigInvalidObs err ->
pure $ "Failed reloading config: " <> err
ConfigSucceededObs ->
pure "Config reloaded"
PoolInit poolSize ->
pure $ "Connection Pool initialized with a maximum size of " <> show poolSize <> " connections"
PoolAcqTimeoutObs -> pure $ jsonMessage SQL.AcquisitionTimeoutUsageError
HasqlPoolObs (SQL.ConnectionObservation uuid status) ->
pure $ "Connection " <> show uuid <> (
case status of
SQL.ConnectingConnectionStatus -> " is being established"
SQL.ReadyForUseConnectionStatus reason -> " is available due to " <> case reason of
SQL.EstablishedConnectionReadyForUseReason -> "connection establishment"
SQL.SessionFailedConnectionReadyForUseReason _ -> "session failure"
SQL.SessionSucceededConnectionReadyForUseReason -> "session success"
SQL.InUseConnectionStatus -> " is used"
SQL.TerminatedConnectionStatus reason -> " is terminated due to " <> case reason of
SQL.AgingConnectionTerminationReason -> "max lifetime"
SQL.IdlenessConnectionTerminationReason -> "max idletime"
SQL.ReleaseConnectionTerminationReason -> "release"
SQL.NetworkErrorConnectionTerminationReason _ -> "network error" -- usage error is already logged, no need to repeat the same message.
SQL.InitializationErrorTerminationReason _ -> "init failure"
)
PoolRequest ->
pure "Trying to borrow a connection from pool"
PoolRequestFullfilled ->
pure "Borrowed a connection from the pool"
PoolFlushed ->
pure "Database connection pool flushed"
JwtCacheLookup _ ->
pure "Looked up a JWT in JWT cache"
JwtCacheEviction ->
pure "Evicted entry from JWT cache"
TerminationUnixSignalObs signal ->
pure $ "Received termination unix signal " <> signal
WarpServerObs txt ->
pure $ "Warp server: " <> txt
where
showMillis :: Double -> Text
showMillis x = toS $ showFFloat (Just 1) x ""
jsonMessage err = T.decodeUtf8 . LBS.toStrict . Error.errorPayload Verbose $ Error.PgError False err
showListenerConnError :: SQL.ConnectionError -> Text
showListenerConnError = maybe "Connection error" (showOnSingleLine '\t' . T.decodeUtf8)
showListenerException :: SomeException -> Text
showListenerException = showOnSingleLine '\t' . show
showOnSingleLine :: Char -> Text -> Text
showOnSingleLine split txt = T.intercalate " " $ T.filter (/= split) <$> T.lines txt -- the errors from hasql-notifications come intercalated with "\t\n"
+4 -5
View File
@@ -96,9 +96,8 @@ data ResultSet
mainTx :: MainQuery -> AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> MainTx
mainTx _ _ _ _ (NoDb x) _ = NoDbTx $ NoDbResult x
mainTx genQ@MainQuery{..} conf@AppConfig{..} AuthResult{..} apiReq (Db plan) sCache =
DbTx isoLvl txMode dbHandler transaction
DbTx isoLvl txMode dbHandler SQL.transactionNoRetry
where
transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction
isoLvl = planIsoLvl conf authRole plan
txMode = planTxMode plan
dbHandler = do
@@ -222,7 +221,7 @@ failPut :: ResultSet -> DbHandler ()
failPut RSStandard{rsQueryTotal=queryTotal} =
when (queryTotal /= 1) $ do
lift SQL.condemn
throwError $ Error.ApiRequestError Error.PutMatchingPkError
throwError $ Error.ApiRequestErr Error.PutMatchingPkError
-- |
-- Fail a response if a single JSON object was requested and not exactly one
@@ -231,13 +230,13 @@ failNotSingular :: MediaType -> ResultSet -> DbHandler ()
failNotSingular mediaType RSStandard{rsQueryTotal=queryTotal} =
when (elem mediaType [MTVndSingularJSON True, MTVndSingularJSON False] && queryTotal /= 1) $ do
lift SQL.condemn
throwError $ Error.ApiRequestError . Error.SingularityError $ toInteger queryTotal
throwError $ Error.ApiRequestErr . Error.SingularityError $ toInteger queryTotal
failExceedsMaxAffectedPref :: (Maybe PreferMaxAffected, Maybe PreferHandling) -> ResultSet -> DbHandler ()
failExceedsMaxAffectedPref (Nothing,_) _ = pure ()
failExceedsMaxAffectedPref (Just (PreferMaxAffected n), handling) RSStandard{rsQueryTotal=queryTotal} = when ((queryTotal > n) && (handling == Just Strict)) $ do
lift SQL.condemn
throwError $ Error.ApiRequestError . Error.MaxAffectedViolationError $ toInteger queryTotal
throwError $ Error.ApiRequestErr . Error.MaxAffectedViolationError $ toInteger queryTotal
-- | Set a transaction to roll back if requested
optionalRollback :: AppConfig -> ApiRequest -> DbHandler ()
+3 -3
View File
@@ -50,10 +50,10 @@ init configDbPoolSize = do
-- Only some observations are used as metrics
observationMetrics :: MetricsState -> ObservationHandler
observationMetrics MetricsState{..} obs = case obs of
(PoolAcqTimeoutObs _) -> do
PoolAcqTimeoutObs -> do
incCounter poolTimeouts
(HasqlPoolObs (SQL.ConnectionObservation _ status)) -> case status of
SQL.ReadyForUseConnectionStatus -> do
SQL.ReadyForUseConnectionStatus _ -> do
incGauge poolAvailable
SQL.InUseConnectionStatus -> do
decGauge poolAvailable
@@ -64,7 +64,7 @@ observationMetrics MetricsState{..} obs = case obs of
incGauge poolWaiting
PoolRequestFullfilled ->
decGauge poolWaiting
SchemaCacheLoadedObs resTime -> do
SchemaCacheLoadedObs resTime _ -> do
withLabel schemaCacheLoads "SUCCESS" incCounter
setGauge schemaCacheQueryTime resTime
SchemaCacheErrorObs{} -> do
+13 -120
View File
@@ -1,4 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DeriveGeneric #-}
{-|
Module : PostgREST.Observation
Description : This module holds an Observation type which is the core of Observability for PostgREST.
@@ -9,23 +9,16 @@ Description : This module holds an Observation type which is the core of Observa
module PostgREST.Observation
( Observation(..)
, ObsFatalError(..)
, observationMessage
, ObservationHandler
, showOnSingleLine
) where
import qualified Data.ByteString.Lazy as LBS
import Data.List.NonEmpty (toList)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Hasql.Connection as SQL
import qualified Hasql.Pool as SQL
import qualified Hasql.Pool.Observation as SQL
import Network.HTTP.Types.Status (Status)
import Numeric (showFFloat)
import PostgREST.Config.PgVersion
import qualified PostgREST.Error as Error
import PostgREST.Query (MainQuery)
import PostgREST.SchemaCache (QueryTimings)
import Protolude hiding (toList)
@@ -39,15 +32,16 @@ data Observation
| DBConnectedObs Text
| SchemaCacheEmptyObs
| SchemaCacheErrorObs (NonEmpty Text) [Text] SQL.UsageError
| SchemaCacheQueriedObs Double
| SchemaCacheSummaryObs Text
| SchemaCacheLoadedObs Double
| SchemaCacheQueriedObs Double (Maybe QueryTimings)
| SchemaCacheLoadedObs Double Text
| ConnectionRetryObs Int
| DBListenStart Text
| DBListenFail Text (Either SQL.ConnectionError (Either SomeException ()))
| DBListenStart (Maybe ByteString) (Maybe ByteString) Text Text -- host, port, version string, channel
| DBListenFail Text (Either SQL.ConnectionError SomeException)
| DBListenRetry Int
| DBListenBugHint -- https://github.com/PostgREST/postgrest/issues/3147
| DBListenerGotSCacheMsg ByteString
| DBListenerGotConfigMsg ByteString
| DBListenerConnectionCleanupFail SomeException
| QueryObs MainQuery Status
| ConfigReadErrorObs SQL.UsageError
| ConfigInvalidObs Text
@@ -56,118 +50,17 @@ data Observation
| QueryErrorCodeHighObs SQL.UsageError
| QueryPgVersionError SQL.UsageError
| PoolInit Int
| PoolAcqTimeoutObs SQL.UsageError
| PoolAcqTimeoutObs
| HasqlPoolObs SQL.Observation
| PoolRequest
| PoolRequestFullfilled
| PoolFlushed
| JwtCacheLookup Bool
| JwtCacheEviction
| TerminationUnixSignalObs Text
| WarpServerObs Text
deriving (Generic)
data ObsFatalError = ServerAuthError | ServerPgrstBug | ServerError42P05 | ServerError08P01
type ObservationHandler = Observation -> IO ()
observationMessage :: Observation -> Text
observationMessage = \case
AdminStartObs address ->
"Admin server listening on " <> address
AppStartObs ver ->
"Starting PostgREST " <> T.decodeUtf8 ver <> "..."
AppServerAddressObs address ->
"API server listening on " <> address
DBConnectedObs ver ->
"Successfully connected to " <> ver
ExitUnsupportedPgVersion pgVer minPgVer ->
"Cannot run in this PostgreSQL version (" <> pgvName pgVer <> "), PostgREST needs at least " <> pgvName minPgVer
ExitDBNoRecoveryObs ->
"Automatic recovery disabled, exiting."
ExitDBFatalError ServerAuthError usageErr ->
"Failed to establish a connection. " <> jsonMessage usageErr
ExitDBFatalError ServerPgrstBug usageErr ->
"This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues. " <> jsonMessage usageErr
ExitDBFatalError ServerError42P05 usageErr ->
"If you are using connection poolers in transaction mode, try setting db-prepared-statements to false. " <> jsonMessage usageErr
ExitDBFatalError ServerError08P01 usageErr ->
"Connection poolers in statement mode are not supported." <> jsonMessage usageErr
SchemaCacheEmptyObs ->
T.decodeUtf8 . LBS.toStrict . Error.errorPayload $ Error.NoSchemaCacheError
SchemaCacheErrorObs dbSchemas extraPaths usageErr ->
"Failed to load the schema cache using "
<> "db-schemas=" <> T.intercalate "," (toList dbSchemas)
<> " and "
<> "db-extra-search-path=" <> T.intercalate "," extraPaths
<> ". " <> jsonMessage usageErr
SchemaCacheQueriedObs resultTime ->
"Schema cache queried in " <> showMillis resultTime <> " milliseconds"
SchemaCacheSummaryObs summary ->
"Schema cache loaded " <> summary
SchemaCacheLoadedObs resultTime ->
"Schema cache loaded in " <> showMillis resultTime <> " milliseconds"
ConnectionRetryObs delay ->
"Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
QueryPgVersionError usageErr ->
"Failed to query the PostgreSQL version. " <> jsonMessage usageErr
DBListenStart channel -> do
"Listening for database notifications on the " <> show channel <> " channel"
DBListenFail channel listenErr ->
"Failed listening for database notifications on the " <> show channel <> " channel. " <>
either showListenerConnError showListenerException listenErr
DBListenRetry delay ->
"Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
DBListenerGotSCacheMsg channel ->
"Received a schema cache reload message on the " <> show channel <> " channel"
DBListenerGotConfigMsg channel ->
"Received a config reload message on the " <> show channel <> " channel"
QueryObs{} ->
mempty -- TODO pending refactor: The logic for printing the query cannot be done here. Join the observationMessage function into observationLogger to avoid this mempty.
ConfigReadErrorObs usageErr ->
"Failed to query database settings for the config parameters." <> jsonMessage usageErr
QueryRoleSettingsErrorObs usageErr ->
"Failed to query the role settings. " <> jsonMessage usageErr
QueryErrorCodeHighObs usageErr ->
jsonMessage usageErr
ConfigInvalidObs err ->
"Failed reloading config: " <> err
ConfigSucceededObs ->
"Config reloaded"
PoolInit poolSize ->
"Connection Pool initialized with a maximum size of " <> show poolSize <> " connections"
PoolAcqTimeoutObs usageErr ->
jsonMessage usageErr
HasqlPoolObs (SQL.ConnectionObservation uuid status) ->
"Connection " <> show uuid <> (
case status of
SQL.ConnectingConnectionStatus -> " is being established"
SQL.ReadyForUseConnectionStatus -> " is available"
SQL.InUseConnectionStatus -> " is used"
SQL.TerminatedConnectionStatus reason -> " is terminated due to " <> case reason of
SQL.AgingConnectionTerminationReason -> "max lifetime"
SQL.IdlenessConnectionTerminationReason -> "max idletime"
SQL.ReleaseConnectionTerminationReason -> "release"
SQL.NetworkErrorConnectionTerminationReason _ -> "network error" -- usage error is already logged, no need to repeat the same message.
)
PoolRequest ->
"Trying to borrow a connection from pool"
PoolRequestFullfilled ->
"Borrowed a connection from the pool"
JwtCacheLookup _ ->
"Looked up a JWT in JWT cache"
JwtCacheEviction ->
"Evicted entry from JWT cache"
where
showMillis :: Double -> Text
showMillis x = toS $ showFFloat (Just 1) (x * 1000) ""
jsonMessage err = T.decodeUtf8 . LBS.toStrict . Error.errorPayload $ Error.PgError False err
showListenerConnError :: SQL.ConnectionError -> Text
showListenerConnError = maybe "Connection error" (showOnSingleLine '\t' . T.decodeUtf8)
showListenerException :: Either SomeException () -> Text
showListenerException (Right _) = "Failed getting notifications" -- should not happen as the listener will never finish (hasql-notifications uses `forever` internally) with a Right result
showListenerException (Left e) = showOnSingleLine '\t' $ show e
showOnSingleLine :: Char -> Text -> Text
showOnSingleLine split txt = T.intercalate " " $ T.filter (/= split) <$> T.lines txt -- the errors from hasql-notifications come intercalated with "\t\n"
+41 -66
View File
@@ -43,6 +43,7 @@ import PostgREST.Error (ApiRequestError (..),
Error (..),
SchemaCacheError (..))
import PostgREST.MediaType (MediaType (..))
import PostgREST.Plan.Negotiate (negotiateContent)
import PostgREST.Query.SqlFragment (sourceCTEName)
import PostgREST.RangeQuery (NonnegRange, allRange,
convertToLimitZeroRange,
@@ -50,7 +51,6 @@ import PostgREST.RangeQuery (NonnegRange, allRange,
import PostgREST.SchemaCache (SchemaCache (..))
import PostgREST.SchemaCache.Identifiers (FieldName,
QualifiedIdentifier (..),
RelIdentifier (..),
Schema)
import PostgREST.SchemaCache.Relationship (Cardinality (..),
Junction (..),
@@ -60,8 +60,6 @@ import PostgREST.SchemaCache.Relationship (Cardinality (..),
import PostgREST.SchemaCache.Representations (DataRepresentation (..),
RepresentationsMap)
import PostgREST.SchemaCache.Routine (MediaHandler (..),
MediaHandlerMap,
ResolvedHandler,
Routine (..),
RoutineMap,
RoutineParam (..),
@@ -172,19 +170,19 @@ dbActionPlan dbAct conf apiReq sCache = case dbAct of
wrappedReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> Bool -> Either Error CrudPlan
wrappedReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} headersOnly = do
qi <- findTable identifier (dbTables sCache)
qi <- findTable identifier sCache
rPlan <- readPlan qi conf sCache apiRequest
(handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest qi iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
(handler, mediaType) <- mapLeft ApiRequestErr $ negotiateContent conf apiRequest qi iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestErr $ InvalidPreferences invalidPrefs else Right ()
return $ WrappedReadPlan rPlan SQL.Read handler mediaType headersOnly qi
mutateReadPlan :: Mutation -> ApiRequest -> QualifiedIdentifier -> AppConfig -> SchemaCache -> Either Error CrudPlan
mutateReadPlan mutation apiRequest@ApiRequest{iPreferences=Preferences{..},..} identifier conf sCache = do
qi <- findTable identifier (dbTables sCache)
qi <- findTable identifier sCache
rPlan <- readPlan qi conf sCache apiRequest
mPlan <- mutatePlan mutation qi apiRequest sCache rPlan
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
(handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest qi iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestErr $ InvalidPreferences invalidPrefs else Right ()
(handler, mediaType) <- mapLeft ApiRequestErr $ negotiateContent conf apiRequest qi iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
return $ MutateReadPlan rPlan mPlan SQL.Write handler mediaType mutation qi
callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CrudPlan
@@ -206,15 +204,15 @@ callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferenc
(Inv, Routine.Immutable) -> SQL.Read
(Inv, Routine.Volatile) -> SQL.Write
cPlan = callPlan proc apiRequest paramKeys args rPlan
(handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest relIdentifier iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
(handler, mediaType) <- mapLeft ApiRequestErr $ negotiateContent conf apiRequest relIdentifier iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestErr $ InvalidPreferences invalidPrefs else Right ()
failMaxAffectedRpcReturnsSingle (preferMaxAffected, preferHandling) proc
return $ CallReadPlan rPlan cPlan txMode proc handler mediaType invMethod identifier
where
qsParams' = QueryParams.qsParams iQueryParams
failMaxAffectedRpcReturnsSingle :: (Maybe PreferMaxAffected, Maybe PreferHandling) -> Routine -> Either Error ()
failMaxAffectedRpcReturnsSingle (Just (PreferMaxAffected _), Just Strict) rout = if funcReturnsSingle rout then Left $ ApiRequestError MaxAffectedRpcViolation else Right ()
failMaxAffectedRpcReturnsSingle (Just (PreferMaxAffected _), Just Strict) rout = if funcReturnsSingle rout then Left $ ApiRequestErr MaxAffectedRpcViolation else Right ()
failMaxAffectedRpcReturnsSingle _ _ = Right ()
hasDefaultSelect :: ReadPlanTree -> Bool
@@ -227,7 +225,7 @@ inspectPlan apiRequest headersOnly schema = do
accepts = iAcceptMediaType apiRequest
mediaType <- if not . null $ L.intersect accepts producedMTs
then Right MTOpenAPI
else Left . ApiRequestError . MediaTypeError $ MediaType.toMime <$> accepts
else Left . ApiRequestErr . MediaTypeError $ MediaType.toMime <$> accepts
return $ InspectPlan mediaType SQL.Read headersOnly schema
{-|
@@ -256,14 +254,16 @@ findProc qi argumentsKeys allProcs contentMediaType isInvPost =
| hasSingleUnnamedParam proc = (ts,proc:fs)
| otherwise = (ts,fs)
-- If the function is called with post and has a single unnamed parameter
-- it can be called depending on content type and the parameter type
hasSingleUnnamedParam Function{pdParams=[RoutineParam{ppType}]} = isInvPost && case (contentMediaType, ppType) of
(MTApplicationJSON, "json") -> True
(MTApplicationJSON, "jsonb") -> True
(MTTextPlain, "text") -> True
(MTTextXML, "xml") -> True
(MTOctetStream, "bytea") -> True
_ -> False
-- it can be called depending on content type and the parameter type.
-- The parameter must have no declared name (ppName == mempty).
hasSingleUnnamedParam Function{pdParams=[RoutineParam{ppName, ppType}]} =
isInvPost && ppName == mempty && case (contentMediaType, ppType) of
(MTApplicationJSON, "json") -> True
(MTApplicationJSON, "jsonb") -> True
(MTTextPlain, "text") -> True
(MTTextXML, "xml") -> True
(MTOctetStream, "bytea") -> True
_ -> False
hasSingleUnnamedParam _ = False
matchesParams proc =
let
@@ -784,7 +784,7 @@ hoistIntoRelSelectFields _ r = r
-- to order once it's aggregated if it's not selected in the inner query beforehand.
addToManyOrderSelects :: ReadPlanTree -> Either Error ReadPlanTree
addToManyOrderSelects (Node rp@ReadPlan{order, select, relAggAlias, relSelect, relSpread = Just ToManySpread {}} forest)
| anyAggSel || anyAggRelSel = Left $ ApiRequestError $ NotImplemented "Aggregates are not implemented for one-to-many or many-to-many spreads."
| anyAggSel || anyAggRelSel = Left $ ApiRequestErr $ NotImplemented "Aggregates are not implemented for one-to-many or many-to-many spreads."
| otherwise = Node rp { order = [], relSpread = newRelSpread } <$> addToManyOrderSelects `traverse` forest
where
newRelSpread = Just ToManySpread { stExtraSelect = addSprExtraSelects, stOrder = addSprOrder}
@@ -806,14 +806,14 @@ addToManyOrderSelects (Node rp forest) = Node rp <$> addToManyOrderSelects `trav
validateAggFunctions :: Bool -> ReadPlanTree -> Either Error ReadPlanTree
validateAggFunctions aggFunctionsAllowed (Node rp@ReadPlan {select} forest)
| not aggFunctionsAllowed && any (isJust . csAggFunction) select = Left $ ApiRequestError AggregatesNotAllowed
| not aggFunctionsAllowed && any (isJust . csAggFunction) select = Left $ ApiRequestErr AggregatesNotAllowed
| otherwise = Node rp <$> traverse (validateAggFunctions aggFunctionsAllowed) forest
-- | Lookup table in the schema cache before creating read plan
findTable :: QualifiedIdentifier -> TablesMap -> Either Error QualifiedIdentifier
findTable qi@QualifiedIdentifier{..} tableMap =
case HM.lookup qi tableMap of
Nothing -> Left $ SchemaCacheErr $ TableNotFound qiSchema qiName (HM.elems tableMap)
findTable :: QualifiedIdentifier -> SchemaCache -> Either Error QualifiedIdentifier
findTable qi@QualifiedIdentifier{..} sc@SchemaCache{dbTables} =
case HM.lookup qi dbTables of
Nothing -> Left $ SchemaCacheErr $ TableNotFound qiSchema qiName sc
Just _ -> Right qi
addFilters :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
@@ -860,9 +860,9 @@ addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
name = fromMaybe relName relAlias in
if isToOne == Just True
then Right $ cot{coRelation=relAggAlias}
else Left $ ApiRequestError $ RelatedOrderNotToOne (qiName from) name
else Left $ ApiRequestErr $ RelatedOrderNotToOne (qiName from) name
Nothing ->
Left $ ApiRequestError $ NotEmbedded coRelation
Left $ ApiRequestErr $ NotEmbedded coRelation
-- | Searches for null filters on embeds, e.g. `projects=not.is.null` on `GET /clients?select=*,projects(*)&projects=not.is.null`
--
@@ -956,17 +956,24 @@ addRanges ApiRequest{..} rReq =
_ -> foldr addRangeToNode (Right rReq) =<< ranges
where
ranges :: Either Error [(EmbedPath, NonnegRange)]
ranges = first (ApiRequestError . QueryParamError) $ QueryParams.pRequestRange `traverse` HM.toList iRange
ranges = first (ApiRequestErr . QueryParamError) $ QueryParams.pRequestRange `traverse` HM.toList iRange
addRangeToNode :: (EmbedPath, NonnegRange) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
addRangeToNode = updateNode (\r (Node q f) -> Node q{range_=r} f)
addLogicTrees :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
addLogicTrees ctx ApiRequest{..} rReq =
foldr addLogicTreeToNode (Right rReq) qsLogic
foldr addLogicTreeToNode (Right rReq) logic
where
QueryParams.QueryParams{..} = iQueryParams
logic =
case iAction of
ActDb (ActRelationRead _ _) -> qsLogic
ActDb (ActRoutine _ _) -> qsLogic
-- For mutations, take the non-root logic filters. These will only affect the embeddings and not the top level of the returned representation.
_ -> filter (not . null . fst) qsLogic
addLogicTreeToNode :: (EmbedPath, LogicTree) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
addLogicTreeToNode = updateNode (\t (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=resolveLogicTree ctx{qi=fromTable} t:lf} f)
@@ -983,13 +990,13 @@ updateNode f ([], a) rr = f a <$> rr
updateNode _ _ (Left e) = Left e
updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
case findNode of
Nothing -> Left $ ApiRequestError $ NotEmbedded targetNodeName
Nothing -> Left $ ApiRequestErr $ NotEmbedded targetNodeName
Just target ->
(\node -> Node rootNode $ node : delete target forest) <$>
updateNode f (remainingPath, a) (Right target)
where
findNode :: Maybe ReadPlanTree
findNode = find (\(Node ReadPlan{relName, relAlias} _) -> relName == targetNodeName || relAlias == Just targetNodeName) forest
findNode = find (\(Node ReadPlan{relName, relAlias} _) -> fromMaybe relName relAlias == targetNodeName) forest
mutatePlan :: Mutation -> QualifiedIdentifier -> ApiRequest -> SchemaCache -> ReadPlanTree -> Either Error MutatePlan
mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{dbTables, dbRepresentations} readReq =
@@ -1007,7 +1014,7 @@ mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{
_ -> False) qsFiltersRoot
then mapRight (\typedColumns -> Insert qi typedColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings mempty False) typedColumnsOrError
else
Left $ ApiRequestError InvalidFilters
Left $ ApiRequestErr InvalidFilters
MutationDelete -> Right $ Delete qi combinedLogic returnings
where
ctx = ResolverContext dbTables dbRepresentations qi "json"
@@ -1114,35 +1121,3 @@ inferColsEmbedNeeds (Node ReadPlan{select} forest) pkCols
-- they are later concatenated with AND in the QueryBuilder
addFilterToLogicForest :: CoercibleFilter -> [CoercibleLogicTree] -> [CoercibleLogicTree]
addFilterToLogicForest flt lf = CoercibleStmnt flt : lf
-- | Do content negotiation. i.e. choose a media type based on the intersection of accepted/produced media types.
negotiateContent :: AppConfig -> ApiRequest -> QualifiedIdentifier -> [MediaType] -> MediaHandlerMap -> Bool -> Either ApiRequestError ResolvedHandler
negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRepresentation=rep}} identifier accepts produces defaultSelect =
case (act, firstAcceptedPick) of
(_, Nothing) -> Left . MediaTypeError $ map MediaType.toMime accepts
(ActDb (ActRelationMut _ _), Just (x, mt)) -> Right (if rep == Just Full then x else NoAgg, mt)
-- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
-- TODO: despite no aggregate, these are responding with a Content-Type, which is not correct.
(ActDb (ActRelationRead _ True), Just (_, mt)) -> Right (NoAgg, mt)
(ActDb (ActRoutine _ (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
(_, Just (x, mt)) -> Right (x, mt)
where
firstAcceptedPick = listToMaybe $ mapMaybe matchMT accepts -- If there are multiple accepted media types, pick the first. This is usual in content negotiation.
matchMT mt = case mt of
-- all the vendored media types have special handling as they have media type parameters, they cannot be overridden
m@(MTVndSingularJSON strip) -> Just (BuiltinAggSingleJson strip, m)
m@MTVndArrayJSONStrip -> Just (BuiltinAggArrayJsonStrip, m)
m@(MTVndPlan (MTVndSingularJSON strip) _ _) -> mtPlanToNothing $ Just (BuiltinAggSingleJson strip, m)
m@(MTVndPlan MTVndArrayJSONStrip _ _) -> mtPlanToNothing $ Just (BuiltinAggArrayJsonStrip, m)
-- TODO the plan should have its own MediaHandler instead of relying on MediaType
m@(MTVndPlan mType _ _) -> mtPlanToNothing $ ((,) . fst <$> lookupHandler mType) <*> pure m
-- all the other media types can be overridden
x -> lookupHandler x
mtPlanToNothing x = if configDbPlanEnabled conf then x else Nothing -- don't find anything if the plan media type is not allowed
lookupHandler mt =
when' defaultSelect (HM.lookup (RelId identifier, MTAny) produces) <|> -- lookup for identifier and `*/*`
when' defaultSelect (HM.lookup (RelId identifier, mt) produces) <|> -- lookup for identifier and a particular media type
HM.lookup (RelAnyElement, mt) produces -- lookup for anyelement and a particular media type
when' :: Bool -> Maybe a -> Maybe a
when' True (Just a) = Just a
when' _ _ = Nothing
+82
View File
@@ -0,0 +1,82 @@
{-|
Module : PostgREST.Plan.Negotiate
Description : PostgREST Content Negotiation
This module contains logic for content negotiation.
RFC: https://datatracker.ietf.org/doc/html/rfc7231#section-3.4
-}
module PostgREST.Plan.Negotiate
( negotiateContent
) where
import qualified Data.HashMap.Strict as HM
import PostgREST.ApiRequest (ApiRequest (..))
import PostgREST.Config (AppConfig (..))
import PostgREST.Error (ApiRequestError (..))
import PostgREST.MediaType (MediaType (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
RelIdentifier (..))
import PostgREST.SchemaCache.Routine (MediaHandler (..),
MediaHandlerMap,
ResolvedHandler)
import PostgREST.ApiRequest.Preferences
import PostgREST.ApiRequest.Types
import qualified PostgREST.MediaType as MediaType
import Protolude hiding (from)
-- We have two general cases of return values from database objects
-- (tables/views/functions):
--
-- 1. "un-mime-typed" values, in most of the cases this is a composite/row
-- value, for example for tables or views, but also often for functions.
-- It can be simple integer values or text or bytea as well.
--
-- For this, we need handlers to transform the "non-mime-typed" values
-- into "mimetypes". We have a default builtin handler that does
-- "application/json". We can add more handlers via aggregates.
--
-- 2. "mime-typed" values, which specifically return a domain type that is
-- associated to a certain mimetype. e.g, a function returning only
-- "image/png".
--
-- FIXME:
-- If the function returns a domain type - let's say image/png, we should
-- accept */*, image/*, and image/png.
-- Related issue: https://github.com/PostgREST/postgrest/issues/3391
-- | Do content negotiation. i.e. choose a media type based on the
-- intersection of accepted/produced media types.
negotiateContent :: AppConfig -> ApiRequest -> QualifiedIdentifier -> [MediaType] -> MediaHandlerMap -> Bool -> Either ApiRequestError ResolvedHandler
negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRepresentation=rep}} identifier accepts produces defaultSelect =
case (act, firstAcceptedPick) of
(_, Nothing) -> Left . MediaTypeError $ map MediaType.toMime accepts
(ActDb (ActRelationMut _ _), Just (x, mt)) -> Right (if rep == Just Full then x else NoAgg, mt)
-- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
-- TODO: despite no aggregate, these are responding with a Content-Type, which is not correct.
(ActDb (ActRelationRead _ True), Just (_, mt)) -> Right (NoAgg, mt)
(ActDb (ActRoutine _ (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
(_, Just (x, mt)) -> Right (x, mt)
where
firstAcceptedPick = listToMaybe $ mapMaybe matchMT accepts -- If there are multiple accepted media types, pick the first. This is usual in content negotiation.
matchMT mt = case mt of
-- all the vendored media types have special handling as they have media type parameters, they cannot be overridden
m@(MTVndSingularJSON strip) -> Just (BuiltinAggSingleJson strip, m)
m@MTVndArrayJSONStrip -> Just (BuiltinAggArrayJsonStrip, m)
m@(MTVndPlan (MTVndSingularJSON strip) _ _) -> mtPlanToNothing $ Just (BuiltinAggSingleJson strip, m)
m@(MTVndPlan MTVndArrayJSONStrip _ _) -> mtPlanToNothing $ Just (BuiltinAggArrayJsonStrip, m)
-- TODO the plan should have its own MediaHandler instead of relying on MediaType
m@(MTVndPlan mType _ _) -> mtPlanToNothing $ ((,) . fst <$> lookupHandler mType) <*> pure m
-- all the other media types can be overridden
x -> lookupHandler x
mtPlanToNothing x = if configDbPlanEnabled conf then x else Nothing -- don't find anything if the plan media type is not allowed
lookupHandler mt =
when' defaultSelect (HM.lookup (RelId identifier, MTAny) produces) <|> -- lookup for identifier and `*/*`
when' defaultSelect (HM.lookup (RelId identifier, mt) produces) <|> -- lookup for identifier and a particular media type
HM.lookup (RelAnyElement, mt) produces -- lookup for anyelement and a particular media type
when' :: Bool -> Maybe a -> Maybe a
when' True (Just a) = Just a
when' _ _ = Nothing
+1 -1
View File
@@ -47,7 +47,7 @@ data CoercibleField = CoercibleField
, cfBaseType :: Text -- ^ The base type of the field in case of domains, or just the type otherwise (without modifiers in case of pg_catalog types)
, cfTransform :: Maybe TransformerProc -- ^ The optional mapping from irType -> targetType.
, cfDefault :: Maybe Text
, cfFullRow :: Bool -- ^ True if the field represents the whole selected row. Used in spread rels: instead of COUNT(*), it does a COUNT(<row>) in order to not mix with other spreaded resources.
, cfFullRow :: Bool -- ^ True if the field represents the whole selected row. Used in spread rels: instead of COUNT(*), it does a COUNT(<row>) in order to not mix with other spread resources.
} deriving (Eq, Show)
unknownField :: FieldName -> JsonPath -> CoercibleField
+3 -3
View File
@@ -43,16 +43,16 @@ data MainQuery = MainQuery
mainQuery :: ActionPlan -> AppConfig -> ApiRequest -> AuthResult -> Maybe QualifiedIdentifier -> MainQuery
mainQuery (NoDb _) _ _ _ _ = MainQuery mempty Nothing mempty (mempty, mempty, mempty) mempty
mainQuery (Db plan) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} authRes preReq =
mainQuery (Db plan) conf@AppConfig{..} apiReq@ApiRequest{iTopLevelRange=range, iPreferences=Preferences{..}} authRes preReq =
let genQ = MainQuery (PreQuery.txVarQuery plan conf authRes apiReq) (PreQuery.preReqQuery <$> preReq) in
case plan of
DbCrud _ WrappedReadPlan{..} ->
let countQuery = QueryBuilder.readPlanToCountQuery wrReadPlan in
genQ (Statements.mainRead wrReadPlan countQuery preferCount configDbMaxRows pMedia wrHandler) (mempty, mempty, mempty)
genQ (Statements.mainRead wrReadPlan countQuery preferCount configDbMaxRows range pMedia wrHandler) (mempty, mempty, mempty)
(if shouldExplainCount preferCount then Just (Statements.postExplain countQuery) else Nothing)
DbCrud _ MutateReadPlan{..} ->
genQ (Statements.mainWrite mrReadPlan mrMutatePlan pMedia mrHandler preferRepresentation preferResolution) (mempty, mempty, mempty) mempty
DbCrud _ CallReadPlan{..} ->
genQ (Statements.mainCall crProc crCallPlan crReadPlan preferCount pMedia crHandler) (mempty, mempty, mempty) mempty
genQ (Statements.mainCall crProc crCallPlan crReadPlan preferCount configDbMaxRows range pMedia crHandler) (mempty, mempty, mempty) mempty
MayUseDb InspectPlan{ipSchema=tSchema} ->
genQ mempty (SqlFragment.accessibleTables tSchema, SqlFragment.accessibleFuncs tSchema, SqlFragment.schemaDescription tSchema) mempty
+5 -1
View File
@@ -10,6 +10,7 @@ module PostgREST.Query.PreQuery
) where
import qualified Data.Aeson as JSON
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.HashMap.Strict as HM
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
@@ -46,7 +47,10 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
pathSql = setConfigWithConstantName ("request.path", iPath)
headersSql = setConfigWithConstantNameJSON "request.headers" iHeaders
cookiesSql = setConfigWithConstantNameJSON "request.cookies" iCookies
claimsSql = [setConfigWithConstantName ("request.jwt.claims", LBS.toStrict $ JSON.encode authClaims)]
claimsSql = [setConfigWithConstantName ("request.jwt.claims", LBS.toStrict $ JSON.encode claims)]
where
claims = authClaims & KM.insert "role" (JSON.String $ decodeUtf8 authRole) -- insert "role" to claims as well
roleSql = [setConfigWithConstantName ("role", authRole)]
roleSettingsSql = setConfigWithDynamicName <$> HM.toList (fromMaybe mempty $ HM.lookup authRole configRoleSettings)
appSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> configAppSettings
+1 -1
View File
@@ -264,7 +264,7 @@ readPlanToCountQuery (Node ReadPlan{from=mainQi, fromAlias=tblAlias, where_=logi
limitedQuery :: SQL.Snippet -> Maybe Integer -> SQL.Snippet
limitedQuery query maxRows = query <> SQL.sql (maybe mempty (\x -> " LIMIT " <> BS.pack (show x)) maxRows)
-- TODO refactor so this function is uneeded and ComputedRelationship QualifiedIdentifier comes from the ReadPlan type
-- TODO refactor so this function is unneeded and ComputedRelationship QualifiedIdentifier comes from the ReadPlan type
getQualifiedIdentifier :: Maybe Relationship -> QualifiedIdentifier -> Maybe Alias -> QualifiedIdentifier
getQualifiedIdentifier rel mainQi tblAlias = case rel of
Just ComputedRelationship{relFunction} -> QualifiedIdentifier mempty $ fromMaybe (qiName relFunction) tblAlias
+17 -9
View File
@@ -23,6 +23,7 @@ module PostgREST.Query.SqlFragment
, locationF
, noLocationF
, orderF
, pageCountSelectF
, pgFmtColumn
, pgFmtFilter
, pgFmtIdent
@@ -96,6 +97,7 @@ import PostgREST.SchemaCache.Routine (MediaHandler (..),
Routine (..),
funcReturnsScalar,
funcReturnsSetOfScalar,
funcReturnsSingle,
funcReturnsSingleComposite)
import Protolude hiding (Sum, cast)
@@ -485,15 +487,21 @@ pgFmtGroup _ CoercibleSelectField{csAggFunction=Just _} = Nothing
pgFmtGroup _ CoercibleSelectField{csAlias=Just alias, csAggFunction=Nothing} = Just $ pgFmtIdent alias
pgFmtGroup qi CoercibleSelectField{csField=fld, csAlias=Nothing, csAggFunction=Nothing} = Just $ pgFmtField qi fld
countF :: SQL.Snippet -> Bool -> (SQL.Snippet, SQL.Snippet)
countF countQuery shouldCount =
if shouldCount
then (
", pgrst_source_count AS (" <> countQuery <> ")"
, "(SELECT pg_catalog.count(*) FROM pgrst_source_count)" )
else (
mempty
, "null::bigint")
countF :: SQL.Snippet -> SQL.Snippet -> Bool -> Maybe Integer -> NonnegRange -> (SQL.Snippet, SQL.Snippet)
countF countQuery pageCountSelect shouldCount maxRows range
| shouldCount = if isJust maxRows || range /= allRange
then ( ", pgrst_source_count AS (" <> countQuery <> ")"
, "(SELECT pg_catalog.count(*) FROM pgrst_source_count)" )
-- When there are no db-max-rows and limits/offsets, the total count will be the same as the page count,
-- so we use the same page count here to avoid doing a separate aggregated count.
else ( mempty, pageCountSelect )
| otherwise = ( mempty, "null::bigint" )
pageCountSelectF :: Maybe Routine -> SQL.Snippet
pageCountSelectF rout =
if maybe False funcReturnsSingle rout
then "1"
else "pg_catalog.count(_postgrest_t)"
returningF :: QualifiedIdentifier -> [FieldName] -> SQL.Snippet
returningF qi returnings =
+13 -13
View File
@@ -20,8 +20,8 @@ import PostgREST.Plan.MutatePlan as MTPlan
import PostgREST.Plan.ReadPlan
import PostgREST.Query.QueryBuilder
import PostgREST.Query.SqlFragment
import PostgREST.SchemaCache.Routine (MediaHandler (..), Routine,
funcReturnsSingle)
import PostgREST.RangeQuery (NonnegRange)
import PostgREST.SchemaCache.Routine (MediaHandler (..), Routine)
import Protolude
@@ -64,23 +64,24 @@ mainWrite rPlan mtplan mt handler rep resolution = mtSnippet mt snippet
_ -> (False,False, mempty);
mainRead :: ReadPlanTree -> SQL.Snippet -> Maybe PreferCount -> Maybe Integer ->
MediaType -> MediaHandler -> SQL.Snippet
mainRead rPlan countQuery pCount maxRows mt handler = mtSnippet mt snippet
NonnegRange -> MediaType -> MediaHandler -> SQL.Snippet
mainRead rPlan countQuery pCount maxRows range mt handler = mtSnippet mt snippet
where
snippet =
"WITH " <> sourceCTE <> " AS ( " <> selectQuery <> " ) " <>
countCTEF <> " " <>
"SELECT " <>
countResultF <> " AS total_result_set, " <>
"pg_catalog.count(_postgrest_t) AS page_total, " <>
pageCountSelect <> " AS page_total, " <>
handlerF Nothing handler <> " AS body, " <>
responseHeadersF <> " AS response_headers, " <>
responseStatusF <> " AS response_status, " <>
"''" <> " AS response_inserted " <>
"FROM ( SELECT * FROM " <> sourceCTE <> " ) _postgrest_t"
(countCTEF, countResultF) = countF countQ $ shouldCount pCount
(countCTEF, countResultF) = countF countQ pageCountSelect (shouldCount pCount) maxRows range
selectQuery = readPlanToQuery rPlan
pageCountSelect = pageCountSelectF Nothing
countQ =
if pCount == Just EstimatedCount then
-- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
@@ -88,28 +89,27 @@ mainRead rPlan countQuery pCount maxRows mt handler = mtSnippet mt snippet
else
countQuery
mainCall :: Routine -> CallPlan -> ReadPlanTree -> Maybe PreferCount ->
MediaType -> MediaHandler -> SQL.Snippet
mainCall rout cPlan rPlan pCount mt handler = mtSnippet mt snippet
mainCall :: Routine -> CallPlan -> ReadPlanTree -> Maybe PreferCount -> Maybe Integer ->
NonnegRange-> MediaType -> MediaHandler -> SQL.Snippet
mainCall rout cPlan rPlan pCount maxRows range mt handler = mtSnippet mt snippet
where
snippet =
"WITH " <> sourceCTE <> " AS (" <> callProcQuery <> ") " <>
countCTEF <>
"SELECT " <>
countResultF <> " AS total_result_set, " <>
(if funcReturnsSingle rout
then "1"
else "pg_catalog.count(_postgrest_t)") <> " AS page_total, " <>
pageCountSelect <> " AS page_total, " <>
handlerF (Just rout) handler <> " AS body, " <>
responseHeadersF <> " AS response_headers, " <>
responseStatusF <> " AS response_status, " <>
"''" <> " AS response_inserted " <>
"FROM (" <> selectQuery <> ") _postgrest_t"
(countCTEF, countResultF) = countF countQuery $ shouldCount pCount
(countCTEF, countResultF) = countF countQuery pageCountSelect (shouldCount pCount) maxRows range
selectQuery = readPlanToQuery rPlan
callProcQuery = callPlanToQuery cPlan
countQuery = readPlanToCountQuery rPlan
pageCountSelect = pageCountSelectF (Just rout)
-- This occurs after the main query runs, that's why it's prefixed with "post"
postExplain :: SQL.Snippet -> SQL.Snippet
+80 -54
View File
@@ -60,13 +60,14 @@ data PgrstResponse = PgrstResponse {
, pgrstBody :: LBS.ByteString
}
actionResponse :: DbResult -> ApiRequest -> (Text, Text) -> AppConfig -> SchemaCache -> Schema -> Bool -> Either Error.Error PgrstResponse
actionResponse :: DbResult -> ApiRequest -> (Text, Text) -> AppConfig -> SchemaCache -> Either Error.Error PgrstResponse
actionResponse (DbCrudResult WrappedReadPlan{pMedia, wrHdrsOnly=headersOnly, crudQi=identifier} RSStandard{..}) ctxApiRequest@ApiRequest{iPreferences=Preferences{..},..} _ _ _ _ _ = do
actionResponse (DbCrudResult plan@WrappedReadPlan{pMedia, wrHdrsOnly=headersOnly, crudQi=identifier} RSStandard{..}) ctxApiRequest@ApiRequest{..} _ AppConfig{..} _ = do
let
(status, contentRange) = RangeQuery.rangeStatusHeader iTopLevelRange rsQueryTotal rsTableTotal
cLHeader = if headersOnly then mempty else [contentLengthHeaderStrict rsBody]
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing Nothing preferCount preferTransaction Nothing preferHandling preferTimezone Nothing []
cLHeader = if headersOnly then mempty else [ contentLengthHeader bod ]
prefHeader = maybeToList . prefAppliedHeader $ responsePreferences plan ctxApiRequest
headers =
[ contentRange
, ( "Content-Location"
@@ -78,7 +79,7 @@ actionResponse (DbCrudResult WrappedReadPlan{pMedia, wrHdrsOnly=headersOnly, cru
++ cLHeader
++ contentTypeHeaders pMedia ctxApiRequest
++ prefHeader
bod | status == HTTP.status416 = Error.errorPayload $ Error.ApiRequestError $ Error.InvalidRange $
bod | status == HTTP.status416 = Error.errorPayload configClientErrorVerbosity $ Error.ApiRequestErr $ Error.InvalidRange $
Error.OutOfBounds (show $ RangeQuery.rangeOffset iTopLevelRange) (maybe "0" show rsTableTotal)
| headersOnly = mempty
| otherwise = LBS.fromStrict rsBody
@@ -87,12 +88,10 @@ actionResponse (DbCrudResult WrappedReadPlan{pMedia, wrHdrsOnly=headersOnly, cru
Right $ PgrstResponse ovStatus ovHeaders bod
actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationCreate, mrMutatePlan, pMedia, crudQi=QualifiedIdentifier{..}} RSStandard{..}) ctxApiRequest@ApiRequest{iPreferences=Preferences{..}, ..} _ _ _ _ _ = do
actionResponse (DbCrudResult plan@MutateReadPlan{mrMutation=MutationCreate, pMedia, crudQi=QualifiedIdentifier{..}} RSStandard{..}) ctxApiRequest@ApiRequest{..} _ _ _ = do
let
pkCols = case mrMutatePlan of { Insert{insPkCols} -> insPkCols; _ -> mempty;}
prefHeader = prefAppliedHeader $
Preferences (if null pkCols && isNothing (qsOnConflict iQueryParams) then Nothing else preferResolution)
preferRepresentation preferCount preferTransaction preferMissing preferHandling preferTimezone Nothing []
prefHeader = prefAppliedHeader $ responsePreferences plan ctxApiRequest
headers =
catMaybes
[ if null rsLocation then
@@ -105,37 +104,39 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationCreate, mrMutateP
<> HTTP.renderSimpleQuery True rsLocation
)
, Just . RangeQuery.contentRangeH 1 0 $
if shouldCount preferCount then Just rsQueryTotal else Nothing
, Just $ contentLengthHeaderStrict rsBody
if shouldCount (preferCount iPreferences) then Just rsQueryTotal else Nothing
, prefHeader ]
isInsertIfGTZero i =
if i <= 0 && preferResolution == Just MergeDuplicates then
if i <= 0 && preferResolution iPreferences == Just MergeDuplicates then
HTTP.status200
else
HTTP.status201
status = maybe HTTP.status200 isInsertIfGTZero rsInserted
(headers', bod) = case preferRepresentation of
(headers', bod) = case preferRepresentation iPreferences of
Just Full -> (headers ++ contentTypeHeaders pMedia ctxApiRequest, LBS.fromStrict rsBody)
Just None -> (headers, mempty)
Just HeadersOnly -> (headers, mempty)
Nothing -> (headers, mempty)
(ovStatus, ovHeaders) <- overrideStatusHeaders rsGucStatus rsGucHeaders status headers'
(ovStatus, ovHeaders) <- overrideStatusHeaders rsGucStatus rsGucHeaders status $ contentLengthHeader bod:headers'
Right $ PgrstResponse ovStatus ovHeaders bod
actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationUpdate, pMedia} RSStandard{..}) ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} _ _ _ _ _ = do
actionResponse (DbCrudResult plan@MutateReadPlan{mrMutation=MutationUpdate, pMedia} RSStandard{..}) ctxApiRequest@ApiRequest{..} _ _ _ = do
let
contentRangeHeader =
Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $
if shouldCount preferCount then Just rsQueryTotal else Nothing
prefHeader = prefAppliedHeader $ Preferences Nothing preferRepresentation preferCount preferTransaction preferMissing preferHandling preferTimezone preferMaxAffected []
if shouldCount (preferCount iPreferences) then Just rsQueryTotal else Nothing
prefHeader = prefAppliedHeader $ responsePreferences plan ctxApiRequest
headers = catMaybes [contentRangeHeader, prefHeader]
lbsBody = LBS.fromStrict rsBody
let (status, headers', body) =
case preferRepresentation of
Just Full -> (HTTP.status200, headers ++ [contentLengthHeaderStrict rsBody] ++ contentTypeHeaders pMedia ctxApiRequest, LBS.fromStrict rsBody)
case preferRepresentation iPreferences of
Just Full -> (HTTP.status200, headers ++ [contentLengthHeader lbsBody] ++ contentTypeHeaders pMedia ctxApiRequest, lbsBody)
Just None -> (HTTP.status204, headers, mempty)
_ -> (HTTP.status204, headers, mempty)
@@ -143,31 +144,33 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationUpdate, pMedia} R
Right $ PgrstResponse ovStatus ovHeaders body
actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationSingleUpsert, pMedia} RSStandard{..}) ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} _ _ _ _ _ = do
actionResponse (DbCrudResult plan@MutateReadPlan{mrMutation=MutationSingleUpsert, pMedia} RSStandard{..}) ctxApiRequest@ApiRequest{..} _ _ _ = do
let
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing preferRepresentation preferCount preferTransaction Nothing preferHandling preferTimezone Nothing []
cLHeader = [contentLengthHeaderStrict rsBody]
prefHeader = maybeToList . prefAppliedHeader $ responsePreferences plan ctxApiRequest
lbsBody = LBS.fromStrict rsBody
cLHeader = [contentLengthHeader lbsBody]
cTHeader = contentTypeHeaders pMedia ctxApiRequest
let isInsertIfGTZero i = if i > 0 then HTTP.status201 else HTTP.status200
upsertStatus = isInsertIfGTZero $ fromJust rsInserted
(status, headers, body) =
case preferRepresentation of
Just Full -> (upsertStatus, cLHeader ++ cTHeader ++ prefHeader, LBS.fromStrict rsBody)
case preferRepresentation iPreferences of
Just Full -> (upsertStatus, cLHeader ++ cTHeader ++ prefHeader, lbsBody)
Just None -> (HTTP.status204, prefHeader, mempty)
_ -> (HTTP.status204, prefHeader, mempty)
(ovStatus, ovHeaders) <- overrideStatusHeaders rsGucStatus rsGucHeaders status headers
Right $ PgrstResponse ovStatus ovHeaders body
actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationDelete, pMedia} RSStandard{..}) ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} _ _ _ _ _ = do
actionResponse (DbCrudResult plan@MutateReadPlan{mrMutation=MutationDelete, pMedia} RSStandard{..}) ctxApiRequest@ApiRequest{..} _ _ _ = do
let
contentRangeHeader = RangeQuery.contentRangeH 1 0 $ if shouldCount preferCount then Just rsQueryTotal else Nothing
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing preferRepresentation preferCount preferTransaction Nothing preferHandling preferTimezone preferMaxAffected []
contentRangeHeader = RangeQuery.contentRangeH 1 0 $ if shouldCount (preferCount iPreferences) then Just rsQueryTotal else Nothing
prefHeader = maybeToList . prefAppliedHeader $ responsePreferences plan ctxApiRequest
headers = contentRangeHeader : prefHeader
lbsBody = LBS.fromStrict rsBody
(status, headers', body) =
case preferRepresentation of
Just Full -> (HTTP.status200, headers ++ [contentLengthHeaderStrict rsBody] ++ contentTypeHeaders pMedia ctxApiRequest, LBS.fromStrict rsBody)
case preferRepresentation iPreferences of
Just Full -> (HTTP.status200, headers ++ [contentLengthHeader lbsBody] ++ contentTypeHeaders pMedia ctxApiRequest, lbsBody)
Just None -> (HTTP.status204, headers, mempty)
_ -> (HTTP.status204, headers, mempty)
@@ -175,17 +178,17 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationDelete, pMedia} R
Right $ PgrstResponse ovStatus ovHeaders body
actionResponse (DbCrudResult CallReadPlan{pMedia, crInvMthd=invMethod, crProc=proc} RSStandard {..}) ctxApiRequest@ApiRequest{iPreferences=Preferences{..},..} _ _ _ _ _ = do
actionResponse (DbCrudResult plan@CallReadPlan{pMedia, crInvMthd=invMethod, crProc=proc} RSStandard {..}) ctxApiRequest@ApiRequest{..} _ AppConfig{..} _ = do
let
(status, contentRange) =
RangeQuery.rangeStatusHeader iTopLevelRange rsQueryTotal rsTableTotal
rsOrErrBody = if status == HTTP.status416
then Error.errorPayload $ Error.ApiRequestError $ Error.InvalidRange
then Error.errorPayload configClientErrorVerbosity $ Error.ApiRequestErr $ Error.InvalidRange
$ Error.OutOfBounds (show $ RangeQuery.rangeOffset iTopLevelRange) (maybe "0" show rsTableTotal)
else LBS.fromStrict rsBody
isHeadMethod = invMethod == InvRead True
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing Nothing preferCount preferTransaction Nothing preferHandling preferTimezone preferMaxAffected []
cLHeader = if isHeadMethod then mempty else [contentLengthHeaderLazy rsOrErrBody]
prefHeader = maybeToList . prefAppliedHeader $ responsePreferences plan ctxApiRequest
cLHeader = if isHeadMethod then mempty else [contentLengthHeader rsOrErrBody]
headers = contentRange : prefHeader
(status', headers', body) =
if Routine.funcReturnsVoid proc then
@@ -199,20 +202,21 @@ actionResponse (DbCrudResult CallReadPlan{pMedia, crInvMthd=invMethod, crProc=pr
Right $ PgrstResponse ovStatus ovHeaders body
actionResponse (DbPlanResult media plan) ctxApiRequest _ _ _ _ _ =
Right $ PgrstResponse HTTP.status200 (contentLengthHeaderStrict plan : contentTypeHeaders media ctxApiRequest) $ LBS.fromStrict plan
actionResponse (DbPlanResult media plan) ctxApiRequest _ _ _ =
let body = LBS.fromStrict plan in
Right $ PgrstResponse HTTP.status200 (contentLengthHeader body : contentTypeHeaders media ctxApiRequest) body
actionResponse (MaybeDbResult InspectPlan{ipHdrsOnly=headersOnly} body) _ versions conf sCache schema negotiatedByProfile =
actionResponse (MaybeDbResult InspectPlan{ipHdrsOnly=headersOnly} body) ApiRequest{..} versions conf sCache =
let
rsBody = maybe mempty (\(x, y, z) -> if headersOnly then mempty else OpenAPI.encode versions conf sCache x y z) body
cLHeader = if headersOnly then mempty else [contentLengthHeaderLazy rsBody]
cLHeader = if headersOnly then mempty else [contentLengthHeader rsBody]
in
Right $ PgrstResponse HTTP.status200 (MediaType.toContentType MTOpenAPI : cLHeader ++ maybeToList (profileHeader schema negotiatedByProfile)) rsBody
Right $ PgrstResponse HTTP.status200 (MediaType.toContentType MTOpenAPI : cLHeader ++ maybeToList (profileHeader iSchema iNegotiatedByProfile)) rsBody
actionResponse (NoDbResult (RelInfoPlan qi@QualifiedIdentifier{..})) _ _ _ SchemaCache{dbTables} _ _ =
actionResponse (NoDbResult (RelInfoPlan qi@QualifiedIdentifier{..})) _ _ _ sc@SchemaCache{dbTables} =
case HM.lookup qi dbTables of
Just tbl -> respondInfo $ allowH tbl
Nothing -> Left $ Error.SchemaCacheErr $ Error.TableNotFound qiSchema qiName (HM.elems dbTables)
Nothing -> Left $ Error.SchemaCacheErr $ Error.TableNotFound qiSchema qiName sc
where
allowH table =
let hasPK = not . null $ tablePKCols table in
@@ -223,16 +227,16 @@ actionResponse (NoDbResult (RelInfoPlan qi@QualifiedIdentifier{..})) _ _ _ Schem
["PATCH" | tableUpdatable table] ++
["DELETE" | tableDeletable table]
actionResponse (NoDbResult (RoutineInfoPlan proc)) _ _ _ _ _ _
actionResponse (NoDbResult (RoutineInfoPlan proc)) _ _ _ _
| pdVolatility proc == Volatile = respondInfo "OPTIONS,POST"
| otherwise = respondInfo "OPTIONS,GET,HEAD,POST"
actionResponse (NoDbResult SchemaInfoPlan) _ _ _ _ _ _ = respondInfo "OPTIONS,GET,HEAD"
actionResponse (NoDbResult SchemaInfoPlan) _ _ _ _ = respondInfo "OPTIONS,GET,HEAD"
respondInfo :: ByteString -> Either Error.Error PgrstResponse
respondInfo allowHeader =
let allOrigins = ("Access-Control-Allow-Origin", "*") in
Right $ PgrstResponse HTTP.status200 [contentLengthHeaderStrict mempty, allOrigins, (HTTP.hAllow, allowHeader)] mempty
Right $ PgrstResponse HTTP.status200 [contentLengthHeader mempty, allOrigins, (HTTP.hAllow, allowHeader)] mempty
-- Status and headers can be overridden as per https://postgrest.org/en/stable/references/transactions.html#response-headers
overrideStatusHeaders :: Maybe Text -> Maybe BS.ByteString -> HTTP.Status -> [HTTP.Header]-> Either Error.Error (HTTP.Status, [HTTP.Header])
@@ -243,20 +247,14 @@ overrideStatusHeaders rsGucStatus rsGucHeaders pgrstStatus pgrstHeaders = do
decodeGucHeaders :: Maybe BS.ByteString -> Either Error.Error [GucHeader]
decodeGucHeaders =
maybe (Right []) $ first (const . Error.ApiRequestError $ Error.GucHeadersError) . JSON.eitherDecode . LBS.fromStrict
maybe (Right []) $ first (const . Error.ApiRequestErr $ Error.GucHeadersError) . JSON.eitherDecode . LBS.fromStrict
decodeGucStatus :: Maybe Text -> Either Error.Error (Maybe HTTP.Status)
decodeGucStatus =
maybe (Right Nothing) $ first (const . Error.ApiRequestError $ Error.GucStatusError) . fmap (Just . toEnum . fst) . decimal
maybe (Right Nothing) $ first (const . Error.ApiRequestErr $ Error.GucStatusError) . fmap (Just . toEnum . fst) . decimal
contentLengthHeader :: Show b => (a -> b) -> a -> HTTP.Header
contentLengthHeader lenFn body = ("Content-Length", show (lenFn body))
contentLengthHeaderStrict :: BS.ByteString -> HTTP.Header
contentLengthHeaderStrict = contentLengthHeader BS.length
contentLengthHeaderLazy :: LBS.ByteString -> HTTP.Header
contentLengthHeaderLazy = contentLengthHeader LBS.length
contentLengthHeader :: LBS.ByteString -> HTTP.Header
contentLengthHeader body = ("Content-Length", show (LBS.length body))
contentTypeHeaders :: MediaType -> ApiRequest -> [HTTP.Header]
contentTypeHeaders mediaType ApiRequest{..} =
@@ -274,3 +272,31 @@ addHeadersIfNotIncluded :: [HTTP.Header] -> [HTTP.Header] -> [HTTP.Header]
addHeadersIfNotIncluded newHeaders initialHeaders =
filter (\(nk, _) -> isNothing $ find (\(ik, _) -> ik == nk) initialHeaders) newHeaders ++
initialHeaders
-- | Get Preferences for Preference-Applied header per plan
responsePreferences :: CrudPlan -> ApiRequest -> Preferences
responsePreferences plan ApiRequest{iPreferences=Preferences{..}, iQueryParams=QueryParams{..}} =
let
-- Only returned on Inserts
preferResolution' = case plan of
MutateReadPlan{mrMutation=MutationCreate, mrMutatePlan} ->
let pkCols = case mrMutatePlan of { Insert{insPkCols} -> insPkCols ; _ -> mempty; }
in (if null pkCols && isNothing qsOnConflict then Nothing else preferResolution)
_ -> Nothing
preferRepresentation' = case plan of
MutateReadPlan{} -> preferRepresentation
_ -> Nothing
preferMissing' = case plan of
MutateReadPlan{mrMutation=MutationCreate} -> preferMissing
MutateReadPlan{mrMutation=MutationUpdate} -> preferMissing
_ -> Nothing
preferMaxAffected' = case plan of
MutateReadPlan{mrMutation=MutationUpdate} -> preferMaxAffected
MutateReadPlan{mrMutation=MutationDelete} -> preferMaxAffected
CallReadPlan{} -> preferMaxAffected
_ -> Nothing
in Preferences preferResolution' preferRepresentation' preferCount preferTransaction preferMissing' preferHandling preferTimezone preferMaxAffected' []

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