Compare commits

...
9 Commits
Author SHA1 Message Date
steve-chavez db9da0b1ca bump version to 12.2.2 2024-07-10 23:07:23 -05:00
steve-chavez f09655b7a6 fix: schema cache retrying without backoff
Fixes https://github.com/PostgREST/postgrest/issues/3523.

Now if there's a failure when obtaining the pg version OR schema cache,
we do the same retrying process. This way we don't add two retries.

Refactors and renames the "connectionWorker" to "schemaCacheLoader".
This makes more sense since what we really want is the schema cache,
the version is the pre-requisite for ensuring our
schema cache queries work.

Additionally, we no longer log ` Attempting to connect to the database...`
at startup unnecessarily. This is only logged whenever there's a retry attempt.
2024-07-10 21:11:20 -05:00
steve-chavezandWolfgang Walther 8715e426c0 chore: remove paypal links
It was tied to a personal account and donations there have been too rare.
2024-07-10 21:13:23 +02:00
Wolfgang WaltherandWolfgang Walther 9a40bc266e fix: List correct enum options when multiple types with same name are present
The schema cache and OpenAPI output would currently list the first found
enum with the same name instead of the correct type. One other case
where this comes up is when a regular type and an enum type have the
same name. For example in the spec fixtures, we have an enum called
"bit". Every "bit" type, no matter whether it's that enum or the
built-in bit type, will show those enum options in the OpenApi output.

Not adding a test, because OpenAPI is supposed to go away in the future
anyway.
2024-07-09 09:09:22 +02:00
Wolfgang WaltherandWolfgang Walther e67461b991 fix: Show number of loaded timezones in log output
There is no reason to hide those, right?
2024-07-09 09:09:22 +02:00
Wolfgang WaltherandWolfgang Walther 420933ceba fix: Make --dump-schema work with in-database pgrst.db_schemas setting
This needs to be loaded from in-database configuration first, otherwise
the dump-schema output will be for the default (public) schema.
2024-07-09 09:09:22 +02:00
Laurence IslaandWolfgang Walther 8f8dbd3425 fix: nested empty embeds no longer return empty values and are correctly omitted 2024-07-09 09:09:22 +02:00
renovate[bot]andWolfgang Walther 8d7d7e4112 chore(deps): update all dependencies 2024-07-06 11:35:11 +02:00
Sandro BauerandWolfgang Walther 521acf61cb docs: fix rendering for inline code block in operator list 2024-07-02 12:28:24 +02:00
20 changed files with 171 additions and 214 deletions
@@ -112,7 +112,7 @@ runs:
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT" echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
- name: Save artifact to GitHub Actions - name: Save artifact to GitHub Actions
if: steps.find-task.outputs.task_found if: steps.find-task.outputs.task_found
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: ${{ inputs.upload }} name: ${{ inputs.upload }}
path: ${{ steps.download.outputs.artifacts }} path: ${{ steps.download.outputs.artifacts }}
+5 -5
View File
@@ -42,7 +42,7 @@ jobs:
- name: Build static executable - name: Build static executable
run: nix-build -A postgrestStatic run: nix-build -A postgrestStatic
- name: Save built executable as artifact - name: Save built executable as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: postgrest-linux-static-x64 name: postgrest-linux-static-x64
path: result/bin/postgrest path: result/bin/postgrest
@@ -51,7 +51,7 @@ jobs:
- name: Build Docker image - 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.tar.gz
- name: Save built Docker image as artifact - name: Save built Docker image as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: postgrest-docker-x64 name: postgrest-docker-x64
path: postgrest-docker.tar.gz path: postgrest-docker.tar.gz
@@ -119,7 +119,7 @@ jobs:
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: haskell-actions/setup@33585e1a16afa5875e124b0ebc89dd0c2f872c21 # v2.7.3 - uses: haskell-actions/setup@ec49483bfc012387b227434aba94f59a6ecd0900 # v2.7.5
with: with:
# This must match the version in stack.yaml's resolver # This must match the version in stack.yaml's resolver
ghc-version: 9.6.5 ghc-version: 9.6.5
@@ -148,7 +148,7 @@ jobs:
run: strip result/postgrest* run: strip result/postgrest*
- name: Save built executable as artifact - name: Save built executable as artifact
if: matrix.artifact if: matrix.artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: ${{ matrix.artifact }} name: ${{ matrix.artifact }}
path: | path: |
@@ -179,7 +179,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: haskell-actions/setup@33585e1a16afa5875e124b0ebc89dd0c2f872c21 # v2.7.3 - uses: haskell-actions/setup@ec49483bfc012387b227434aba94f59a6ecd0900 # v2.7.5
with: with:
ghc-version: ${{ matrix.ghc }} ghc-version: ${{ matrix.ghc }}
- name: Cache .cabal - name: Cache .cabal
+5 -5
View File
@@ -99,7 +99,7 @@ jobs:
- name: Extract downloaded binaries - name: Extract downloaded binaries
run: tar -xvf result.tar.xz && rm result.tar.xz run: tar -xvf result.tar.xz && rm result.tar.xz
- name: Save aarch64 executable as artifact - name: Save aarch64 executable as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: postgrest-ubuntu-aarch64 name: postgrest-ubuntu-aarch64
path: result/postgrest path: result/postgrest
@@ -183,7 +183,7 @@ jobs:
echo "Relevant extract from CHANGELOG.md:" echo "Relevant extract from CHANGELOG.md:"
cat CHANGES.md cat CHANGES.md
- name: Save CHANGES.md as artifact - name: Save CHANGES.md as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: release-changes name: release-changes
path: CHANGES.md path: CHANGES.md
@@ -201,7 +201,7 @@ jobs:
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Download all artifacts - name: Download all artifacts
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
path: artifacts path: artifacts
- name: Create release bundle with archives for all builds - name: Create release bundle with archives for all builds
@@ -226,7 +226,7 @@ jobs:
artifacts/postgrest-windows-x64/postgrest.exe artifacts/postgrest-windows-x64/postgrest.exe
- name: Save release bundle - name: Save release bundle
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: release-bundle name: release-bundle
path: release-bundle path: release-bundle
@@ -275,7 +275,7 @@ jobs:
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Download Docker image - name: Download Docker image
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: postgrest-docker-x64 name: postgrest-docker-x64
- name: Publish images on Docker Hub - name: Publish images on Docker Hub
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure'
steps: steps:
- name: Download from Artifacts - name: Download from Artifacts
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }} run-id: ${{ github.event.workflow_run.id }}
+1 -1
View File
@@ -131,7 +131,7 @@ jobs:
postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }} postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }}
postgrest-loadtest-report > loadtest/loadtest.md postgrest-loadtest-report > loadtest/loadtest.md
- name: Upload report - name: Upload report
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: loadtest.md name: loadtest.md
path: loadtest/loadtest.md path: loadtest/loadtest.md
+10
View File
@@ -5,6 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
## [12.2.2] - 2024-07-10
### Fixed
- #3093, Nested empty embeds no longer show empty values and are correctly omitted - @laurenceisla
- #3644, Make --dump-schema work with in-database pgrst.db_schemas setting - @wolfgangwalther
- #3644, Show number of timezones in schema cache load report - @wolfgangwalther
- #3644, List correct enum options in OpenApi output when multiple types with same name are present - @wolfgangwalther
- #3523, Fix schema cache loading retry without backoff - @steve-chavez
## [12.2.1] - 2024-06-27 ## [12.2.1] - 2024-06-27
### Fixed ### Fixed
+1 -6
View File
@@ -1,7 +1,6 @@
![Logo](static/postgrest.png "Logo") ![Logo](static/postgrest.png "Logo")
[![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest) [![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/postgrest)
[![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/postgrest) [![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/postgrest)
[![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org) [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org)
[![Docker Stars](https://img.shields.io/docker/pulls/postgrest/postgrest.svg)](https://hub.docker.com/r/postgrest/postgrest/) [![Docker Stars](https://img.shields.io/docker/pulls/postgrest/postgrest.svg)](https://hub.docker.com/r/postgrest/postgrest/)
@@ -145,11 +144,7 @@ and the [API guide](http://postgrest.org/en/stable/api.html).
## Supporting development ## Supporting development
You can help PostgREST ongoing maintenance and development by: You can help PostgREST ongoing maintenance and development by making a regular donation through Patreon https://www.patreon.com/postgrest
- Making a regular donation through Patreon https://www.patreon.com/postgrest
- Alternatively, you can make a one-time donation via Paypal https://www.paypal.me/postgrest
Every donation will be spent on making PostgREST better for the whole community. Every donation will be spent on making PostgREST better for the whole community.
-1
View File
@@ -2,7 +2,6 @@
[![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/postgrest) [![Join the chat at https://gitter.im/begriffs/postgrest](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/begriffs/postgrest)
[![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest) [![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/postgrest)
[![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org) [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://postgrest.org)
[![Build Status](https://github.com/postgrest/postgrest/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain) [![Build Status](https://github.com/postgrest/postgrest/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain)
-3
View File
@@ -22,9 +22,6 @@ PostgREST Documentation
.. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854 .. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854
:target: https://www.patreon.com/postgrest :target: https://www.patreon.com/postgrest
.. image:: https://img.shields.io/badge/Donate-PayPal-green.svg
:target: https://www.paypal.com/paypalme/postgrest
| |
PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations. PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations.
+1 -1
View File
@@ -82,7 +82,7 @@ cs :code:`@>` contains e.g. :code:`?tags=cs.{example,
cd :code:`<@` contained in e.g. :code:`?values=cd.{1,2,3}` cd :code:`<@` contained in e.g. :code:`?values=cd.{1,2,3}`
ov :code:`&&` overlap (have points in common), e.g. :code:`?period=ov.[2017-01-01,2017-06-30]` ov :code:`&&` overlap (have points in common), e.g. :code:`?period=ov.[2017-01-01,2017-06-30]`
also supports array types, use curly braces instead of square brackets e.g. also supports array types, use curly braces instead of square brackets e.g.
:code: `?arr=ov.{1,3}` :code:`?arr=ov.{1,3}`
sl :code:`<<` strictly left of, e.g. :code:`?range=sl.(1,10)` sl :code:`<<` strictly left of, e.g. :code:`?range=sl.(1,10)`
sr :code:`>>` strictly right of sr :code:`>>` strictly right of
nxr :code:`&<` does not extend to the right of, e.g. :code:`?range=nxr.(1,10)` nxr :code:`&<` does not extend to the right of, e.g. :code:`?range=nxr.(1,10)`
+1 -1
View File
@@ -1,5 +1,5 @@
name: postgrest name: postgrest
version: 12.2.1 version: 12.2.2
synopsis: REST API for any Postgres database synopsis: REST API for any Postgres database
description: Reads the schema of a PostgreSQL database and creates RESTful routes description: Reads the schema of a PostgreSQL database and creates RESTful routes
for tables, views, and functions, supporting all HTTP methods that security for tables, views, and functions, supporting all HTTP methods that security
+3 -3
View File
@@ -68,14 +68,14 @@ run appState = do
observer $ AppStartObs prettyVersion observer $ AppStartObs prettyVersion
AppState.connectionWorker appState AppState.schemaCacheLoader appState -- Loads the initial SchemaCache
Unix.installSignalHandlers (AppState.getMainThreadId appState) (AppState.connectionWorker appState) (AppState.reReadConfig False appState) Unix.installSignalHandlers (AppState.getMainThreadId appState) (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState)
Listener.runListener appState Listener.runListener appState
Admin.runAdmin appState (serverSettings conf) Admin.runAdmin appState (serverSettings conf)
let app = postgrest configLogLevel appState (AppState.connectionWorker appState) let app = postgrest configLogLevel appState (AppState.schemaCacheLoader appState)
case configServerUnixSocket of case configServerUnixSocket of
Just path -> do Just path -> do
+104 -164
View File
@@ -24,8 +24,8 @@ module PostgREST.AppState
, putPgVersion , putPgVersion
, putIsListenerOn , putIsListenerOn
, usePool , usePool
, reReadConfig , readInDbConfig
, connectionWorker , schemaCacheLoader
, getObserver , getObserver
, isLoaded , isLoaded
, isPending , isPending
@@ -85,39 +85,37 @@ data AuthResult = AuthResult
data AppState = AppState data AppState = AppState
-- | Database connection pool -- | Database connection pool
{ statePool :: SQL.Pool { statePool :: SQL.Pool
-- | Database server version, will be updated by the connectionWorker -- | Database server version
, statePgVersion :: IORef PgVersion , statePgVersion :: IORef PgVersion
-- | No schema cache at the start. Will be filled in by the connectionWorker -- | Schema cache
, stateSchemaCache :: IORef (Maybe SchemaCache) , stateSchemaCache :: IORef (Maybe SchemaCache)
-- | The schema cache status -- | The schema cache status
, stateSCacheStatus :: IORef SchemaCacheStatus , stateSCacheStatus :: IORef SchemaCacheStatus
-- | The connection status
, stateConnStatus :: IORef ConnectionStatus
-- | State of the LISTEN channel -- | State of the LISTEN channel
, stateIsListenerOn :: IORef Bool , stateIsListenerOn :: IORef Bool
-- | starts the connection worker with a debounce -- | starts the connection worker with a debounce
, debouncedConnectionWorker :: IO () , debouncedSCacheLoader :: IO ()
-- | Config that can change at runtime -- | Config that can change at runtime
, stateConf :: IORef AppConfig , stateConf :: IORef AppConfig
-- | Time used for verifying JWT expiration -- | Time used for verifying JWT expiration
, stateGetTime :: IO UTCTime , stateGetTime :: IO UTCTime
-- | Used for killing the main thread in case a subthread fails -- | Used for killing the main thread in case a subthread fails
, stateMainThreadId :: ThreadId , stateMainThreadId :: ThreadId
-- | Keeps track of the next delay for db connection retry -- | Keeps track of the next delay for db connection retry
, stateNextDelay :: IORef Int , stateNextDelay :: IORef Int
-- | Keeps track of the next delay for the listener -- | Keeps track of the next delay for the listener
, stateNextListenerDelay :: IORef Int , stateNextListenerDelay :: IORef Int
-- | JWT Cache -- | JWT Cache
, jwtCache :: C.Cache ByteString AuthResult , jwtCache :: C.Cache ByteString AuthResult
-- | Network socket for REST API -- | Network socket for REST API
, stateSocketREST :: NS.Socket , stateSocketREST :: NS.Socket
-- | Network socket for the admin UI -- | Network socket for the admin UI
, stateSocketAdmin :: Maybe NS.Socket , stateSocketAdmin :: Maybe NS.Socket
-- | Observation handler -- | Observation handler
, stateObserver :: ObservationHandler , stateObserver :: ObservationHandler
, stateLogger :: Logger.LoggerState , stateLogger :: Logger.LoggerState
, stateMetrics :: Metrics.MetricsState , stateMetrics :: Metrics.MetricsState
} }
-- | Schema cache status -- | Schema cache status
@@ -126,15 +124,8 @@ data SchemaCacheStatus
| SCPending | SCPending
deriving Eq deriving Eq
-- | Current database connection status
data ConnectionStatus
= ConnEstablished
| ConnPending
deriving Eq
type AppSockets = (NS.Socket, Maybe NS.Socket) type AppSockets = (NS.Socket, Maybe NS.Socket)
init :: AppConfig -> IO AppState init :: AppConfig -> IO AppState
init conf@AppConfig{configLogLevel, configDbPoolSize} = do init conf@AppConfig{configLogLevel, configDbPoolSize} = do
loggerState <- Logger.init loggerState <- Logger.init
@@ -153,7 +144,6 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step <$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
<*> newIORef Nothing <*> newIORef Nothing
<*> newIORef SCPending <*> newIORef SCPending
<*> newIORef ConnPending
<*> newIORef False <*> newIORef False
<*> pure (pure ()) <*> pure (pure ())
<*> newIORef conf <*> newIORef conf
@@ -168,15 +158,15 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
<*> pure loggerState <*> pure loggerState
<*> pure metricsState <*> pure metricsState
debWorker <- deb <-
let decisecond = 100000 in let decisecond = 100000 in
mkDebounce defaultDebounceSettings mkDebounce defaultDebounceSettings
{ debounceAction = internalConnectionWorker appState { debounceAction = internalSchemaCacheLoad appState
, debounceFreq = decisecond , debounceFreq = decisecond
, debounceEdge = leadingEdge -- runs the worker at the start and the end , debounceEdge = leadingEdge -- runs the worker at the start and the end
} }
return appState { debouncedConnectionWorker = debWorker} return appState { debouncedSCacheLoader = deb}
destroy :: AppState -> IO () destroy :: AppState -> IO ()
destroy = destroyPool destroy = destroyPool
@@ -302,15 +292,12 @@ getSchemaCache = readIORef . stateSchemaCache
putSchemaCache :: AppState -> Maybe SchemaCache -> IO () putSchemaCache :: AppState -> Maybe SchemaCache -> IO ()
putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState) putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState)
connectionWorker :: AppState -> IO () schemaCacheLoader :: AppState -> IO ()
connectionWorker = debouncedConnectionWorker schemaCacheLoader = debouncedSCacheLoader
getNextDelay :: AppState -> IO Int getNextDelay :: AppState -> IO Int
getNextDelay = readIORef . stateNextDelay getNextDelay = readIORef . stateNextDelay
putNextDelay :: AppState -> Int -> IO ()
putNextDelay = atomicWriteIORef . stateNextDelay
getNextListenerDelay :: AppState -> IO Int getNextListenerDelay :: AppState -> IO Int
getNextListenerDelay = readIORef . stateNextListenerDelay getNextListenerDelay = readIORef . stateNextListenerDelay
@@ -338,167 +325,120 @@ getSocketAdmin = stateSocketAdmin
getMainThreadId :: AppState -> ThreadId getMainThreadId :: AppState -> ThreadId
getMainThreadId = stateMainThreadId getMainThreadId = stateMainThreadId
getIsListenerOn :: AppState -> IO Bool isConnEstablished :: AppState -> IO Bool
getIsListenerOn appState = do isConnEstablished appState = do
AppConfig{..} <- getConfig appState AppConfig{..} <- getConfig appState
if configDbChannelEnabled then if configDbChannelEnabled then -- if the listener is enabled, we can be sure the connection is up
readIORef $ stateIsListenerOn appState readIORef $ stateIsListenerOn appState
else else -- otherwise the only way to check the connection is to make a query
pure True isRight <$> usePool appState (SQL.sql "SELECT 1")
putIsListenerOn :: AppState -> Bool -> IO () putIsListenerOn :: AppState -> Bool -> IO ()
putIsListenerOn = atomicWriteIORef . stateIsListenerOn putIsListenerOn = atomicWriteIORef . stateIsListenerOn
isConnEstablished :: AppState -> IO Bool
isConnEstablished x = do
conf <- getConfig x
if configDbChannelEnabled conf
then do -- if the listener is enabled, we can be sure the connection status is always up to date
st <- readIORef $ stateConnStatus x
return $ st == ConnEstablished
else -- otherwise the only way to check the connection is to make a query
isRight <$> usePool x (SQL.sql "SELECT 1")
isLoaded :: AppState -> IO Bool isLoaded :: AppState -> IO Bool
isLoaded x = do isLoaded x = do
scacheStatus <- readIORef $ stateSCacheStatus x scacheStatus <- readIORef $ stateSCacheStatus x
connEstablished <- isConnEstablished x connEstablished <- isConnEstablished x
listenerOn <- getIsListenerOn x return $ scacheStatus == SCLoaded && connEstablished
return $ scacheStatus == SCLoaded && connEstablished && listenerOn
isPending :: AppState -> IO Bool isPending :: AppState -> IO Bool
isPending x = do isPending x = do
scacheStatus <- readIORef $ stateSCacheStatus x scacheStatus <- readIORef $ stateSCacheStatus x
connStatus <- readIORef $ stateConnStatus x connEstablished <- isConnEstablished x
listenerOn <- getIsListenerOn x return $ scacheStatus == SCPending || not connEstablished
return $ scacheStatus == SCPending || connStatus == ConnPending || not listenerOn
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO () putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
putSCacheStatus = atomicWriteIORef . stateSCacheStatus putSCacheStatus = atomicWriteIORef . stateSCacheStatus
putConnStatus :: AppState -> ConnectionStatus -> IO ()
putConnStatus = atomicWriteIORef . stateConnStatus
getObserver :: AppState -> ObservationHandler getObserver :: AppState -> ObservationHandler
getObserver = stateObserver getObserver = stateObserver
-- | Load the SchemaCache by using a connection from the pool. internalSchemaCacheLoad :: AppState -> IO ()
loadSchemaCache :: AppState -> IO SchemaCacheStatus internalSchemaCacheLoad appState = do
loadSchemaCache appState@AppState{stateObserver=observer} = do AppConfig{..} <- getConfig appState
conf@AppConfig{..} <- getConfig appState void $ retryingSchemaCacheLoad appState
(resultTime, result) <- -- We cannot retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue.
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in when configDbConfig $ readInDbConfig False appState
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
case result of
Left e -> do
putSCacheStatus appState SCPending
putSchemaCache appState Nothing
observer $ SchemaCacheErrorObs e
return SCPending
Right sCache -> do -- | Try to load the schema cache and retry if it fails.
-- 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
putSchemaCache appState $ Just sCache
observer $ SchemaCacheQueriedObs resultTime
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
observer $ SchemaCacheLoadedObs t
putSCacheStatus appState SCLoaded
return SCLoaded
-- | The purpose of this worker is to obtain a healthy connection to pg and an
-- up-to-date schema cache(SchemaCache). This method is meant to be called
-- multiple times by the same thread, but does nothing if the previous
-- invocation has not terminated. In all cases this method does not halt the
-- calling thread, the work is performed in a separate thread.
-- --
-- Background thread that does the following : -- This is done by repeatedly: 1) flushing the pool, 2) querying the version and validating that the postgres version is supported by us, and 3) loading the schema cache.
-- 1. Tries to connect to pg server and will keep trying until success. -- It's necessary to flush the pool:
-- 2. Checks if the pg version is supported and if it's not it kills the main --
-- program. -- + Because connections cache the pg catalog(see #2620)
-- 3. Obtains the sCache. If this fails, it goes back to 1. -- + For rapid recovery. Otherwise, the pool idle or lifetime timeout would have to be reached for new healthy connections to be acquired.
internalConnectionWorker :: AppState -> IO () retryingSchemaCacheLoad :: AppState -> IO (Maybe PgVersion, Maybe SchemaCache)
internalConnectionWorker appState@AppState{stateObserver=observer, stateMainThreadId=mainThreadId} = work retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThreadId=mainThreadId} =
retrying retryPolicy shouldRetry (\RetryStatus{rsIterNumber, rsPreviousDelay} -> do
when (rsIterNumber > 0) $ do
let delay = fromMaybe 0 rsPreviousDelay `div` oneSecondInUs
observer $ ConnectionRetryObs delay
putNextListenerDelay appState delay
flushPool appState
(,) <$> qPgVersion <*> qSchemaCache
)
where where
work = do qPgVersion :: IO (Maybe PgVersion)
qPgVersion = do
AppConfig{..} <- getConfig appState AppConfig{..} <- getConfig appState
observer DBConnectAttemptObs
connStatus <- establishConnection appState
case connStatus of
ConnPending ->
unless configDbPoolAutomaticRecovery $ do
observer ExitDBNoRecoveryObs
killThread mainThreadId
ConnEstablished -> do
actualPgVersion <- getPgVersion appState
when (actualPgVersion < minimumPgVersion) $ do
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
killThread mainThreadId
observer (DBConnectedObs $ pgvFullName actualPgVersion)
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
when configDbConfig $ reReadConfig False appState
scStatus <- loadSchemaCache appState
case scStatus of
SCLoaded ->
-- do nothing and proceed if the load was successful
return ()
SCPending ->
-- retry reloading the schema cache
work
-- | Repeatedly flush the pool, and check if a connection from the
-- pool allows access to the PostgreSQL database.
--
-- Releasing the pool is key for rapid recovery. Otherwise, the pool
-- timeout would have to be reached for new healthy connections to be acquired.
-- Which might not happen if the server is busy with requests. No idle
-- connection, no pool timeout.
--
-- It's also necessary to release the pool connections because they cache the pg catalog(see #2620)
--
-- The connection tries are capped, but if the connection times out no error is
-- thrown, just 'False' is returned.
establishConnection :: AppState -> IO ConnectionStatus
establishConnection appState@AppState{stateObserver=observer} =
retrying retryPolicy shouldRetry $
const $ flushPool appState >> getConnectionStatus
where
getConnectionStatus :: IO ConnectionStatus
getConnectionStatus = do
pgVersion <- usePool appState (queryPgVersion False) -- No need to prepare the query here, as the connection might not be established pgVersion <- usePool appState (queryPgVersion False) -- No need to prepare the query here, as the connection might not be established
case pgVersion of case pgVersion of
Left e -> do Left e -> do
observer $ ConnectionPgVersionErrorObs e observer $ QueryPgVersionError e
putConnStatus appState ConnPending unless configDbPoolAutomaticRecovery $ do
return ConnPending observer ExitDBNoRecoveryObs
Right version -> do killThread mainThreadId
putConnStatus appState ConnEstablished return Nothing
putPgVersion appState version Right actualPgVersion -> do
return ConnEstablished when (actualPgVersion < minimumPgVersion) $ do
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
killThread mainThreadId
observer $ DBConnectedObs $ pgvFullName actualPgVersion
putPgVersion appState actualPgVersion
return $ Just actualPgVersion
shouldRetry :: RetryStatus -> ConnectionStatus -> IO Bool qSchemaCache :: IO (Maybe SchemaCache)
shouldRetry rs isConnSucc = do 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)
case result of
Left e -> do
putSCacheStatus appState SCPending
putSchemaCache appState Nothing
observer $ SchemaCacheErrorObs 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
putSchemaCache appState $ Just sCache
observer $ SchemaCacheQueriedObs resultTime
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
observer $ SchemaCacheLoadedObs t
putSCacheStatus appState SCLoaded
return $ Just sCache
shouldRetry :: RetryStatus -> (Maybe PgVersion, Maybe SchemaCache) -> IO Bool
shouldRetry _ (pgVer, sCache) = do
AppConfig{..} <- getConfig appState AppConfig{..} <- getConfig appState
let let itShould = configDbPoolAutomaticRecovery && (isNothing pgVer || isNothing sCache)
delay = fromMaybe 0 (rsPreviousDelay rs) `div` oneSecondInUs
itShould = ConnPending == isConnSucc && configDbPoolAutomaticRecovery
when itShould $ observer $ ConnectionRetryObs delay
when itShould $ putNextDelay appState delay
return itShould return itShould
retryPolicy :: RetryPolicy retryPolicy :: RetryPolicy
retryPolicy = retryPolicy =
let let delayMicroseconds = 32*oneSecondInUs {-32 seconds-} in
delayMicroseconds = 32000000 -- 32 seconds
in
capDelay delayMicroseconds $ exponentialBackoff oneSecondInUs capDelay delayMicroseconds $ exponentialBackoff oneSecondInUs
oneSecondInUs = 1000000 -- | One second in microseconds
-- | Re-reads the config plus config options from the db oneSecondInUs = 1000000 -- one second in microseconds
reReadConfig :: Bool -> AppState -> IO ()
reReadConfig startingUp appState@AppState{stateObserver=observer} = do -- | Reads the in-db config and reads the config file again
readInDbConfig :: Bool -> AppState -> IO ()
readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
AppConfig{..} <- getConfig appState AppConfig{..} <- getConfig appState
pgVer <- getPgVersion appState pgVer <- getPgVersion appState
dbSettings <- dbSettings <-
+4 -2
View File
@@ -42,9 +42,11 @@ main CLI{cliCommand, cliPath} = do
AppState.destroy AppState.destroy
(\appState -> case cliCommand of (\appState -> case cliCommand of
CmdDumpConfig -> do CmdDumpConfig -> do
when configDbConfig $ AppState.reReadConfig True appState when configDbConfig $ AppState.readInDbConfig True appState
putStr . Config.toText =<< AppState.getConfig appState putStr . Config.toText =<< AppState.getConfig appState
CmdDumpSchema -> putStrLn =<< dumpSchema appState CmdDumpSchema -> do
when configDbConfig $ AppState.readInDbConfig True appState
putStrLn =<< dumpSchema appState
CmdRun -> App.run appState) CmdRun -> App.run appState)
-- | Dump SchemaCache schema to JSON -- | Dump SchemaCache schema to JSON
+4 -4
View File
@@ -54,8 +54,8 @@ retryingListen appState = do
delay <- AppState.getNextListenerDelay appState delay <- AppState.getNextListenerDelay appState
when (delay > 1) $ do -- if we did a retry when (delay > 1) $ do -- if we did a retry
-- assume we lost notifications, call the connection worker which will also reload the schema cache -- assume we lost notifications, refresh the schema cache
AppState.connectionWorker appState AppState.schemaCacheLoader appState
-- reset the delay -- reset the delay
AppState.putNextListenerDelay appState 1 AppState.putNextListenerDelay appState 1
@@ -74,8 +74,8 @@ retryingListen appState = do
handleNotification channel msg = handleNotification channel msg =
if | BS.null msg -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader if | BS.null msg -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader
| msg == "reload schema" -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader | msg == "reload schema" -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader
| msg == "reload config" -> observer (DBListenerGotConfigMsg channel) >> AppState.reReadConfig False appState | msg == "reload config" -> observer (DBListenerGotConfigMsg channel) >> AppState.readInDbConfig False appState
| otherwise -> pure () -- Do nothing if anything else than an empty message is sent | otherwise -> pure () -- Do nothing if anything else than an empty message is sent
cacheReloader = cacheReloader =
AppState.connectionWorker appState AppState.schemaCacheLoader appState
+4 -7
View File
@@ -29,7 +29,6 @@ data Observation
| AppStartObs ByteString | AppStartObs ByteString
| AppServerPortObs NS.PortNumber | AppServerPortObs NS.PortNumber
| AppServerUnixObs FilePath | AppServerUnixObs FilePath
| DBConnectAttemptObs
| ExitUnsupportedPgVersion PgVersion PgVersion | ExitUnsupportedPgVersion PgVersion PgVersion
| ExitDBNoRecoveryObs | ExitDBNoRecoveryObs
| ExitDBFatalError ObsFatalError SQL.UsageError | ExitDBFatalError ObsFatalError SQL.UsageError
@@ -39,7 +38,6 @@ data Observation
| SchemaCacheSummaryObs Text | SchemaCacheSummaryObs Text
| SchemaCacheLoadedObs Double | SchemaCacheLoadedObs Double
| ConnectionRetryObs Int | ConnectionRetryObs Int
| ConnectionPgVersionErrorObs SQL.UsageError
| DBListenStart Text | DBListenStart Text
| DBListenFail Text (Either SQL.ConnectionError (Either SomeException ())) | DBListenFail Text (Either SQL.ConnectionError (Either SomeException ()))
| DBListenRetry Int | DBListenRetry Int
@@ -50,6 +48,7 @@ data Observation
| ConfigSucceededObs | ConfigSucceededObs
| QueryRoleSettingsErrorObs SQL.UsageError | QueryRoleSettingsErrorObs SQL.UsageError
| QueryErrorCodeHighObs SQL.UsageError | QueryErrorCodeHighObs SQL.UsageError
| QueryPgVersionError SQL.UsageError
| PoolAcqTimeoutObs SQL.UsageError | PoolAcqTimeoutObs SQL.UsageError
| HasqlPoolObs SQL.Observation | HasqlPoolObs SQL.Observation
| PoolRequest | PoolRequest
@@ -69,8 +68,6 @@ observationMessage = \case
"Listening on port " <> show port "Listening on port " <> show port
AppServerUnixObs sock -> AppServerUnixObs sock ->
"Listening on unix socket " <> show sock "Listening on unix socket " <> show sock
DBConnectAttemptObs ->
"Attempting to connect to the database..."
DBConnectedObs ver -> DBConnectedObs ver ->
"Successfully connected to " <> ver "Successfully connected to " <> ver
ExitUnsupportedPgVersion pgVer minPgVer -> ExitUnsupportedPgVersion pgVer minPgVer ->
@@ -78,7 +75,7 @@ observationMessage = \case
ExitDBNoRecoveryObs -> ExitDBNoRecoveryObs ->
"Automatic recovery disabled, exiting." "Automatic recovery disabled, exiting."
ExitDBFatalError ServerAuthError usageErr -> ExitDBFatalError ServerAuthError usageErr ->
jsonMessage usageErr "Failed to establish a connection. " <> jsonMessage usageErr
ExitDBFatalError ServerPgrstBug usageErr -> ExitDBFatalError ServerPgrstBug usageErr ->
"This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues. " <> jsonMessage usageErr "This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues. " <> jsonMessage usageErr
ExitDBFatalError ServerError42P05 usageErr -> ExitDBFatalError ServerError42P05 usageErr ->
@@ -95,8 +92,8 @@ observationMessage = \case
"Schema cache loaded in " <> showMillis resultTime <> " milliseconds" "Schema cache loaded in " <> showMillis resultTime <> " milliseconds"
ConnectionRetryObs delay -> ConnectionRetryObs delay ->
"Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..." "Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
ConnectionPgVersionErrorObs usageErr -> QueryPgVersionError usageErr ->
jsonMessage usageErr "Failed to query the PostgreSQL version. " <> jsonMessage usageErr
DBListenStart channel -> do DBListenStart channel -> do
"Listening for notifications on the " <> show channel <> " channel" "Listening for notifications on the " <> show channel <> " channel"
DBListenFail channel listenErr -> DBListenFail channel listenErr ->
+5 -1
View File
@@ -622,7 +622,11 @@ generateRelSelectField (Node ReadPlan{relToParent=Just rel, select, relName, rel
where where
rsSelName = fromMaybe relName relAlias rsSelName = fromMaybe relName relAlias
rsEmbedMode = if relIsToOne rel then JsonObject else JsonArray rsEmbedMode = if relIsToOne rel then JsonObject else JsonArray
rsEmptyEmbed = null select && null forest rsEmptyEmbed = hasOnlyNullEmbed (null select) forest
hasOnlyNullEmbed = foldr checkIfNullEmbed
checkIfNullEmbed :: ReadPlanTree -> Bool -> Bool
checkIfNullEmbed (Node ReadPlan{select=s} f) isNullEmbed =
isNullEmbed && hasOnlyNullEmbed (null s) f
generateRelSelectField _ = Nothing generateRelSelectField _ = Nothing
generateSpreadSelectFields :: ReadPlan -> [SpreadSelectField] generateSpreadSelectFields :: ReadPlan -> [SpreadSelectField]
+6 -6
View File
@@ -95,13 +95,14 @@ instance JSON.ToJSON SchemaCache where
] ]
showSummary :: SchemaCache -> Text showSummary :: SchemaCache -> Text
showSummary (SchemaCache tbls rels routs reps mediaHdlrs _) = showSummary (SchemaCache tbls rels routs reps mediaHdlrs tzs) =
T.intercalate ", " T.intercalate ", "
[ show (HM.size tbls) <> " Relations" [ show (HM.size tbls) <> " Relations"
, show (HM.size rels) <> " Relationships" , show (HM.size rels) <> " Relationships"
, show (HM.size routs) <> " Functions" , show (HM.size routs) <> " Functions"
, show (HM.size reps) <> " Domain Representations" , show (HM.size reps) <> " Domain Representations"
, show (HM.size mediaHdlrs) <> " Media Type Handlers" , show (HM.size mediaHdlrs) <> " Media Type Handlers"
, show (S.size tzs) <> " Timezones"
] ]
-- | A view foreign key or primary key dependency detected on its source table -- | A view foreign key or primary key dependency detected on its source table
@@ -646,7 +647,7 @@ tablesSqlQuery pgVer =
information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypid(a.*, t.*),
information_schema._pg_truetypmod(a.*, t.*) information_schema._pg_truetypmod(a.*, t.*)
)::integer AS character_maximum_length, )::integer AS character_maximum_length,
COALESCE(bt.typname, t.typname)::name AS udt_name, COALESCE(bt.oid, t.oid) AS base_type,
a.attnum::integer AS position a.attnum::integer AS position
FROM pg_attribute a FROM pg_attribute a
LEFT JOIN pg_description AS d LEFT JOIN pg_description AS d
@@ -690,14 +691,13 @@ tablesSqlQuery pgVer =
FROM columns info FROM columns info
LEFT OUTER JOIN ( LEFT OUTER JOIN (
SELECT SELECT
n.nspname AS s, e.enumtypid,
t.typname AS n,
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS vals array_agg(e.enumlabel ORDER BY e.enumsortorder) AS vals
FROM pg_type t FROM pg_type t
JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_enum e ON t.oid = e.enumtypid
JOIN pg_namespace n ON n.oid = t.typnamespace JOIN pg_namespace n ON n.oid = t.typnamespace
GROUP BY s,n GROUP BY enumtypid
) AS enum_info ON info.udt_name = enum_info.n ) AS enum_info ON info.base_type = enum_info.enumtypid
WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema') WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema')
GROUP BY info.table_schema, info.table_name GROUP BY info.table_schema, info.table_name
), ),
+1 -1
View File
@@ -778,7 +778,7 @@ def test_metrics_include_schema_cache_fails(defaultenv, metapostgrest):
r'pgrst_schema_cache_loads_total{status="FAIL"} (\d+)', response.text r'pgrst_schema_cache_loads_total{status="FAIL"} (\d+)', response.text
).group(1) ).group(1)
) )
assert metrics > 3.0 assert metrics == 1.0
reset_statement_timeout(metapostgrest, role) reset_statement_timeout(metapostgrest, role)
+14 -1
View File
@@ -1218,6 +1218,20 @@ spec actualPgVersion = do
[json|[{"id":1,"name":"Angela Martin"}]|] [json|[{"id":1,"name":"Angela Martin"}]|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
it "works on nested relationships" $ do
get "/users?select=*,users_tasks(tasks(projects()))" `shouldRespondWith`
[json| [{"id":1,"name":"Angela Martin"}, {"id":2,"name":"Michael Scott"}, {"id":3,"name":"Dwight Schrute"}]|]
{ matchHeaders = [matchContentTypeJson] }
get "/users?select=*,users_tasks!inner(tasks!inner(projects()))&users_tasks.tasks.id=eq.3" `shouldRespondWith`
[json| [{"id":1,"name":"Angela Martin"}]|]
{ matchHeaders = [matchContentTypeJson] }
get "/users?select=*,tasks(projects(clients()),users_tasks())" `shouldRespondWith`
[json| [{"id":1,"name":"Angela Martin"}, {"id":2,"name":"Michael Scott"}, {"id":3,"name":"Dwight Schrute"}]|]
{ matchHeaders = [matchContentTypeJson] }
get "/users?select=*,tasks!inner(projects(clients()),users_tasks(),name)&tasks.id=eq.3" `shouldRespondWith`
[json| [{"id":1,"name":"Angela Martin","tasks":[{"name": "Design w10"}]}]|]
{ matchHeaders = [matchContentTypeJson] }
context "empty root select" $ context "empty root select" $
it "gives all columns" $ do it "gives all columns" $ do
get "/projects?select=" `shouldRespondWith` get "/projects?select=" `shouldRespondWith`
@@ -1413,4 +1427,3 @@ spec actualPgVersion = do
get "/infinite_recursion?select=*" `shouldRespondWith` get "/infinite_recursion?select=*" `shouldRespondWith`
[json|{"code":"42P17","message":"infinite recursion detected in rules for relation \"infinite_recursion\"","details":null,"hint":null}|] [json|{"code":"42P17","message":"infinite recursion detected in rules for relation \"infinite_recursion\"","details":null,"hint":null}|]
{ matchStatus = 500 } { matchStatus = 500 }