Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
272e2e7535 | ||
|
|
ea153523d1 | ||
|
|
bf0a1173b5 | ||
|
|
3a28968f3c | ||
|
|
86aac1ead5 |
@@ -11,7 +11,7 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- uses: nixbuild/nix-quick-install-action@63ca48f939ee3b8d835f4126562537df0fee5b91 # v32
|
- uses: nixbuild/nix-quick-install-action@1f095fee853b33114486cfdeae62fa099cda35a9 # v33
|
||||||
with:
|
with:
|
||||||
nix_conf: |-
|
nix_conf: |-
|
||||||
always-allow-substitutes = true
|
always-allow-substitutes = true
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## [13.0.6] - 2025-08-30
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix logging the Haskell type instead of the listener error message directly by @laurenceisla in #3588
|
||||||
|
- Fix format of `IPv6` address logged at PostgREST startup by @taimoorzaeem in #4291
|
||||||
|
- Fix empty enum in `preferParams` OpenAPI parameter by @laurenceisla in #4292
|
||||||
|
|
||||||
## [13.0.5] - 2025-08-24
|
## [13.0.5] - 2025-08-24
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: postgrest
|
name: postgrest
|
||||||
version: 13.0.5
|
version: 13.0.6
|
||||||
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
|
||||||
|
|||||||
@@ -13,5 +13,9 @@ resolveHost sock = do
|
|||||||
sn <- NS.getSocketName sock
|
sn <- NS.getSocketName sock
|
||||||
case sn of
|
case sn of
|
||||||
NS.SockAddrInet _ hostAddr -> pure $ Just $ fromString $ show $ fromHostAddress hostAddr
|
NS.SockAddrInet _ hostAddr -> pure $ Just $ fromString $ show $ fromHostAddress hostAddr
|
||||||
NS.SockAddrInet6 _ _ hostAddr6 _ -> pure $ Just $ fromString $ show $ fromHostAddress6 hostAddr6
|
-- The IPv6 addresses are wrapped in [] brackets. This is done in accordance
|
||||||
|
-- to RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).
|
||||||
|
-- In short, we did this to have a clear separation between the port and host
|
||||||
|
-- because the components of an IPv6 are separated with the ':' character.
|
||||||
|
NS.SockAddrInet6 _ _ hostAddr6 _ -> pure $ Just $ fromString $ "[" ++ show (fromHostAddress6 hostAddr6) ++ "]"
|
||||||
_ -> pure Nothing
|
_ -> pure Nothing
|
||||||
|
|||||||
@@ -108,11 +108,8 @@ observationMessage = \case
|
|||||||
DBListenStart channel -> do
|
DBListenStart channel -> do
|
||||||
"Listening for database notifications on the " <> show channel <> " channel"
|
"Listening for database notifications on the " <> show channel <> " channel"
|
||||||
DBListenFail channel listenErr ->
|
DBListenFail channel listenErr ->
|
||||||
"Failed listening for database notifications on the " <> show channel <> " channel. " <> (
|
"Failed listening for database notifications on the " <> show channel <> " channel. " <>
|
||||||
case listenErr of
|
either showListenerConnError showListenerException listenErr
|
||||||
Left err -> show err
|
|
||||||
Right err -> showListenerError err
|
|
||||||
)
|
|
||||||
DBListenRetry delay ->
|
DBListenRetry delay ->
|
||||||
"Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
|
"Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
|
||||||
DBListenerGotSCacheMsg channel ->
|
DBListenerGotSCacheMsg channel ->
|
||||||
@@ -157,8 +154,11 @@ observationMessage = \case
|
|||||||
|
|
||||||
jsonMessage err = T.decodeUtf8 . LBS.toStrict . Error.errorPayload $ Error.PgError False err
|
jsonMessage err = T.decodeUtf8 . LBS.toStrict . Error.errorPayload $ Error.PgError False err
|
||||||
|
|
||||||
showListenerError :: Either SomeException () -> Text
|
showOnSingleLine txt = T.intercalate " " $ T.filter (/= '\t') <$> T.lines txt -- the errors from hasql-notifications come intercalated with "\t\n"
|
||||||
showListenerError (Right _) = "Failed getting notifications" -- should not happen as the listener will never finish (hasql-notifications uses `forever` internally) with a Right result
|
|
||||||
showListenerError (Left e) =
|
showListenerConnError :: SQL.ConnectionError -> Text
|
||||||
let showOnSingleLine txt = T.intercalate " " $ T.filter (/= '\t') <$> T.lines txt in -- the errors from hasql-notifications come intercalated with "\t\n"
|
showListenerConnError = maybe "Connection error" (showOnSingleLine . T.decodeUtf8)
|
||||||
showOnSingleLine $ show e
|
|
||||||
|
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 $ show e
|
||||||
|
|||||||
@@ -171,8 +171,9 @@ makePreferParam ts =
|
|||||||
& schema .~ ParamOther ((mempty :: ParamOtherSchema)
|
& schema .~ ParamOther ((mempty :: ParamOtherSchema)
|
||||||
& in_ .~ ParamHeader
|
& in_ .~ ParamHeader
|
||||||
& type_ ?~ SwaggerString
|
& type_ ?~ SwaggerString
|
||||||
& enum_ .~ JSON.decode (JSON.encode $ foldl (<>) [] (val <$> ts)))
|
& enum_ .~ if null enu then Nothing else JSON.decode (JSON.encode enu))
|
||||||
where
|
where
|
||||||
|
enu = foldl (<>) [] (val <$> ts)
|
||||||
val :: Text -> [Text]
|
val :: Text -> [Text]
|
||||||
val = \case
|
val = \case
|
||||||
"count" -> ["count=none"]
|
"count" -> ["count=none"]
|
||||||
|
|||||||
+13
-2
@@ -96,7 +96,9 @@ def run(
|
|||||||
if port:
|
if port:
|
||||||
env["PGRST_SERVER_PORT"] = str(port)
|
env["PGRST_SERVER_PORT"] = str(port)
|
||||||
env["PGRST_SERVER_HOST"] = host or "localhost"
|
env["PGRST_SERVER_HOST"] = host or "localhost"
|
||||||
baseurl = f"http://localhost:{port}"
|
# When constructing IPv6 address, host address should be bracketed like [host]
|
||||||
|
apihost = f"[{host}]" if host and is_ipv6(host) else "localhost"
|
||||||
|
baseurl = f"http://{apihost}:{port}"
|
||||||
else:
|
else:
|
||||||
socketfile = pathlib.Path(tmpdir) / "postgrest.sock"
|
socketfile = pathlib.Path(tmpdir) / "postgrest.sock"
|
||||||
env["PGRST_SERVER_UNIX_SOCKET"] = str(socketfile)
|
env["PGRST_SERVER_UNIX_SOCKET"] = str(socketfile)
|
||||||
@@ -104,7 +106,8 @@ def run(
|
|||||||
|
|
||||||
adminport = freeport(port)
|
adminport = freeport(port)
|
||||||
env["PGRST_ADMIN_SERVER_PORT"] = str(adminport)
|
env["PGRST_ADMIN_SERVER_PORT"] = str(adminport)
|
||||||
adminurl = f"http://localhost:{adminport}"
|
adminhost = f"[{host}]" if host and is_ipv6(host) else "localhost"
|
||||||
|
adminurl = f"http://{adminhost}:{adminport}"
|
||||||
|
|
||||||
command = [POSTGREST_BIN]
|
command = [POSTGREST_BIN]
|
||||||
env["HPCTIXFILE"] = hpctixfile()
|
env["HPCTIXFILE"] = hpctixfile()
|
||||||
@@ -218,3 +221,11 @@ def sleep_pool_connection(url, seconds):
|
|||||||
session.get(url + f"/rpc/sleep?seconds={seconds}", timeout=0.1)
|
session.get(url + f"/rpc/sleep?seconds={seconds}", timeout=0.1)
|
||||||
except requests.exceptions.ReadTimeout:
|
except requests.exceptions.ReadTimeout:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def is_ipv6(addr):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET6, addr)
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|||||||
+24
-3
@@ -1355,9 +1355,9 @@ def test_log_postgrest_version(defaultenv):
|
|||||||
assert "Starting PostgREST %s..." % version in output[0]
|
assert "Starting PostgREST %s..." % version in output[0]
|
||||||
|
|
||||||
|
|
||||||
def test_log_postgrest_host_and_port(defaultenv):
|
@pytest.mark.parametrize("host", ["127.0.0.1", "::1"])
|
||||||
|
def test_log_postgrest_host_and_port(host, defaultenv):
|
||||||
"PostgREST should output the host and port it is bound to."
|
"PostgREST should output the host and port it is bound to."
|
||||||
host = "127.0.0.1"
|
|
||||||
port = freeport()
|
port = freeport()
|
||||||
|
|
||||||
with run(
|
with run(
|
||||||
@@ -1365,7 +1365,10 @@ def test_log_postgrest_host_and_port(defaultenv):
|
|||||||
) as postgrest:
|
) as postgrest:
|
||||||
output = postgrest.read_stdout(nlines=10)
|
output = postgrest.read_stdout(nlines=10)
|
||||||
|
|
||||||
assert f"API server listening on {host}:{port}" in output[2] # output-sensitive
|
if is_ipv6(host): # IPv6
|
||||||
|
assert f"API server listening on [{host}]:{port}" in output[2]
|
||||||
|
else: # IPv4
|
||||||
|
assert f"API server listening on {host}:{port}" in output[2]
|
||||||
|
|
||||||
|
|
||||||
def test_succeed_w_role_having_superuser_settings(defaultenv):
|
def test_succeed_w_role_having_superuser_settings(defaultenv):
|
||||||
@@ -1930,3 +1933,21 @@ def test_schema_cache_error_observation(defaultenv):
|
|||||||
"Failed to load the schema cache using db-schemas=public and db-extra-search-path=x"
|
"Failed to load the schema cache using db-schemas=public and db-extra-search-path=x"
|
||||||
in output[7]
|
in output[7]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_listener_connection_errors(defaultenv):
|
||||||
|
"The logs should show the listener connection error message in a single line"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGHOST": "no_host",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env, no_startup_stdout=False, wait_for_readiness=False) as postgrest:
|
||||||
|
output = postgrest.read_stdout(nlines=5)
|
||||||
|
assert any(
|
||||||
|
'Failed listening for database notifications on the "pgrst" channel. could not translate host name "no_host" to address:'
|
||||||
|
in line
|
||||||
|
for line in output
|
||||||
|
)
|
||||||
|
|||||||
@@ -1085,6 +1085,13 @@ spec = describe "OpenAPI" $ do
|
|||||||
immutableGet `shouldNotBe` Nothing
|
immutableGet `shouldNotBe` Nothing
|
||||||
immutablePost `shouldNotBe` Nothing
|
immutablePost `shouldNotBe` Nothing
|
||||||
|
|
||||||
|
it "does not include empty enum in the preferParams parameter" $ do
|
||||||
|
r <- simpleBody <$> get "/"
|
||||||
|
let preferParams = r ^? key "parameters" . key "preferParams" . key "enum"
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
preferParams `shouldBe` Nothing
|
||||||
|
|
||||||
describe "Security" $
|
describe "Security" $
|
||||||
it "does not include security or security definitions by default" $ do
|
it "does not include security or security definitions by default" $ do
|
||||||
r <- simpleBody <$> get "/"
|
r <- simpleBody <$> get "/"
|
||||||
|
|||||||
Reference in New Issue
Block a user