From 0718904019dfe49427497d9d90dda7657c6750df Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 17:32:43 +0100 Subject: [PATCH 1/8] fix(completions): adjust range for later stmts --- crates/pgls_lsp/src/handlers/completions.rs | 10 ++- .../src/features/completions.rs | 12 +++ crates/pgls_workspace/src/workspace/server.rs | 2 +- .../src/workspace/server.tests.rs | 76 +++++++++++++++++++ 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/crates/pgls_lsp/src/handlers/completions.rs b/crates/pgls_lsp/src/handlers/completions.rs index 4985a50eb..2b06a6090 100644 --- a/crates/pgls_lsp/src/handlers/completions.rs +++ b/crates/pgls_lsp/src/handlers/completions.rs @@ -20,10 +20,12 @@ pub fn get_completions( let doc = session.document(&url)?; let encoding = adapters::negotiated_encoding(session.client_capabilities().unwrap()); - let completion_result = match session.workspace.get_completions(GetCompletionsParams { - path, - position: get_cursor_position(session, &url, params.text_document_position.position)?, - }) { + let position = get_cursor_position(session, &url, params.text_document_position.position)?; + + let completion_result = match session + .workspace + .get_completions(GetCompletionsParams { path, position }) + { Ok(result) => result, Err(e) => match e { WorkspaceError::DatabaseConnectionError(_) => { diff --git a/crates/pgls_workspace/src/features/completions.rs b/crates/pgls_workspace/src/features/completions.rs index 773803589..85a242091 100644 --- a/crates/pgls_workspace/src/features/completions.rs +++ b/crates/pgls_workspace/src/features/completions.rs @@ -21,6 +21,18 @@ pub struct CompletionsResult { pub(crate) items: Vec, } +impl CompletionsResult { + pub fn with_offset(mut items: Vec, offset: TextSize) -> Self { + for item in &mut items { + if let Some(text_edit) = &mut item.completion_text { + text_edit.range += offset; + } + } + + Self { items } + } +} + impl IntoIterator for CompletionsResult { type Item = CompletionItem; type IntoIter = as IntoIterator>::IntoIter; diff --git a/crates/pgls_workspace/src/workspace/server.rs b/crates/pgls_workspace/src/workspace/server.rs index 0d2be3ec9..2ff11ec5f 100644 --- a/crates/pgls_workspace/src/workspace/server.rs +++ b/crates/pgls_workspace/src/workspace/server.rs @@ -746,7 +746,7 @@ impl Workspace for WorkspaceServer { text: id.content().to_string(), }); - Ok(CompletionsResult { items }) + Ok(CompletionsResult::with_offset(items, range.start())) } } } diff --git a/crates/pgls_workspace/src/workspace/server.tests.rs b/crates/pgls_workspace/src/workspace/server.tests.rs index 29ffb3fd5..d49dda2ac 100644 --- a/crates/pgls_workspace/src/workspace/server.tests.rs +++ b/crates/pgls_workspace/src/workspace/server.tests.rs @@ -793,3 +793,79 @@ async fn test_search_path_configuration(test_db: PgPool) { ); } } + +#[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] +async fn test_multi_line_completions(test_db: PgPool) { + let setup = r#" + create schema auth; + + create table auth.users ( + id serial primary key, + email text not null + ); + "#; + + test_db.execute(setup).await.expect("setup sql failed"); + + let mut conf = PartialConfiguration::init(); + conf.merge_with(PartialConfiguration { + db: Some(PartialDatabaseConfiguration { + database: Some( + test_db + .connect_options() + .get_database() + .unwrap() + .to_string(), + ), + ..Default::default() + }), + ..Default::default() + }); + + let workspace = get_test_workspace(Some(conf)).expect("Unable to create test workspace"); + + let path = PgLSPath::new("test.sql"); + + let content = r#" +select * from auth.users; + +select * from auth.|; + +select * from auth.users; + "# + .trim(); + + let position = content + .find('|') + .map(|idx| pgls_text_size::TextSize::new(idx as u32)) + .expect("Unable to find cursor position in test content"); + + let sanitized_content = content.replace('|', ""); + + workspace + .open_file(OpenFileParams { + path: path.clone(), + content: sanitized_content.into(), + version: 1, + }) + .expect("Unable to open test file"); + + let completions = workspace + .get_completions(crate::workspace::GetCompletionsParams { + path: path.clone(), + position, + }) + .expect("Unable to request completions"); + + assert_eq!( + completions.items.len(), + 1, + "Expected one completion response" + ); + + assert_eq!( + completions.items[0].completion_text.as_ref().unwrap().range, + TextRange::new(position, position), + "Expected no syntax diagnostic" + ); +} From eebce70a8e9fe27df1270227d63a713d539d6aa0 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 17:37:39 +0100 Subject: [PATCH 2/8] no more query --- ...d5a53712d68afd273423f9310cd793586f532.json | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 .sqlx/query-b869d517301aaf69d382f092c09d5a53712d68afd273423f9310cd793586f532.json diff --git a/.sqlx/query-b869d517301aaf69d382f092c09d5a53712d68afd273423f9310cd793586f532.json b/.sqlx/query-b869d517301aaf69d382f092c09d5a53712d68afd273423f9310cd793586f532.json deleted file mode 100644 index c460f458d..000000000 --- a/.sqlx/query-b869d517301aaf69d382f092c09d5a53712d68afd273423f9310cd793586f532.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n(\nwith foreign_keys as (\n select\n cl.relnamespace::regnamespace::text as schema_name,\n cl.relname as table_name,\n cl.oid as table_oid,\n ct.conname as fkey_name,\n ct.conkey as col_attnums\n from\n pg_catalog.pg_constraint ct\n join pg_catalog.pg_class cl -- fkey owning table\n on ct.conrelid = cl.oid\n left join pg_catalog.pg_depend d\n on d.objid = cl.oid\n and d.deptype = 'e'\n where\n ct.contype = 'f' -- foreign key constraints\n and d.objid is null -- exclude tables that are dependencies of extensions\n and cl.relnamespace::regnamespace::text not in (\n 'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'extensions'\n )\n),\nindex_ as (\n select\n pi.indrelid as table_oid,\n indexrelid::regclass as index_,\n string_to_array(indkey::text, ' ')::smallint[] as col_attnums\n from\n pg_catalog.pg_index pi\n where\n indisvalid\n)\nselect\n 'unindexed_foreign_keys' as \"name!\",\n 'Unindexed foreign keys' as \"title!\",\n 'INFO' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Identifies foreign key constraints without a covering index, which can impact database performance.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has a foreign key \\`%s\\` without a covering index. This can lead to suboptimal query performance.',\n fk.schema_name,\n fk.table_name,\n fk.fkey_name\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0001_unindexed_foreign_keys' as \"remediation!\",\n jsonb_build_object(\n 'schema', fk.schema_name,\n 'name', fk.table_name,\n 'type', 'table',\n 'fkey_name', fk.fkey_name,\n 'fkey_columns', fk.col_attnums\n ) as \"metadata!\",\n format('unindexed_foreign_keys_%s_%s_%s', fk.schema_name, fk.table_name, fk.fkey_name) as \"cache_key!\"\nfrom\n foreign_keys fk\n left join index_ idx\n on fk.table_oid = idx.table_oid\n and fk.col_attnums = idx.col_attnums[1:array_length(fk.col_attnums, 1)]\n left join pg_catalog.pg_depend dep\n on idx.table_oid = dep.objid\n and dep.deptype = 'e'\nwhere\n idx.index_ is null\n and fk.schema_name not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null -- exclude tables owned by extensions\norder by\n fk.schema_name,\n fk.table_name,\n fk.fkey_name)\nunion all\n(\nselect\n 'auth_users_exposed' as \"name!\",\n 'Exposed Auth Users' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects if auth.users is exposed to anon or authenticated roles via a view or materialized view in schemas exposed to PostgREST, potentially compromising user data security.' as \"description!\",\n format(\n 'View/Materialized View \"%s\" in the public schema may expose \\`auth.users\\` data to anon or authenticated roles.',\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0002_auth_users_exposed' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'view',\n 'exposed_to', array_remove(array_agg(DISTINCT case when pg_catalog.has_table_privilege('anon', c.oid, 'SELECT') then 'anon' when pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT') then 'authenticated' end), null)\n ) as \"metadata!\",\n format('auth_users_exposed_%s_%s', n.nspname, c.relname) as \"cache_key!\"\nfrom\n -- Identify the oid for auth.users\n pg_catalog.pg_class auth_users_pg_class\n join pg_catalog.pg_namespace auth_users_pg_namespace\n on auth_users_pg_class.relnamespace = auth_users_pg_namespace.oid\n and auth_users_pg_class.relname = 'users'\n and auth_users_pg_namespace.nspname = 'auth'\n -- Depends on auth.users\n join pg_catalog.pg_depend d\n on d.refobjid = auth_users_pg_class.oid\n join pg_catalog.pg_rewrite r\n on r.oid = d.objid\n join pg_catalog.pg_class c\n on c.oid = r.ev_class\n join pg_catalog.pg_namespace n\n on n.oid = c.relnamespace\n join pg_catalog.pg_class pg_class_auth_users\n on d.refobjid = pg_class_auth_users.oid\nwhere\n d.deptype = 'n'\n and (\n pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')\n or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')\n )\n and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))\n -- Exclude self\n and c.relname <> '0002_auth_users_exposed'\n -- There are 3 insecure configurations\n and\n (\n -- Materialized views don't support RLS so this is insecure by default\n (c.relkind in ('m')) -- m for materialized view\n or\n -- Standard View, accessible to anon or authenticated that is security_definer\n (\n c.relkind = 'v' -- v for view\n -- Exclude security invoker views\n and not (\n lower(coalesce(c.reloptions::text,'{}'))::text[]\n && array[\n 'security_invoker=1',\n 'security_invoker=true',\n 'security_invoker=yes',\n 'security_invoker=on'\n ]\n )\n )\n or\n -- Standard View, security invoker, but no RLS enabled on auth.users\n (\n c.relkind in ('v') -- v for view\n -- is security invoker\n and (\n lower(coalesce(c.reloptions::text,'{}'))::text[]\n && array[\n 'security_invoker=1',\n 'security_invoker=true',\n 'security_invoker=yes',\n 'security_invoker=on'\n ]\n )\n and not pg_class_auth_users.relrowsecurity\n )\n )\ngroup by\n n.nspname,\n c.relname,\n c.oid)\nunion all\n(\nwith policies as (\n select\n nsp.nspname as schema_name,\n pb.tablename as table_name,\n pc.relrowsecurity as is_rls_active,\n polname as policy_name,\n polpermissive as is_permissive, -- if not, then restrictive\n (select array_agg(r::regrole) from unnest(polroles) as x(r)) as roles,\n case polcmd\n when 'r' then 'SELECT'\n when 'a' then 'INSERT'\n when 'w' then 'UPDATE'\n when 'd' then 'DELETE'\n when '*' then 'ALL'\n end as command,\n qual,\n with_check\n from\n pg_catalog.pg_policy pa\n join pg_catalog.pg_class pc\n on pa.polrelid = pc.oid\n join pg_catalog.pg_namespace nsp\n on pc.relnamespace = nsp.oid\n join pg_catalog.pg_policies pb\n on pc.relname = pb.tablename\n and nsp.nspname = pb.schemaname\n and pa.polname = pb.policyname\n)\nselect\n 'auth_rls_initplan' as \"name!\",\n 'Auth RLS Initialization Plan' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Detects if calls to \\`current_setting()\\` and \\`auth.()\\` in RLS policies are being unnecessarily re-evaluated for each row' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has a row level security policy \\`%s\\` that re-evaluates current_setting() or auth.() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \\`auth.()\\` with \\`(select auth.())\\`. See [docs](https://supabase.com/docs/guides/database/postgres/row-level-security#call-functions-with-select) for more info.',\n schema_name,\n table_name,\n policy_name\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0003_auth_rls_initplan' as \"remediation!\",\n jsonb_build_object(\n 'schema', schema_name,\n 'name', table_name,\n 'type', 'table'\n ) as \"metadata!\",\n format('auth_rls_init_plan_%s_%s_%s', schema_name, table_name, policy_name) as \"cache_key!\"\nfrom\n policies\nwhere\n is_rls_active\n -- NOTE: does not include realtime in support of monitoring policies on realtime.messages\n and schema_name not in (\n '_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', 'pgtle', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and (\n -- Example: auth.uid()\n (\n qual like '%auth.uid()%'\n and lower(qual) not like '%select auth.uid()%'\n )\n or (\n qual like '%auth.jwt()%'\n and lower(qual) not like '%select auth.jwt()%'\n )\n or (\n qual like '%auth.role()%'\n and lower(qual) not like '%select auth.role()%'\n )\n or (\n qual like '%auth.email()%'\n and lower(qual) not like '%select auth.email()%'\n )\n or (\n qual like '%current\\_setting(%)%'\n and lower(qual) not like '%select current\\_setting(%)%'\n )\n or (\n with_check like '%auth.uid()%'\n and lower(with_check) not like '%select auth.uid()%'\n )\n or (\n with_check like '%auth.jwt()%'\n and lower(with_check) not like '%select auth.jwt()%'\n )\n or (\n with_check like '%auth.role()%'\n and lower(with_check) not like '%select auth.role()%'\n )\n or (\n with_check like '%auth.email()%'\n and lower(with_check) not like '%select auth.email()%'\n )\n or (\n with_check like '%current\\_setting(%)%'\n and lower(with_check) not like '%select current\\_setting(%)%'\n )\n ))\nunion all\n(\nselect\n 'no_primary_key' as \"name!\",\n 'No Primary Key' as \"title!\",\n 'INFO' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Detects if a table does not have a primary key. Tables without a primary key can be inefficient to interact with at scale.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` does not have a primary key',\n pgns.nspname,\n pgc.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0004_no_primary_key' as \"remediation!\",\n jsonb_build_object(\n 'schema', pgns.nspname,\n 'name', pgc.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'no_primary_key_%s_%s',\n pgns.nspname,\n pgc.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class pgc\n join pg_catalog.pg_namespace pgns\n on pgns.oid = pgc.relnamespace\n left join pg_catalog.pg_index pgi\n on pgi.indrelid = pgc.oid\n left join pg_catalog.pg_depend dep\n on pgc.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n pgc.relkind = 'r' -- regular tables\n and pgns.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null -- exclude tables owned by extensions\ngroup by\n pgc.oid,\n pgns.nspname,\n pgc.relname\nhaving\n max(coalesce(pgi.indisprimary, false)::int) = 0)\nunion all\n(\nselect\n 'unused_index' as \"name!\",\n 'Unused Index' as \"title!\",\n 'INFO' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Detects if an index has never been used and may be a candidate for removal.' as \"description!\",\n format(\n 'Index \\`%s\\` on table \\`%s.%s\\` has not been used',\n psui.indexrelname,\n psui.schemaname,\n psui.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0005_unused_index' as \"remediation!\",\n jsonb_build_object(\n 'schema', psui.schemaname,\n 'name', psui.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'unused_index_%s_%s_%s',\n psui.schemaname,\n psui.relname,\n psui.indexrelname\n ) as \"cache_key!\"\n\nfrom\n pg_catalog.pg_stat_user_indexes psui\n join pg_catalog.pg_index pi\n on psui.indexrelid = pi.indexrelid\n left join pg_catalog.pg_depend dep\n on psui.relid = dep.objid\n and dep.deptype = 'e'\nwhere\n psui.idx_scan = 0\n and not pi.indisunique\n and not pi.indisprimary\n and dep.objid is null -- exclude tables owned by extensions\n and psui.schemaname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n ))\nunion all\n(\nselect\n 'multiple_permissive_policies' as \"name!\",\n 'Multiple Permissive Policies' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Detects if multiple permissive row level security policies are present on a table for the same \\`role\\` and \\`action\\` (e.g. insert). Multiple permissive policies are suboptimal for performance as each policy must be executed for every relevant query.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has multiple permissive policies for role \\`%s\\` for action \\`%s\\`. Policies include \\`%s\\`',\n n.nspname,\n c.relname,\n r.rolname,\n act.cmd,\n array_agg(p.polname order by p.polname)\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0006_multiple_permissive_policies' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'multiple_permissive_policies_%s_%s_%s_%s',\n n.nspname,\n c.relname,\n r.rolname,\n act.cmd\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_policy p\n join pg_catalog.pg_class c\n on p.polrelid = c.oid\n join pg_catalog.pg_namespace n\n on c.relnamespace = n.oid\n join pg_catalog.pg_roles r\n on p.polroles @> array[r.oid]\n or p.polroles = array[0::oid]\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e',\n lateral (\n select x.cmd\n from unnest((\n select\n case p.polcmd\n when 'r' then array['SELECT']\n when 'a' then array['INSERT']\n when 'w' then array['UPDATE']\n when 'd' then array['DELETE']\n when '*' then array['SELECT', 'INSERT', 'UPDATE', 'DELETE']\n else array['ERROR']\n end as actions\n )) x(cmd)\n ) act(cmd)\nwhere\n c.relkind = 'r' -- regular tables\n and p.polpermissive -- policy is permissive\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and r.rolname not like 'pg_%'\n and r.rolname not like 'supabase%admin'\n and not r.rolbypassrls\n and dep.objid is null -- exclude tables owned by extensions\ngroup by\n n.nspname,\n c.relname,\n r.rolname,\n act.cmd\nhaving\n count(1) > 1)\nunion all\n(\nselect\n 'policy_exists_rls_disabled' as \"name!\",\n 'Policy Exists RLS Disabled' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects cases where row level security (RLS) policies have been created, but RLS has not been enabled for the underlying table.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has RLS policies but RLS is not enabled on the table. Policies include %s.',\n n.nspname,\n c.relname,\n array_agg(p.polname order by p.polname)\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0007_policy_exists_rls_disabled' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'policy_exists_rls_disabled_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_policy p\n join pg_catalog.pg_class c\n on p.polrelid = c.oid\n join pg_catalog.pg_namespace n\n on c.relnamespace = n.oid\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n c.relkind = 'r' -- regular tables\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n -- RLS is disabled\n and not c.relrowsecurity\n and dep.objid is null -- exclude tables owned by extensions\ngroup by\n n.nspname,\n c.relname)\nunion all\n(\nselect\n 'rls_enabled_no_policy' as \"name!\",\n 'RLS Enabled No Policy' as \"title!\",\n 'INFO' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects cases where row level security (RLS) has been enabled on a table but no RLS policies have been created.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has RLS enabled, but no policies exist',\n n.nspname,\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0008_rls_enabled_no_policy' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'rls_enabled_no_policy_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class c\n left join pg_catalog.pg_policy p\n on p.polrelid = c.oid\n join pg_catalog.pg_namespace n\n on c.relnamespace = n.oid\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n c.relkind = 'r' -- regular tables\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n -- RLS is enabled\n and c.relrowsecurity\n and p.polname is null\n and dep.objid is null -- exclude tables owned by extensions\ngroup by\n n.nspname,\n c.relname)\nunion all\n(\nselect\n 'duplicate_index' as \"name!\",\n 'Duplicate Index' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Detects cases where two ore more identical indexes exist.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has identical indexes %s. Drop all except one of them',\n n.nspname,\n c.relname,\n array_agg(pi.indexname order by pi.indexname)\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0009_duplicate_index' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', case\n when c.relkind = 'r' then 'table'\n when c.relkind = 'm' then 'materialized view'\n else 'ERROR'\n end,\n 'indexes', array_agg(pi.indexname order by pi.indexname)\n ) as \"metadata!\",\n format(\n 'duplicate_index_%s_%s_%s',\n n.nspname,\n c.relname,\n array_agg(pi.indexname order by pi.indexname)\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_indexes pi\n join pg_catalog.pg_namespace n\n on n.nspname = pi.schemaname\n join pg_catalog.pg_class c\n on pi.tablename = c.relname\n and n.oid = c.relnamespace\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n c.relkind in ('r', 'm') -- tables and materialized views\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null -- exclude tables owned by extensions\ngroup by\n n.nspname,\n c.relkind,\n c.relname,\n replace(pi.indexdef, pi.indexname, '')\nhaving\n count(*) > 1)\nunion all\n(\nselect\n 'security_definer_view' as \"name!\",\n 'Security Definer View' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects views defined with the SECURITY DEFINER property. These views enforce Postgres permissions and row level security policies (RLS) of the view creator, rather than that of the querying user' as \"description!\",\n format(\n 'View \\`%s.%s\\` is defined with the SECURITY DEFINER property',\n n.nspname,\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0010_security_definer_view' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'view'\n ) as \"metadata!\",\n format(\n 'security_definer_view_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class c\n join pg_catalog.pg_namespace n\n on n.oid = c.relnamespace\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n c.relkind = 'v'\n and (\n pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')\n or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')\n )\n and substring(pg_catalog.version() from 'PostgreSQL ([0-9]+)') >= '15' -- security invoker was added in pg15\n and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null -- exclude views owned by extensions\n and not (\n lower(coalesce(c.reloptions::text,'{}'))::text[]\n && array[\n 'security_invoker=1',\n 'security_invoker=true',\n 'security_invoker=yes',\n 'security_invoker=on'\n ]\n ))\nunion all\n(\nselect\n 'function_search_path_mutable' as \"name!\",\n 'Function Search Path Mutable' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects functions where the search_path parameter is not set.' as \"description!\",\n format(\n 'Function \\`%s.%s\\` has a role mutable search_path',\n n.nspname,\n p.proname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', p.proname,\n 'type', 'function'\n ) as \"metadata!\",\n format(\n 'function_search_path_mutable_%s_%s_%s',\n n.nspname,\n p.proname,\n md5(p.prosrc) -- required when function is polymorphic\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_proc p\n join pg_catalog.pg_namespace n\n on p.pronamespace = n.oid\n left join pg_catalog.pg_depend dep\n on p.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null -- exclude functions owned by extensions\n -- Search path not set\n and not exists (\n select 1\n from unnest(coalesce(p.proconfig, '{}')) as config\n where config like 'search_path=%'\n ))\nunion all\n(\nselect\n 'rls_disabled_in_public' as \"name!\",\n 'RLS Disabled in Public' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects cases where row level security (RLS) has not been enabled on tables in schemas exposed to PostgREST' as \"description!\",\n format(\n 'Table \\`%s.%s\\` is public, but RLS has not been enabled.',\n n.nspname,\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0013_rls_disabled_in_public' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'rls_disabled_in_public_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class c\n join pg_catalog.pg_namespace n\n on c.relnamespace = n.oid\nwhere\n c.relkind = 'r' -- regular tables\n -- RLS is disabled\n and not c.relrowsecurity\n and (\n pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')\n or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')\n )\n and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n ))\nunion all\n(\nselect\n 'extension_in_public' as \"name!\",\n 'Extension in Public' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects extensions installed in the \\`public\\` schema.' as \"description!\",\n format(\n 'Extension \\`%s\\` is installed in the public schema. Move it to another schema.',\n pe.extname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0014_extension_in_public' as \"remediation!\",\n jsonb_build_object(\n 'schema', pe.extnamespace::regnamespace,\n 'name', pe.extname,\n 'type', 'extension'\n ) as \"metadata!\",\n format(\n 'extension_in_public_%s',\n pe.extname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_extension pe\nwhere\n -- plpgsql is installed by default in public and outside user control\n -- confirmed safe\n pe.extname not in ('plpgsql')\n -- Scoping this to public is not optimal. Ideally we would use the postgres\n -- search path. That currently isn't available via SQL. In other lints\n -- we have used has_schema_privilege('anon', 'extensions', 'USAGE') but that\n -- is not appropriate here as it would evaluate true for the extensions schema\n and pe.extnamespace::regnamespace::text = 'public')\nunion all\n(\nwith policies as (\n select\n nsp.nspname as schema_name,\n pb.tablename as table_name,\n polname as policy_name,\n qual,\n with_check\n from\n pg_catalog.pg_policy pa\n join pg_catalog.pg_class pc\n on pa.polrelid = pc.oid\n join pg_catalog.pg_namespace nsp\n on pc.relnamespace = nsp.oid\n join pg_catalog.pg_policies pb\n on pc.relname = pb.tablename\n and nsp.nspname = pb.schemaname\n and pa.polname = pb.policyname\n)\nselect\n 'rls_references_user_metadata' as \"name!\",\n 'RLS references user metadata' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects when Supabase Auth user_metadata is referenced insecurely in a row level security (RLS) policy.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has a row level security policy \\`%s\\` that references Supabase Auth \\`user_metadata\\`. \\`user_metadata\\` is editable by end users and should never be used in a security context.',\n schema_name,\n table_name,\n policy_name\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0015_rls_references_user_metadata' as \"remediation!\",\n jsonb_build_object(\n 'schema', schema_name,\n 'name', table_name,\n 'type', 'table'\n ) as \"metadata!\",\n format('rls_references_user_metadata_%s_%s_%s', schema_name, table_name, policy_name) as \"cache_key!\"\nfrom\n policies\nwhere\n schema_name not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and (\n -- Example: auth.jwt() -> 'user_metadata'\n -- False positives are possible, but it isn't practical to string match\n -- If false positive rate is too high, this expression can iterate\n qual like '%auth.jwt()%user_metadata%'\n or qual like '%current_setting(%request.jwt.claims%)%user_metadata%'\n or with_check like '%auth.jwt()%user_metadata%'\n or with_check like '%current_setting(%request.jwt.claims%)%user_metadata%'\n ))\nunion all\n(\nselect\n 'materialized_view_in_api' as \"name!\",\n 'Materialized View in API' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects materialized views that are accessible over the Data APIs.' as \"description!\",\n format(\n 'Materialized view \\`%s.%s\\` is selectable by anon or authenticated roles',\n n.nspname,\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0016_materialized_view_in_api' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'materialized view'\n ) as \"metadata!\",\n format(\n 'materialized_view_in_api_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class c\n join pg_catalog.pg_namespace n\n on n.oid = c.relnamespace\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n c.relkind = 'm'\n and (\n pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')\n or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')\n )\n and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null)\nunion all\n(\nselect\n 'foreign_table_in_api' as \"name!\",\n 'Foreign Table in API' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects foreign tables that are accessible over APIs. Foreign tables do not respect row level security policies.' as \"description!\",\n format(\n 'Foreign table \\`%s.%s\\` is accessible over APIs',\n n.nspname,\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0017_foreign_table_in_api' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'foreign table'\n ) as \"metadata!\",\n format(\n 'foreign_table_in_api_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class c\n join pg_catalog.pg_namespace n\n on n.oid = c.relnamespace\n left join pg_catalog.pg_depend dep\n on c.oid = dep.objid\n and dep.deptype = 'e'\nwhere\n c.relkind = 'f'\n and (\n pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')\n or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')\n )\n and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))\n and n.nspname not in (\n '_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', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'\n )\n and dep.objid is null)\nunion all\n(\nselect\n 'unsupported_reg_types' as \"name!\",\n 'Unsupported reg types' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Identifies columns using unsupported reg* types outside pg_catalog schema, which prevents database upgrades using pg_upgrade.' as \"description!\",\n format(\n 'Table \\`%s.%s\\` has a column \\`%s\\` with unsupported reg* type \\`%s\\`.',\n n.nspname,\n c.relname,\n a.attname,\n t.typname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=unsupported_reg_types' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'column', a.attname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'unsupported_reg_types_%s_%s_%s',\n n.nspname,\n c.relname,\n a.attname\n ) AS cache_key\nfrom\n pg_catalog.pg_attribute a\n join pg_catalog.pg_class c\n on a.attrelid = c.oid\n join pg_catalog.pg_namespace n\n on c.relnamespace = n.oid\n join pg_catalog.pg_type t\n on a.atttypid = t.oid\n join pg_catalog.pg_namespace tn\n on t.typnamespace = tn.oid\nwhere\n tn.nspname = 'pg_catalog'\n and t.typname in ('regcollation', 'regconfig', 'regdictionary', 'regnamespace', 'regoper', 'regoperator', 'regproc', 'regprocedure')\n and n.nspname not in ('pg_catalog', 'information_schema', 'pgsodium'))\nunion all\n(\nselect\n 'insecure_queue_exposed_in_api' as \"name!\",\n 'Insecure Queue Exposed in API' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects cases where an insecure Queue is exposed over Data APIs' as \"description!\",\n format(\n 'Table \\`%s.%s\\` is public, but RLS has not been enabled.',\n n.nspname,\n c.relname\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0019_insecure_queue_exposed_in_api' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c.relname,\n 'type', 'table'\n ) as \"metadata!\",\n format(\n 'rls_disabled_in_public_%s_%s',\n n.nspname,\n c.relname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_class c\n join pg_catalog.pg_namespace n\n on c.relnamespace = n.oid\nwhere\n c.relkind in ('r', 'I') -- regular or partitioned tables\n and not c.relrowsecurity -- RLS is disabled\n and (\n pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')\n or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')\n )\n and n.nspname = 'pgmq' -- tables in the pgmq schema\n and c.relname like 'q_%' -- only queue tables\n -- Constant requirements\n and 'pgmq_public' = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ','))))))\nunion all\n(\nwith constants as (\n select current_setting('block_size')::numeric as bs, 23 as hdr, 4 as ma\n),\n\nbloat_info as (\n select\n ma,\n bs,\n schemaname,\n tablename,\n (datawidth + (hdr + ma - (case when hdr % ma = 0 then ma else hdr % ma end)))::numeric as datahdr,\n (maxfracsum * (nullhdr + ma - (case when nullhdr % ma = 0 then ma else nullhdr % ma end))) as nullhdr2\n from (\n select\n schemaname,\n tablename,\n hdr,\n ma,\n bs,\n sum((1 - null_frac) * avg_width) as datawidth,\n max(null_frac) as maxfracsum,\n hdr + (\n select 1 + count(*) / 8\n from pg_stats s2\n where\n null_frac <> 0\n and s2.schemaname = s.schemaname\n and s2.tablename = s.tablename\n ) as nullhdr\n from pg_stats s, constants\n group by 1, 2, 3, 4, 5\n ) as foo\n),\n\ntable_bloat as (\n select\n schemaname,\n tablename,\n cc.relpages,\n bs,\n ceil((cc.reltuples * ((datahdr + ma -\n (case when datahdr % ma = 0 then ma else datahdr % ma end)) + nullhdr2 + 4)) / (bs - 20::float)) as otta\n from\n bloat_info\n join pg_class cc\n on cc.relname = bloat_info.tablename\n join pg_namespace nn\n on cc.relnamespace = nn.oid\n and nn.nspname = bloat_info.schemaname\n and nn.nspname <> 'information_schema'\n where\n cc.relkind = 'r'\n and cc.relam = (select oid from pg_am where amname = 'heap')\n),\n\nbloat_data as (\n select\n 'table' as type,\n schemaname,\n tablename as object_name,\n round(case when otta = 0 then 0.0 else table_bloat.relpages / otta::numeric end, 1) as bloat,\n case when relpages < otta then 0 else (bs * (table_bloat.relpages - otta)::bigint)::bigint end as raw_waste\n from\n table_bloat\n)\n\nselect\n 'table_bloat' as \"name!\",\n 'Table Bloat' as \"title!\",\n 'INFO' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['PERFORMANCE'] as \"categories!\",\n 'Detects if a table has excess bloat and may benefit from maintenance operations like vacuum full or cluster.' as \"description!\",\n format(\n 'Table `%s`.`%s` has excessive bloat',\n bloat_data.schemaname,\n bloat_data.object_name\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0020_table_bloat' as \"remediation!\",\n jsonb_build_object(\n 'schema', bloat_data.schemaname,\n 'name', bloat_data.object_name,\n 'type', bloat_data.type\n ) as \"metadata!\",\n format(\n 'table_bloat_%s_%s',\n bloat_data.schemaname,\n bloat_data.object_name\n ) as \"cache_key!\"\nfrom\n bloat_data\nwhere\n bloat > 70.0\n and raw_waste > (20 * 1024 * 1024) -- filter for waste > 200 MB\norder by\n schemaname,\n object_name)\nunion all\n(\nselect\n 'fkey_to_auth_unique' as \"name!\",\n 'Foreign Key to Auth Unique Constraint' as \"title!\",\n 'ERROR' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects user defined foreign keys to unique constraints in the auth schema.' as \"description!\",\n format(\n 'Table `%s`.`%s` has a foreign key `%s` referencing an auth unique constraint',\n n.nspname, -- referencing schema\n c_rel.relname, -- referencing table\n c.conname -- fkey name\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0021_fkey_to_auth_unique' as \"remediation!\",\n jsonb_build_object(\n 'schema', n.nspname,\n 'name', c_rel.relname,\n 'foreign_key', c.conname\n ) as \"metadata!\",\n format(\n 'fkey_to_auth_unique_%s_%s_%s',\n n.nspname, -- referencing schema\n c_rel.relname, -- referencing table\n c.conname\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_constraint c\n join pg_catalog.pg_class c_rel\n on c.conrelid = c_rel.oid\n join pg_catalog.pg_namespace n\n on c_rel.relnamespace = n.oid\n join pg_catalog.pg_class ref_rel\n on c.confrelid = ref_rel.oid\n join pg_catalog.pg_namespace cn\n on ref_rel.relnamespace = cn.oid\n join pg_catalog.pg_index i\n on c.conindid = i.indexrelid\nwhere c.contype = 'f'\n and cn.nspname = 'auth'\n and i.indisunique\n and not i.indisprimary)\nunion all\n(\nselect\n 'extension_versions_outdated' as \"name!\",\n 'Extension Versions Outdated' as \"title!\",\n 'WARN' as \"level!\",\n 'EXTERNAL' as \"facing!\",\n array['SECURITY'] as \"categories!\",\n 'Detects extensions that are not using the default (recommended) version.' as \"description!\",\n format(\n 'Extension `%s` is using version `%s` but version `%s` is available. Using outdated extension versions may expose the database to security vulnerabilities.',\n ext.name,\n ext.installed_version,\n ext.default_version\n ) as \"detail!\",\n 'https://supabase.com/docs/guides/database/database-linter?lint=0022_extension_versions_outdated' as \"remediation!\",\n jsonb_build_object(\n 'extension_name', ext.name,\n 'installed_version', ext.installed_version,\n 'default_version', ext.default_version\n ) as \"metadata!\",\n format(\n 'extension_versions_outdated_%s_%s',\n ext.name,\n ext.installed_version\n ) as \"cache_key!\"\nfrom\n pg_catalog.pg_available_extensions ext\njoin\n -- ignore versions not in pg_available_extension_versions\n -- e.g. residue of pg_upgrade\n pg_catalog.pg_available_extension_versions extv\n on extv.name = ext.name and extv.installed\nwhere\n ext.installed_version is not null\n and ext.default_version is not null\n and ext.installed_version != ext.default_version\norder by\n ext.name)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name!", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "title!", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "level!", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "facing!", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "categories!", - "type_info": "TextArray" - }, - { - "ordinal": 5, - "name": "description!", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "detail!", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "remediation!", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "metadata!", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "cache_key!", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ] - }, - "hash": "b869d517301aaf69d382f092c09d5a53712d68afd273423f9310cd793586f532" -} From 18f9d5b4bbc633a06e5321b51070036333440af9 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 17:45:22 +0100 Subject: [PATCH 3/8] ready2 --- crates/pgls_workspace/src/workspace/server.tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pgls_workspace/src/workspace/server.tests.rs b/crates/pgls_workspace/src/workspace/server.tests.rs index d49dda2ac..34fa4715d 100644 --- a/crates/pgls_workspace/src/workspace/server.tests.rs +++ b/crates/pgls_workspace/src/workspace/server.tests.rs @@ -845,7 +845,7 @@ select * from auth.users; workspace .open_file(OpenFileParams { path: path.clone(), - content: sanitized_content.into(), + content: sanitized_content, version: 1, }) .expect("Unable to open test file"); From d12c00be01ec02d943a10940e4b77909db710f00 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 18:07:01 +0100 Subject: [PATCH 4/8] use pinned commit --- .github/workflows/pull_request.yml | 8 ++++---- .github/workflows/release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3a979a34d..ea7694dbe 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -113,7 +113,7 @@ jobs: sleep 1 done - - uses: tree-sitter/setup-action@v2 + - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 with: install-lib: false @@ -172,7 +172,7 @@ jobs: - name: Setup Postgres uses: ./.github/actions/setup-postgres - - uses: tree-sitter/setup-action@v2 + - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 with: install-lib: false @@ -204,7 +204,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: cache-base: main - - uses: tree-sitter/setup-action@v2 + - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 with: install-lib: false - name: Build main binary @@ -259,7 +259,7 @@ jobs: cache-base: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: tree-sitter/setup-action@v2 + - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 with: install-lib: false - name: Ensure RustFMT on nightly toolchain diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 375cdcb99..3cfc027b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: sudo apt-get update sudo apt-get install -y musl-tools - - uses: tree-sitter/setup-action@v2 + - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 with: install-lib: false From 2942f05918f1eec9d02c36e37efe7cf329996d70 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 18:10:04 +0100 Subject: [PATCH 5/8] undo --- .github/workflows/pull_request.yml | 8 ++++---- .github/workflows/release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ea7694dbe..3a979a34d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -113,7 +113,7 @@ jobs: sleep 1 done - - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 + - uses: tree-sitter/setup-action@v2 with: install-lib: false @@ -172,7 +172,7 @@ jobs: - name: Setup Postgres uses: ./.github/actions/setup-postgres - - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 + - uses: tree-sitter/setup-action@v2 with: install-lib: false @@ -204,7 +204,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: cache-base: main - - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 + - uses: tree-sitter/setup-action@v2 with: install-lib: false - name: Build main binary @@ -259,7 +259,7 @@ jobs: cache-base: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 + - uses: tree-sitter/setup-action@v2 with: install-lib: false - name: Ensure RustFMT on nightly toolchain diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3cfc027b5..375cdcb99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: sudo apt-get update sudo apt-get install -y musl-tools - - uses: tree-sitter/setup-action@c4c258862a6a7e43a7aef152256bbf659972c408 + - uses: tree-sitter/setup-action@v2 with: install-lib: false From 1bf7f2e51a235acc9917c2cb082bfc00a2f60ede Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 18:47:29 +0100 Subject: [PATCH 6/8] try revert --- .github/workflows/pull_request.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3a979a34d..a06c416ef 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -113,9 +113,8 @@ jobs: sleep 1 done - - uses: tree-sitter/setup-action@v2 - with: - install-lib: false + - name: Setup tree-sitter + run: cargo install tree-sitter-cli - name: Setup sqlx-cli run: cargo install sqlx-cli @@ -172,9 +171,8 @@ jobs: - name: Setup Postgres uses: ./.github/actions/setup-postgres - - uses: tree-sitter/setup-action@v2 - with: - install-lib: false + - name: Setup Postgres + run: cargo install tree-sitter-cli - name: Run tests run: cargo test --workspace From df30dd539c77acf5846d58b7f28c1df5f13061b9 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 19:08:37 +0100 Subject: [PATCH 7/8] cache+oginstall --- .github/workflows/pull_request.yml | 30 ++++++++++++++++++++++++++---- .github/workflows/release.yml | 9 +++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a06c416ef..8b2e49742 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -113,6 +113,13 @@ jobs: sleep 1 done + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/tree-sitter + ~/.cargo/bin/sqlx + key: ${{ runner.os }}-tree-sitter-${{ hashFiles('rust-toolchain.toml') }} + - name: Setup tree-sitter run: cargo install tree-sitter-cli @@ -159,6 +166,7 @@ jobs: uses: actions/checkout@v4 with: submodules: true + - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -171,6 +179,12 @@ jobs: - name: Setup Postgres uses: ./.github/actions/setup-postgres + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/tree-sitter + key: ${{ runner.os }}-tree-sitter-${{ hashFiles('rust-toolchain.toml') }} + - name: Setup Postgres run: cargo install tree-sitter-cli @@ -202,9 +216,13 @@ jobs: uses: moonrepo/setup-rust@v1 with: cache-base: main - - uses: tree-sitter/setup-action@v2 + - uses: actions/cache@v4 with: - install-lib: false + path: | + ~/.cargo/bin/tree-sitter + key: ${{ runner.os }}-tree-sitter-${{ hashFiles('rust-toolchain.toml') }} + - name: setup tree-sitter + run: cargo install tree-sitter-cli - name: Build main binary run: cargo build -p pgls_cli --release - name: Setup Bun @@ -257,9 +275,13 @@ jobs: cache-base: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: tree-sitter/setup-action@v2 + - uses: actions/cache@v4 with: - install-lib: false + path: | + ~/.cargo/bin/tree-sitter + key: ${{ runner.os }}-tree-sitter-${{ hashFiles('rust-toolchain.toml') }} + - name: setup tree-sitter + run: cargo install tree-sitter-cli - name: Ensure RustFMT on nightly toolchain run: rustup component add rustfmt --toolchain nightly - name: echo toolchain diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 375cdcb99..1c2671d95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,9 +72,14 @@ jobs: sudo apt-get update sudo apt-get install -y musl-tools - - uses: tree-sitter/setup-action@v2 + - uses: actions/cache@v4 with: - install-lib: false + path: | + ~/.cargo/bin/tree-sitter + key: ${{ runner.os }}-tree-sitter-${{ hashFiles('rust-toolchain.toml') }} + + - name: Setup tree-sitter + run: cargo install tree-sitter-cli - name: Setup Postgres uses: ./.github/actions/setup-postgres From d6cb9073db56bbcaa0ab297f90f8ea016cf249c7 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 12 Dec 2025 19:14:08 +0100 Subject: [PATCH 8/8] safe to use --- .github/workflows/pull_request.yml | 8 ++++---- .github/workflows/release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8b2e49742..16a6015ee 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -113,7 +113,7 @@ jobs: sleep 1 done - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ~/.cargo/bin/tree-sitter @@ -179,7 +179,7 @@ jobs: - name: Setup Postgres uses: ./.github/actions/setup-postgres - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ~/.cargo/bin/tree-sitter @@ -216,7 +216,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: cache-base: main - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ~/.cargo/bin/tree-sitter @@ -275,7 +275,7 @@ jobs: cache-base: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ~/.cargo/bin/tree-sitter diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c2671d95..1c4b00aea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: sudo apt-get update sudo apt-get install -y musl-tools - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ~/.cargo/bin/tree-sitter