Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ All notable changes to this project will be documented in this file. From versio
- 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
- Add GHC runtime metrics to the metrics endpoint by @mkleczek in #4862
- Add config `url-use-legacy-target-names` to allow using target names in filters when it has an alias by @laurenceisla in #4075
Comment thread
steve-chavez marked this conversation as resolved.

### Fixed

- Shutdown should wait for in flight requests by @mkleczek in #4702
- 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
+ You need to set the `url-use-legacy-target-names` config to `false`.
- Fix connection retrying message in `PGRST000` error by @netqo in #4980
+ Remove redundant "Retrying the connection." from message because it is logged separately
- Fix request failures when `work_mem` is set on a role by @laurenceisla in #4955
Expand All @@ -33,8 +35,13 @@ All notable changes to this project will be documented in this file. From versio
+ 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.

### Deprecated

- Deprecate filtering by the name of an embedded table when it has an alias by @steve-chavez, @laurenceisla in #4075
+ e.g. `?select=alias:table(*)&table.id=eq.1` will not be possible anymore, use `?select=alias:table(*)&alias.id=eq.1` instead.
+ You will see a warning in the logs when this happens.
Comment thread
laurenceisla marked this conversation as resolved.
Comment thread
laurenceisla marked this conversation as resolved.
+ You can disable this behavior now by setting `url-use-legacy-target-names = false`.

## [14.12] - 2026-05-20

Expand Down
27 changes: 27 additions & 0 deletions docs/references/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -969,3 +969,30 @@ server-unix-socket-mode
.. code:: bash

server-unix-socket-mode = "660"

.. _url-use-legacy-target-names:

url-use-legacy-target-names
---------------------------

=============== =================================
**Type** Boolean
**Default** True
**Reloadable** Y
**Environment** PGRST_URL_USE_LEGACY_TARGET_NAMES
**In-Database** pgrst.url_use_legacy_target_names
=============== =================================

When active, it allows using the target name of the relationship in filters even if it has an alias:

.. code:: bash

/table?select=alias:target(*)&target.order=id

This feature will be removed in a future release, so you should start using the ``alias`` in these cases.
You will see a warning in the logs when this happens.

.. code::

28/May/2026:20:33:22 -0500: WARNING: Embedded resource was referenced by relation name even though it has an alias. This is deprecated and will stop working in a future release.
28/May/2026:20:33:22 -0500: Please update the filters that use `target` to `alias` in `GET /table?select=alias:target(*)&target.order=id`
5 changes: 5 additions & 0 deletions src/PostgREST/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache jwtTime authResul
(parseTime, apiReq@ApiRequest{..}) <- withTiming conf $ liftEither . mapLeft Error.ApiRequestErr $ ApiRequest.userApiRequest conf prefs req body
(planTime, plan) <- withTiming conf $ liftEither $ Plan.actionPlan iAction conf apiReq sCache

let warnings = Plan.legacyWarnings plan
shouldShowWarnings = configUrlUseLegacyTargetNames && not (null warnings)
liftIO $ when shouldShowWarnings $
observer $ LegacyTargetNameWarningObs iMethod (iPath <> Wai.rawQueryString req) warnings -- TODO maybe store rawQueryString in ApiRequest for consistency

let mainQ = Query.mainQuery plan conf apiReq authResult configDbPreRequest
tx = MainTx.mainTx mainQ conf authResult apiReq plan sCache
obsQuery s = when configLogQuery $ observer $ QueryObs mainQ s
Expand Down
8 changes: 8 additions & 0 deletions src/PostgREST/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ data AppConfig = AppConfig
, configServerTimingEnabled :: Bool
, configServerUnixSocket :: Maybe FilePath
, configServerUnixSocketMode :: FileMode
, configUrlUseLegacyTargetNames :: Bool
, configAdminServerHost :: Text
, configAdminServerPort :: Maybe Int
, configRoleSettings :: RoleSettings
Expand Down Expand Up @@ -205,6 +206,7 @@ toText conf =
,("server-timing-enabled", T.toLower . show . configServerTimingEnabled)
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
,("server-unix-socket-mode", q . T.pack . showSocketMode)
,("url-use-legacy-target-names", T.toLower . show . configUrlUseLegacyTargetNames)
,("admin-server-host", q . configAdminServerHost)
,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
]
Expand Down Expand Up @@ -320,6 +322,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> (fromMaybe False <$> optBool "server-timing-enabled")
<*> (fmap T.unpack <$> optString "server-unix-socket")
<*> parseSocketFileMode "server-unix-socket-mode"
<*> (fromMaybe True <$> optBool "url-use-legacy-target-names")
<*> (defaultServerHost <$> optWithAlias (optString "admin-server-host")
(optString "server-host"))
<*> parseAdminServerPort "admin-server-port"
Expand Down Expand Up @@ -786,4 +789,9 @@ exampleConfigFile = S.unlines
, "## Unix socket file mode"
, "## When none is provided, 660 is applied by default"
, "# server-unix-socket-mode = \"660\""
, ""
, "## Use legacy target names in relationship filters"
, "## If active, allows using the target name of the relationship in filters even if it has an alias."
, "## Otherwise it only allows the alias in filters"
, "url-use-legacy-target-names = true"
]
1 change: 1 addition & 0 deletions src/PostgREST/Config/Database.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dbSettingsNames =
,"server_cors_allowed_origins"
,"server_trace_header"
,"server_timing_enabled"
,"url_use_legacy_target_names"
]

queryPgVersion :: Session PgVersion
Expand Down
8 changes: 8 additions & 0 deletions src/PostgREST/Logger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ observationLogger loggerState logLevel obs = case obs of
o@(QueryObs _ status) -> do
when (shouldLogResponse logLevel status) $
logWithZTime loggerState $ observationMessages o
o@LegacyTargetNameWarningObs {} ->
when (logLevel >= LogError) $ do
logWithZTime loggerState $ observationMessages o
Comment on lines +88 to +90
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we logging a warning on log level error? That doesn't make sense.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wolfgangwalther I thought you agreed above that this was fine?

I don't see any other way to communicate to an admin that some requests will fail on a next version. My read is that these are critical. Perhaps we should remove the WARNING: prefix?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agreed to emitting a warning. But a warning is emitted on log-level=warn, ofc?

How can this thing be critical, if the request actually succeeds? It really is just a warning, nothing else.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But a warning is emitted on log-level=warn, ofc?

The point is to communicate to every user about the upcoming breaking change, if we cannot make log-level=warn the default (#4904), then this is not effective.

How can this thing be critical, if the request actually succeeds?

Because it will fail on a next postgREST upgrade.

What other options do we have to communicate this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What other options do we have to communicate this?

The release notes are the canonical way to communicate this. You can hint in the release notes that this is deprecated now and if people are unsure whether they depend on it, they can enable log-level=warn to see the relevant warnings.

I don't think we should go at this assuming we can communicate this to users who do not look at the release notes at all. Those are very likely not going to look at their logs either, no matter on which level you log that stuff.

Or, to put differently: I regularly look at the release notes of things when I update. I only look at the logs when something is already broken.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they can enable log-level=warn to see the relevant warnings.

The problem really is that 4xx are also logged on warn, if these 4xx are useless for admins (discussed before), then maybe we should not log them at all? We could move them to the info level and have warn as default. Then we can make deprecations more useful and log these WARNINGs when they happen. WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're going in circles here.

The 4xx are "useless" in the same way as this new warning is: This warning is not actionable for admins. The change needs to happen on the client, not the server.

Imho what we really need to do is:

  • Do not deprecate, do not warn at all.
  • Throw an error immediately for these queries.
  • Still catch the case and provide a good error message to the client. Something that has a clear hint saying that this is not supported anymore and they should change their query by doing X.

This would report the problem in the right place - the client, where the old syntax originates from.

o@PoolRequest ->
when (logLevel >= LogDebug) $ do
logWithZTime loggerState $ observationMessages o
Expand Down Expand Up @@ -190,6 +193,11 @@ observationMessages = \case
let snipts = renderSnippet <$> [mqTxVars, fromMaybe mempty mqPreReq, mqMain, x, y, z, fromMaybe mempty mqExplain]
in
showOnSingleLine '\n' . T.decodeUtf8 <$> filter (/= mempty) snipts
LegacyTargetNameWarningObs requestMethod requestTarget warnings ->
let replacement (relName, alias) = "`" <> relName <> "` to `" <> alias <> "`" in
[ "WARNING: Embedded resource was referenced by relation name even though it has an alias. This is deprecated and will stop working in a future release."
, "Please update the filters that use " <> T.intercalate ", " (replacement <$> warnings) <> " in " <> "`" <> T.decodeUtf8 (requestMethod <> " " <> requestTarget) <> "`"
]
ConfigReadErrorObs usageErr ->
pure $ "Failed to query database settings for the config parameters." <> jsonMessage usageErr
QueryRoleSettingsErrorObs usageErr ->
Expand Down
1 change: 1 addition & 0 deletions src/PostgREST/Observation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ data Observation
| DBListenerGotConfigMsg ByteString
| DBListenerConnectionCleanupFail SomeException
| QueryObs MainQuery Status
| LegacyTargetNameWarningObs ByteString ByteString [(Text, Text)]
| ConfigReadErrorObs SQL.UsageError
| ConfigInvalidObs Text
| ConfigSucceededObs
Expand Down
84 changes: 58 additions & 26 deletions src/PostgREST/Plan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
, InspectPlan(..)
, InfoPlan(..)
, CrudPlan(..)
, legacyWarnings
) where

import qualified Data.HashMap.Strict as HM
Expand Down Expand Up @@ -144,6 +145,24 @@
| RoutineInfoPlan Routine -- info about function
| SchemaInfoPlan -- info about schema cache

legacyWarnings :: ActionPlan -> [(Text, Text)]
legacyWarnings (NoDb _) = []
legacyWarnings (Db dbPlan) =
case dbPlan of
DbCrud _ crudPlan ->
readPlanWarnings $ case crudPlan of
WrappedReadPlan{wrReadPlan} -> wrReadPlan
MutateReadPlan{mrReadPlan} -> mrReadPlan
CallReadPlan{crReadPlan} -> crReadPlan
MayUseDb _ -> []
where
readPlanWarnings :: ReadPlanTree -> [(Text, Text)]
readPlanWarnings (Node ReadPlan{relName, relAlias, relIsLegacyTargetNameMatch} forest) =
[ (relName, alias)
| relIsLegacyTargetNameMatch
, Just alias <- [relAlias]
] <> foldMap readPlanWarnings forest

actionPlan :: Action -> AppConfig -> ApiRequest -> SchemaCache -> Either Error ActionPlan
actionPlan act conf apiReq sCache = case act of
ActDb dbAct -> Db <$> dbActionPlan dbAct conf apiReq sCache
Expand Down Expand Up @@ -354,7 +373,7 @@
-- | Adds filters, order, limits on its respective nodes.
-- | Adds joins conditions obtained from resource embedding.
readPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> Either Error ReadPlanTree
readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows, configDbAggregates} SchemaCache{dbTables, dbRelationships, dbRepresentations} apiRequest =
readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows, configDbAggregates, configUrlUseLegacyTargetNames} SchemaCache{dbTables, dbRelationships, dbRepresentations} apiRequest =
let
-- JSON output format hardcoded for now. In the future we might want to support other output mappings such as CSV.
ctx = ResolverContext dbTables dbRepresentations qi "json"
Expand All @@ -369,18 +388,18 @@
addAliases =<<
expandStars ctx =<<
addRels qiSchema (iAction apiRequest) dbRelationships Nothing =<<
addLogicTrees ctx apiRequest =<<
addRanges apiRequest =<<
addOrders ctx apiRequest =<<
addFilters ctx apiRequest (initReadRequest ctx $ QueryParams.qsSelect $ iQueryParams apiRequest)
addLogicTrees ctx apiRequest configUrlUseLegacyTargetNames =<<
addRanges apiRequest configUrlUseLegacyTargetNames =<<
addOrders ctx apiRequest configUrlUseLegacyTargetNames =<<
addFilters ctx apiRequest configUrlUseLegacyTargetNames (initReadRequest ctx $ QueryParams.qsSelect $ iQueryParams apiRequest)

-- Build the initial read plan tree
initReadRequest :: ResolverContext -> [Tree SelectItem] -> ReadPlanTree
initReadRequest ctx@ResolverContext{qi=QualifiedIdentifier{..}} =
foldr (treeEntry rootDepth) $ Node defReadPlan{from=qi ctx, relName=qiName, depth=rootDepth} []
where
rootDepth = 0
defReadPlan = ReadPlan [] (QualifiedIdentifier mempty mempty) Nothing [] [] allRange mempty Nothing [] Nothing mempty Nothing Nothing Nothing [] rootDepth
defReadPlan = ReadPlan [] (QualifiedIdentifier mempty mempty) Nothing [] [] allRange mempty Nothing [] Nothing mempty Nothing Nothing Nothing [] rootDepth False
treeEntry :: Depth -> Tree SelectItem -> ReadPlanTree -> ReadPlanTree
treeEntry depth (Node si fldForest) (Node q rForest) =
let nxtDepth = succ depth in
Expand Down Expand Up @@ -816,8 +835,8 @@
Nothing -> Left $ SchemaCacheErr $ TableNotFound qiSchema qiName sc
Just _ -> Right qi

addFilters :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
addFilters ctx ApiRequest{..} rReq =
addFilters :: ResolverContext -> ApiRequest -> Bool -> ReadPlanTree -> Either Error ReadPlanTree
addFilters ctx ApiRequest{..} useTargetNames rReq =
foldr addFilterToNode (Right rReq) flts
where
QueryParams.QueryParams{..} = iQueryParams
Expand All @@ -829,15 +848,15 @@

addFilterToNode :: (EmbedPath, Filter) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
addFilterToNode =
updateNode (\flt (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=addFilterToLogicForest (resolveFilter ctx{qi=fromTable} flt) lf} f)
updateNode useTargetNames (\flt (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=addFilterToLogicForest (resolveFilter ctx{qi=fromTable} flt) lf} f)

addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
addOrders ctx ApiRequest{..} rReq = foldr addOrderToNode (Right rReq) qsOrder
addOrders :: ResolverContext -> ApiRequest -> Bool -> ReadPlanTree -> Either Error ReadPlanTree
addOrders ctx ApiRequest{..} useTargetNames rReq = foldr addOrderToNode (Right rReq) qsOrder
where
QueryParams.QueryParams{..} = iQueryParams

addOrderToNode :: (EmbedPath, [OrderTerm]) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
addOrderToNode = updateNode (\o (Node q f) -> Node q{order=resolveOrder ctx <$> o} f)
addOrderToNode = updateNode useTargetNames (\o (Node q f) -> Node q{order=resolveOrder ctx <$> o} f)

resolveOrder :: ResolverContext -> OrderTerm -> CoercibleOrderTerm
resolveOrder _ (OrderRelationTerm a b c d) = CoercibleOrderRelationTerm a b c d
Expand Down Expand Up @@ -887,7 +906,8 @@
-- relToParent = Nothing,
-- relJoinConds = [],
-- relAlias = Nothing, relAggAlias = "clients_projects_1", relHint = Nothing, relJoinType = Nothing, relSpread = Nothing, depth = 1,
-- relSelect = []
-- relSelect = [],
-- relIsLegacyTargetNameMatch = False
-- },
-- subForest = []
-- }
Expand All @@ -913,7 +933,8 @@
-- ],
-- order = [], range_ = fullRange, relName = "clients", relToParent = Nothing, relJoinConds = [], relAlias = Nothing, relAggAlias = "", relHint = Nothing,
-- relJoinType = Nothing, relSpread = Nothing, depth = 0,
-- relSelect = []
-- relSelect = [],
-- relIsLegacyTargetNameMatch = False
-- },
-- subForest = subForst
-- }
Expand Down Expand Up @@ -949,8 +970,8 @@
flt@(CoercibleStmnt _) ->
Right flt

addRanges :: ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
addRanges ApiRequest{..} rReq =
addRanges :: ApiRequest -> Bool -> ReadPlanTree -> Either Error ReadPlanTree
addRanges ApiRequest{..} useTargetNames rReq =
case iAction of
ActDb (ActRelationMut _ _) -> Right rReq
_ -> foldr addRangeToNode (Right rReq) =<< ranges
Expand All @@ -959,10 +980,10 @@
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)
addRangeToNode = updateNode useTargetNames (\r (Node q f) -> Node q{range_=r} f)

addLogicTrees :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
addLogicTrees ctx ApiRequest{..} rReq =
addLogicTrees :: ResolverContext -> ApiRequest -> Bool -> ReadPlanTree -> Either Error ReadPlanTree
addLogicTrees ctx ApiRequest{..} useTargetNames rReq =
foldr addLogicTreeToNode (Right rReq) logic
where
QueryParams.QueryParams{..} = iQueryParams
Expand All @@ -975,7 +996,7 @@
_ -> 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)
addLogicTreeToNode = updateNode useTargetNames (\t (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=resolveLogicTree ctx{qi=fromTable} t:lf} f)

resolveLogicTree :: ResolverContext -> LogicTree -> CoercibleLogicTree
resolveLogicTree ctx (Stmnt flt) = CoercibleStmnt $ resolveFilter ctx flt
Expand All @@ -985,18 +1006,29 @@
resolveFilter ctx (Filter fld opExpr) = CoercibleFilter{field=resolveQueryInputField ctx fld opExpr, opExpr=opExpr}

-- Find a Node of the Tree and apply a function to it
updateNode :: (a -> ReadPlanTree -> ReadPlanTree) -> (EmbedPath, a) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
updateNode f ([], a) rr = f a <$> rr
updateNode _ _ (Left e) = Left e
updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
updateNode :: Bool -> (a -> ReadPlanTree -> ReadPlanTree) -> (EmbedPath, a) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
updateNode _ f ([], a) rr = f a <$> rr
updateNode _ _ _ (Left e) = Left e

Check warning on line 1011 in src/PostgREST/Plan.hs

View check run for this annotation

Codecov / codecov/patch

src/PostgREST/Plan.hs#L1011

Added line #L1011 was not covered by tests
updateNode useTargetNames f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
case findNode of
Nothing -> Left $ ApiRequestErr $ NotEmbedded targetNodeName
Just target ->
(\node -> Node rootNode $ node : delete target forest) <$>
updateNode f (remainingPath, a) (Right target)
updateNode useTargetNames f (remainingPath, a) (Right $ updateLegacyAttrs target)
where
findNode :: Maybe ReadPlanTree
findNode = find (\(Node ReadPlan{relName, relAlias} _) -> fromMaybe relName relAlias == targetNodeName) forest
findNode = find matchTarget forest

matchTarget :: ReadPlanTree -> Bool
matchTarget (Node ReadPlan{relName, relAlias} _)
| useTargetNames = relName == targetNodeName || relAlias == Just targetNodeName
| otherwise = fromMaybe relName relAlias == targetNodeName

updateLegacyAttrs :: ReadPlanTree -> ReadPlanTree
updateLegacyAttrs node@(Node rPlan@ReadPlan{relName, relAlias} children)
| relName == targetNodeName && isJust relAlias && relAlias /= Just targetNodeName =
Node rPlan{relIsLegacyTargetNameMatch=True} children
| otherwise = node

mutatePlan :: Mutation -> QualifiedIdentifier -> ApiRequest -> SchemaCache -> ReadPlanTree -> Either Error MutatePlan
mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{dbTables, dbRepresentations} readReq =
Expand Down
Loading