From fd6a3bdccfe078ca0fec820efa6b5a05b27dbec2 Mon Sep 17 00:00:00 2001 From: Joel Jakobsson Date: Mon, 15 Dec 2025 21:34:48 +0100 Subject: [PATCH] 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. --- CHANGELOG.md | 2 ++ src/PostgREST/Plan.hs | 18 ++++++++++-------- test/spec/Feature/Query/RpcSpec.hs | 14 +++++++++++++- test/spec/fixtures/schema.sql | 5 +++++ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c2a88e9..ba4f4ce6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ All notable changes to this project will be documented in this file. From versio ### 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. diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index df6229152..73836b264 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -256,14 +256,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 diff --git a/test/spec/Feature/Query/RpcSpec.hs b/test/spec/Feature/Query/RpcSpec.hs index c3745e0f2..e8b6e2aa0 100644 --- a/test/spec/Feature/Query/RpcSpec.hs +++ b/test/spec/Feature/Query/RpcSpec.hs @@ -1146,12 +1146,24 @@ spec = } context "single unnamed param" $ do - it "can insert json directly" $ + it "can insert json directly with unnamed parameter" $ post "/rpc/unnamed_json_param" [json|{"A": 1, "B": 2, "C": 3}|] `shouldRespondWith` [json|{"A": 1, "B": 2, "C": 3}|] + it "rejects json body when single param has a name" $ + post "/rpc/named_json_param" + [json|{"A": 1, "B": 2, "C": 3}|] + `shouldRespondWith` + [json|{ + "code":"PGRST202", + "message":"Could not find the function test.named_json_param(A, B, C) in the schema cache", + "details":"Searched for the function test.named_json_param with parameters A, B, C or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.", + "hint":null + }|] + { matchStatus = 404 } + it "can insert text directly" $ do request methodPost "/rpc/unnamed_text_param" [("Content-Type", "text/plain"), ("Accept", "text/plain")] diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index 19167867b..1af8f5212 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2356,6 +2356,11 @@ create or replace function test.unnamed_json_param(json) returns json as $$ select $1; $$ language sql; +-- Function with a NAMED json parameter (for testing single param fallback behavior) +create or replace function test.named_json_param(data json) returns json as $$ + select data; +$$ language sql; + create or replace function test.unnamed_text_param(text) returns "text/plain" as $$ select $1::"text/plain"; $$ language sql;