From 766e51cd99764d44703a2c09b9329031abb3c6d7 Mon Sep 17 00:00:00 2001 From: Anton Karpov Date: Sun, 2 Aug 2026 23:49:20 +0300 Subject: [PATCH] fix(0011): do not report aggregates as having a mutable search_path `0011_function_search_path_mutable` scans `pg_proc` without filtering `prokind`, so aggregates are reported alongside functions. An aggregate's `pg_proc` row is a placeholder: it has no body, `prolang` is `internal`, and `CREATE AGGREGATE` has no `SET` clause, so `proconfig` is always null. The lint's "search path not set" condition is therefore always true for aggregates, and there is no way for a user to resolve the warning. Reproducing the report: create function public.uuid_min(uuid, uuid) returns uuid set search_path = '' language sql immutable strict as $$ select least($1, $2) $$; create aggregate public.min_uuid(uuid) ( sfunc = public.uuid_min, stype = uuid, combinefunc = public.uuid_min, parallel = safe ); `public.min_uuid` is reported even though every function it is built from pins its search path. Exclude `prokind = 'a'`. Procedures and window functions are left alone: both accept a `SET` clause and are still linted. No coverage is lost, because an aggregate's support functions are ordinary `pg_proc` entries and continue to be reported on their own -- the test asserts exactly that. splinter.sql regenerated via bin/compile.py. Closes #139 --- lints/0011_function_search_path_mutable.sql | 4 ++ splinter.sql | 4 ++ .../0011_function_search_path_mutable.out | 37 +++++++++++++++++++ .../sql/0011_function_search_path_mutable.sql | 33 +++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/lints/0011_function_search_path_mutable.sql b/lints/0011_function_search_path_mutable.sql index 36ed1db..a13fbfe 100644 --- a/lints/0011_function_search_path_mutable.sql +++ b/lints/0011_function_search_path_mutable.sql @@ -37,6 +37,10 @@ where '_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) and dep.objid is null -- exclude functions owned by extensions + -- Exclude aggregates: their pg_proc entry is a placeholder with no body and + -- CREATE AGGREGATE has no SET clause, so they can never carry a search_path. + -- The support functions they are built from are linted in their own right. + and p.prokind <> 'a' -- Search path not set and not exists ( select 1 diff --git a/splinter.sql b/splinter.sql index 9cd66c9..a69a3e0 100644 --- a/splinter.sql +++ b/splinter.sql @@ -696,6 +696,10 @@ where '_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) and dep.objid is null -- exclude functions owned by extensions + -- Exclude aggregates: their pg_proc entry is a placeholder with no body and + -- CREATE AGGREGATE has no SET clause, so they can never carry a search_path. + -- The support functions they are built from are linted in their own right. + and p.prokind <> 'a' -- Search path not set and not exists ( select 1 diff --git a/test/expected/0011_function_search_path_mutable.out b/test/expected/0011_function_search_path_mutable.out index b24c556..4b30c8b 100644 --- a/test/expected/0011_function_search_path_mutable.out +++ b/test/expected/0011_function_search_path_mutable.out @@ -47,4 +47,41 @@ begin; ------+-------+-------+--------+------------+-------------+--------+-------------+----------+----------- (0 rows) + -- An aggregate is not reported: CREATE AGGREGATE has no SET clause, so its + -- pg_proc entry can never carry a search_path + create function public.uuid_min(uuid, uuid) + returns uuid + set search_path = '' + language sql + immutable strict + as $$ + select least($1, $2); + $$; + create aggregate public.min_uuid(uuid) ( + sfunc = public.uuid_min, + stype = uuid, + combinefunc = public.uuid_min, + parallel = safe + ); + -- 0 issues + select * from lint."0011_function_search_path_mutable"; + name | title | level | facing | categories | description | detail | remediation | metadata | cache_key +------+-------+-------+--------+------------+-------------+--------+-------------+----------+----------- +(0 rows) + + -- The support function is still linted on its own + create or replace function public.uuid_min(uuid, uuid) + returns uuid + language sql + immutable strict + as $$ + select least($1, $2); + $$; + -- 1 issue, for public.uuid_min and not for the aggregate + select * from lint."0011_function_search_path_mutable"; + name | title | level | facing | categories | description | detail | remediation | metadata | cache_key +------------------------------+------------------------------+-------+----------+------------+---------------------------------------------------------------+-------------------------------------------------------------+--------------------------------------------------------------------------------------------------+--------------------------------------------------------------+------------------------------------------------------------------------------- + function_search_path_mutable | Function Search Path Mutable | WARN | EXTERNAL | {SECURITY} | Detects functions where the search_path parameter is not set. | Function \`public.uuid_min\` has a role mutable search_path | https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable | {"name": "uuid_min", "type": "function", "schema": "public"} | function_search_path_mutable_public_uuid_min_f4e24529160b926c46151c3f612a6c29 +(1 row) + rollback; diff --git a/test/sql/0011_function_search_path_mutable.sql b/test/sql/0011_function_search_path_mutable.sql index a727fce..182100c 100644 --- a/test/sql/0011_function_search_path_mutable.sql +++ b/test/sql/0011_function_search_path_mutable.sql @@ -39,5 +39,38 @@ begin; select * from lint."0011_function_search_path_mutable"; + -- An aggregate is not reported: CREATE AGGREGATE has no SET clause, so its + -- pg_proc entry can never carry a search_path + create function public.uuid_min(uuid, uuid) + returns uuid + set search_path = '' + language sql + immutable strict + as $$ + select least($1, $2); + $$; + + create aggregate public.min_uuid(uuid) ( + sfunc = public.uuid_min, + stype = uuid, + combinefunc = public.uuid_min, + parallel = safe + ); + + -- 0 issues + select * from lint."0011_function_search_path_mutable"; + + -- The support function is still linted on its own + create or replace function public.uuid_min(uuid, uuid) + returns uuid + language sql + immutable strict + as $$ + select least($1, $2); + $$; + + -- 1 issue, for public.uuid_min and not for the aggregate + select * from lint."0011_function_search_path_mutable"; + rollback;