Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5613288
perf(lints): deparse each policy once in auth_rls_initplan
avallete Oct 20, 2026
2067c45
perf(lints): read policy expressions from pg_policy in rls_references…
avallete Oct 20, 2026
d3db70d
perf(lints): read policy expressions from pg_policy in rls_policy_alw…
avallete Oct 20, 2026
68feff5
perf(lints): read policy expressions from pg_policy in auth_rls_initplan
avallete Oct 20, 2026
8c923b0
perf(lints): inline pg_stat_user_indexes in unused_index
avallete Oct 20, 2026
c338e99
refactor(lints): order auth_rls_initplan output
avallete Oct 20, 2026
c355634
perf(lints): compare index keys without a text round trip in unindexe…
avallete Oct 20, 2026
93fea2c
perf(lints): check for a primary key with not exists in no_primary_key
avallete Oct 20, 2026
dd70f9f
perf(lints): resolve the auth schema once in fkey_to_auth_unique
avallete Oct 20, 2026
161c1f1
perf(lints): count null columns in the same aggregate in table_bloat
avallete Oct 20, 2026
8e826d8
Revert "perf(lints): resolve the auth schema once in fkey_to_auth_uni…
avallete Oct 21, 2026
ef79a0c
Revert "perf(lints): inline pg_stat_user_indexes in unused_index"
avallete Oct 21, 2026
cfd240b
Revert "perf(lints): compare index keys without a text round trip in …
avallete Oct 21, 2026
d5b3dba
Merge remote-tracking branch 'origin/avallete/splinter-rule-optimizat…
avallete Oct 21, 2026
6578b53
Merge remote-tracking branch 'origin/avallete/splinter-rule-optimizat…
avallete Oct 22, 2026
3571371
Merge remote-tracking branch 'origin/avallete/splinter-rule-optimizat…
avallete Oct 22, 2026
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
19 changes: 10 additions & 9 deletions lints/0003_auth_rls_initplan.sql
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
create view lint."0003_auth_rls_initplan" as

with policies as (
with policies as materialized (
select
nsp.nspname as schema_name,
pb.tablename as table_name,
pc.relname as table_name,
pc.relrowsecurity as is_rls_active,
polname as policy_name,
polpermissive as is_permissive, -- if not, then restrictive
Expand All @@ -15,18 +15,15 @@ with policies as (
when 'd' then 'DELETE'
when '*' then 'ALL'
end as command,
qual,
with_check
-- what pg_policies reports as qual and with_check
pg_catalog.pg_get_expr(pa.polqual, pa.polrelid) as qual,
pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid) as with_check
from
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
)
select
'auth_rls_initplan' as name,
Expand Down Expand Up @@ -98,4 +95,8 @@ where
with_check like '%current\_setting(%)%'
and lower(with_check) not like '%select current\_setting(%)%'
)
);
)
order by
schema_name,
table_name,
policy_name;
14 changes: 6 additions & 8 deletions lints/0004_no_primary_key.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ from
pg_catalog.pg_class pgc
join pg_catalog.pg_namespace pgns
on pgns.oid = pgc.relnamespace
left join pg_catalog.pg_index pgi
on pgi.indrelid = pgc.oid
left join pg_catalog.pg_depend dep
on pgc.oid = dep.objid
and dep.deptype = 'e'
Expand All @@ -39,9 +37,9 @@ 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 tables owned by extensions
group by
pgc.oid,
pgns.nspname,
pgc.relname
having
max(coalesce(pgi.indisprimary, false)::int) = 0;
and not exists (
select 1
from pg_catalog.pg_index pgi
where pgi.indrelid = pgc.oid
and pgi.indisprimary
);
11 changes: 4 additions & 7 deletions lints/0015_rls_references_user_metadata.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@ create view lint."0015_rls_references_user_metadata" as
with policies as (
select
nsp.nspname as schema_name,
pb.tablename as table_name,
pc.relname as table_name,
polname as policy_name,
qual,
with_check
-- what pg_policies reports as qual and with_check
pg_catalog.pg_get_expr(pa.polqual, pa.polrelid) as qual,
pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid) as with_check
from
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
)
select
'rls_references_user_metadata' as name,
Expand Down
10 changes: 2 additions & 8 deletions lints/0020_table_bloat.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,8 @@ bloat_info as (
bs,
sum((1 - null_frac) * avg_width) as datawidth,
max(null_frac) as maxfracsum,
hdr + (
select 1 + count(*) / 8
from pg_stats s2
where
null_frac <> 0
and s2.schemaname = s.schemaname
and s2.tablename = s.tablename
) as nullhdr
-- the same pg_stats rows the correlated subquery used to re-read: this group's
hdr + 1 + count(*) filter (where null_frac <> 0) / 8 as nullhdr
from pg_stats s, constants
group by 1, 2, 3, 4, 5
) as foo
Expand Down
15 changes: 6 additions & 9 deletions lints/0024_rls_policy_always_true.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ create view lint."0024_rls_policy_always_true" as
with policies as (
select
nsp.nspname as schema_name,
pb.tablename as table_name,
pc.relname as table_name,
pc.relrowsecurity as is_rls_active,
pa.polname as policy_name,
pa.polpermissive as is_permissive,
Expand All @@ -18,21 +18,18 @@ with policies as (
when 'd' then 'DELETE'
when '*' then 'ALL'
end as command,
pb.qual,
pb.with_check,
-- what pg_policies reports as qual and with_check
pg_catalog.pg_get_expr(pa.polqual, pa.polrelid) as qual,
pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid) as with_check,
-- Normalize expressions by removing whitespace and lowercasing
replace(replace(replace(lower(coalesce(pb.qual, '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_qual,
replace(replace(replace(lower(coalesce(pb.with_check, '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_with_check
replace(replace(replace(lower(coalesce(pg_catalog.pg_get_expr(pa.polqual, pa.polrelid), '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_qual,
replace(replace(replace(lower(coalesce(pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid), '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_with_check
from
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
where
pc.relkind = 'r' -- regular tables
and nsp.nspname not in (
Expand Down
69 changes: 28 additions & 41 deletions splinter.sql
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ group by
c.oid)
union all
(
with policies as (
with policies as materialized (
select
nsp.nspname as schema_name,
pb.tablename as table_name,
pc.relname as table_name,
pc.relrowsecurity as is_rls_active,
polname as policy_name,
polpermissive as is_permissive, -- if not, then restrictive
Expand All @@ -204,18 +204,15 @@ with policies as (
when 'd' then 'DELETE'
when '*' then 'ALL'
end as command,
qual,
with_check
-- what pg_policies reports as qual and with_check
pg_catalog.pg_get_expr(pa.polqual, pa.polrelid) as qual,
pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid) as with_check
from
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
)
select
'auth_rls_initplan' as name,
Expand Down Expand Up @@ -287,7 +284,11 @@ where
with_check like '%current\_setting(%)%'
and lower(with_check) not like '%select current\_setting(%)%'
)
))
)
order by
schema_name,
table_name,
policy_name)
union all
(
select
Expand Down Expand Up @@ -317,8 +318,6 @@ from
pg_catalog.pg_class pgc
join pg_catalog.pg_namespace pgns
on pgns.oid = pgc.relnamespace
left join pg_catalog.pg_index pgi
on pgi.indrelid = pgc.oid
left join pg_catalog.pg_depend dep
on pgc.oid = dep.objid
and dep.deptype = 'e'
Expand All @@ -329,12 +328,12 @@ 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 tables owned by extensions
group by
pgc.oid,
pgns.nspname,
pgc.relname
having
max(coalesce(pgi.indisprimary, false)::int) = 0)
and not exists (
select 1
from pg_catalog.pg_index pgi
where pgi.indrelid = pgc.oid
and pgi.indisprimary
))
union all
(
select
Expand Down Expand Up @@ -782,20 +781,17 @@ union all
with policies as (
select
nsp.nspname as schema_name,
pb.tablename as table_name,
pc.relname as table_name,
polname as policy_name,
qual,
with_check
-- what pg_policies reports as qual and with_check
pg_catalog.pg_get_expr(pa.polqual, pa.polrelid) as qual,
pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid) as with_check
from
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
)
select
'rls_references_user_metadata' as name,
Expand Down Expand Up @@ -1026,14 +1022,8 @@ bloat_info as (
bs,
sum((1 - null_frac) * avg_width) as datawidth,
max(null_frac) as maxfracsum,
hdr + (
select 1 + count(*) / 8
from pg_stats s2
where
null_frac <> 0
and s2.schemaname = s.schemaname
and s2.tablename = s.tablename
) as nullhdr
-- the same pg_stats rows the correlated subquery used to re-read: this group's
hdr + 1 + count(*) filter (where null_frac <> 0) / 8 as nullhdr
from pg_stats s, constants
group by 1, 2, 3, 4, 5
) as foo
Expand Down Expand Up @@ -1299,7 +1289,7 @@ union all
with policies as (
select
nsp.nspname as schema_name,
pb.tablename as table_name,
pc.relname as table_name,
pc.relrowsecurity as is_rls_active,
pa.polname as policy_name,
pa.polpermissive as is_permissive,
Expand All @@ -1312,21 +1302,18 @@ with policies as (
when 'd' then 'DELETE'
when '*' then 'ALL'
end as command,
pb.qual,
pb.with_check,
-- what pg_policies reports as qual and with_check
pg_catalog.pg_get_expr(pa.polqual, pa.polrelid) as qual,
pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid) as with_check,
-- Normalize expressions by removing whitespace and lowercasing
replace(replace(replace(lower(coalesce(pb.qual, '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_qual,
replace(replace(replace(lower(coalesce(pb.with_check, '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_with_check
replace(replace(replace(lower(coalesce(pg_catalog.pg_get_expr(pa.polqual, pa.polrelid), '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_qual,
replace(replace(replace(lower(coalesce(pg_catalog.pg_get_expr(pa.polwithcheck, pa.polrelid), '')), ' ', ''), E'\n', ''), E'\t', '') as normalized_with_check
from
pg_catalog.pg_policy pa
join pg_catalog.pg_class pc
on pa.polrelid = pc.oid
join pg_catalog.pg_namespace nsp
on pc.relnamespace = nsp.oid
join pg_catalog.pg_policies pb
on pc.relname = pb.tablename
and nsp.nspname = pb.schemaname
and pa.polname = pb.policyname
where
pc.relkind = 'r' -- regular tables
and nsp.nspname not in (
Expand Down
6 changes: 3 additions & 3 deletions test/expected/0003_auth_rls_initplan.out
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ begin;
select * from lint."0003_auth_rls_initplan";
name | title | level | facing | categories | description | detail | remediation | metadata | cache_key
-------------------+------------------------------+-------+----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------+------------------------------------------------------+----------------------------------------------------------
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_uid\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_uid
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_current_setting\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_current_setting
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_email\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_email
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_jwt\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_jwt
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_role\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_role
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_email\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_email
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_current_setting\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_current_setting
auth_rls_initplan | Auth RLS Initialization Plan | WARN | EXTERNAL | {PERFORMANCE} | Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row | Table \`public.foo\` has a row level security policy \`bad_policy_uid\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info. | https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan | {"name": "foo", "type": "table", "schema": "public"} | auth_rls_init_plan_public_foo_bad_policy_uid
(5 rows)

rollback to savepoint a;
Expand Down
Loading