Skip to content

SQL: set search_path in create-function options and do $$ blocks break parsing — every Supabase migration reports parse_partial #1598

Description

@cherryb16

Version

codebase-memory-mcp 0.10.3 (release binary, macOS arm64 / Darwin 27.0.0).

What happened

Two PostgreSQL constructs are not handled by the vendored tree-sitter SQL grammar, and both are
ubiquitous in Supabase projects:

  1. set search_path = ... in a create function options list — flags the file parse_partial
    starting at the set line.
  2. do $$ ... $$ blocks — flags the whole block.

On a real Supabase migration set this means 39 of 39 SQL files report parse_partial, with the
damage cascading well past the offending clause: one 795-line file reports 97-674 (the entire
function body), another reports 18-226, and a 55-line do $$ migration reports 1-55 — the whole
file.

This matters disproportionately because set search_path is
Supabase's own hardening recommendation
for security definer / security invoker functions. Their linter actively tells you to add it, so
any hardened Supabase codebase will hit this on essentially every RPC.

Reproduction

Isolating the clause — identical function body each time, only the options list changes:

create or replace function public.f_v1()
returns integer
language plpgsql
<CLAUSE>
as $fn$
declare
  v_count integer;
begin
  select 1 into v_count;
  return v_count;
end
$fn$;
<CLAUSE> Result
(none) clean
volatile clean
security invoker clean
set search_path = pg_catalog parse_partial 4-4
all three combined parse_partial 6-6

The flagged range points exactly at the set line. volatile and security invoker parse fine, so
the gap is specifically the SET configuration_parameter { TO \| = } value production in
create function's options list.

Separately, a bare do $$ ... $$ block:

do $$
declare
  t text;
begin
  foreach t in array array['accounts'] loop
    execute format('alter table public.%I enable row level security', t);
  end loop;
end
$$;

parse_partial 1-3,5-6,7-7,8-9.

Both reproduced with --mode full on a fresh single-file repo, so this is not incremental-reload
staleness.

Interaction with #1287 (makes this easy to miss)

Re-indexing an already-indexed repo returns parse_partial_count: 0 while
check_index_coverage — same generation, generation_matches: true,
recording_status: complete — still reports every file partial with unchanged ranges. Node and
edge counts are byte-identical across the re-index, confirming nothing was re-parsed.

So the surface most likely to be checked after a re-index reports the index as coverage-clean while
the authoritative metadata disagrees. This is #1287, confirmed here on a release build on macOS
arm64
(the original report was dev on Linux x86_64).

Scope — what still works

Worth stating so this is triaged at the right severity. Despite the flags:

  • search_code finds identifiers inside the unparsed ranges correctly, and attributes them to the
    right containing function.
  • Top-level create function nodes still exist with accurate line spans.
  • Cross-file links from SQL to application code still resolve.

What is lost is intra-body structure: statement-level nodes, call edges between plpgsql
statements, and declare-block variables. So this degrades search_graph / trace_path inside
function bodies, not text discovery.

Relationship to existing issues

Suggested direction

Handling the SET production in the create function options list looks like the cheap, high-yield
fix — it is a small grammar addition and unblocks the majority of real Supabase migrations. Full
PL/pgSQL body parsing (a do block or dollar-quoted body as an embedded-language region) is the
larger follow-on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions