Pular para o conteúdo

Noções de segurança

Supabase PGRST202: "Could not find the function in the schema cache"

Supabase "Could not find the function in the schema cache" mostra os nomes de argumentos que seu app enviou. Compare-os com a função.

Vlad Tkachenko12 min de leitura
Um formulário impresso pregado na parede com uma fileira de campos rotulados e, na frente, uma cópia preenchida com um campo aceso em âmbar.

Em resumo

  • Supabase "Could not find the function in the schema cache" (PGRST202) significa que nenhuma função tem esse nome e aceita exatamente os nomes de argumentos que seu app enviou. Os nomes entre parênteses na mensagem são os que seu app enviou.
  • Confira primeiro os nomes dos argumentos: userId enviado para uma função que declara user_id, um prefixo p_ que faltou, ou um valor que estava undefined e por isso nunca foi enviado.
  • O Supabase atualiza sozinho a lista de funções sempre que uma é criada, alterada ou apagada, então confira os nomes e o schema antes de recarregar qualquer coisa.

Seu builder escreveu uma função no banco para carregar os pedidos de um cliente, e a página que a chama fica vazia. O console do navegador mostra isto:

{
  "code": "PGRST202",
  "details": "Searched for the function public.get_orders with parameter userId or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.",
  "hint": "Perhaps you meant to call the function public.get_orders(user_id)",
  "message": "Could not find the function public.get_orders(userId) in the schema cache"
}

Supabase "Could not find the function in the schema cache" significa que o PostgREST não tem nenhuma função com esse nome que aceite exatamente os argumentos que seu app enviou. O PostgREST é a parte do Supabase que responde às chamadas supabase.rpc() do seu app, e ele mantém a própria lista de todas as funções que pode chamar, chamada schema cache.

Aqui está a parte que quase toda resposta erra: ela manda você recarregar o schema cache. No Supabase essa lista se atualiza sozinha sempre que uma função é criada ou alterada. A mensagem já disse o que está errado. userId, entre parênteses, é o que seu app enviou. user_id, na dica, é o que a função espera.

O que significa "Could not find the function in the schema cache"?

Que nenhuma função no schema onde o PostgREST procurou tem esse nome junto com exatamente esses nomes de argumentos.

Pense em cada função do banco como um formulário impresso pregado atrás de um balcão: o nome dela no alto e um campo rotulado para cada argumento. Quando seu app chama supabase.rpc('get_orders', { userId: id }), ele entrega uma cópia preenchida. O PostgREST procura na parede um formulário com o mesmo nome e os mesmos rótulos. Se nenhum tiver, a cópia é recusada no balcão, e a mensagem lê de volta os rótulos que você preencheu.

Quatro detalhes estão fixos no próprio código do PostgREST:

  • o código é PGRST202 e o status é HTTP 404, o de uma página que não existe
  • os parênteses listam os nomes de argumentos que seu app enviou. Quando ele não enviou nenhum, a mensagem diz without parameters no lugar
  • public. antes do nome é o schema onde o PostgREST procurou
  • a dica, quando existe, nomeia a função mais parecida que o PostgREST conhece, com os nomes de argumentos dela

Compare os parênteses com a dica antes de qualquer outra coisa.

O PostgREST casa uma chamada pelo nome da função e por cada nome de argumento. userId e user_id são dois rótulos diferentes, então nenhum formulário da parede bate.

Causa 1: os nomes dos argumentos não batem

O PostgREST escolhe uma função pelo nome e pelos nomes dos argumentos juntos, então uma função chamada com os nomes errados é, para o PostgREST, uma função que não existe.

As regras, tiradas da busca no planejador do PostgREST:

  • todo argumento sem valor padrão precisa ser enviado, com o nome exato
  • um argumento com valor padrão pode ficar de fora
  • um nome que a função não declara a descarta, mesmo quando todos os outros nomes batem

Um nome pode estar errado de três jeitos fáceis de deixar passar. O primeiro é o JavaScript encontrando o SQL: seu app escreve userId e a função declara user_id.

O segundo é um prefixo. Algumas funções chamam os argumentos de p_user_id, para não serem confundidos com uma coluna chamada user_id, e aí a chamada também precisa levar o p_. Numa discussão do Supabase a chamada enviava id, nombre e mais três, a função declarava p_id, p_nombre e o resto, e a dica imprimiu cada um dos nomes certos.

O terceiro é a regra que também renomeia colunas. Um nome digitado em SQL sem aspas duplas vai para minúsculas, então create function get_orders(userId uuid) salva o argumento como userid. Um usuário do FlutterFlow caiu exatamente nisso: a chamada enviava userId, e a dica respondeu (userid). A versão deste erro para colunas tem uma tabela do que cada grafia cria.

Para ver os nomes que o banco salvou, rode isto no SQL Editor:

select p.proname, pg_get_function_arguments(p.oid)
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.proname = 'get_orders';

pg_get_function_arguments mostra os argumentos como foram declarados, com os valores padrão, então também mostra quais você pode deixar de fora.

A correção mais segura fica no app, porque não muda nada no banco. Peça ao seu builder para chamar a função pelos nomes salvos: "get_orders recebe user_id; passe { user_id: … } em toda chamada .rpc() para ela". Renomear o argumento é mais difícil do que parece. A documentação do Postgres diz sobre CREATE OR REPLACE FUNCTION: "You cannot change the name already assigned to any input parameter". A função precisa ser apagada e criada de novo, e qualquer outra tela que a chame pelo nome antigo passa a falhar do mesmo jeito.

Causa 2: o valor era undefined, então o nome nunca foi enviado

Um argumento cujo valor é undefined nunca chega ao PostgREST, e a função passa a ser procurada sem ele.

O supabase-js envia seus argumentos como JSON, e JSON não tem undefined. A referência do MDN sobre JSON.stringify diz que esses valores "are either omitted (when found in an object) or changed to null (when found in an array)". Então esta chamada, feita antes de o usuário logado carregar:

await supabase.rpc('get_orders', { user_id: user?.id })

envia {}, e a resposta é Could not find the function public.get_orders without parameters in the schema cache. Com dois argumentos e um deles undefined, os parênteses listam só o que chegou.

O nome no seu código está certo, e é por isso que ler o código não encontra o problema. A mensagem encontra: os parênteses estão vazios, ou têm um nome a menos do que o código passa.

A correção é fazer a chamada quando o valor já existir. Quando a função foi escrita para lidar com um valor ausente, passe null de propósito, que o JSON mantém.

Um filtro faz o contrário com o mesmo valor. .eq('user_id', user?.id) o coloca na requisição como a palavra undefined, e uma coluna uuid responde invalid input syntax for type uuid.

O JSON deixa de fora um valor undefined, então o argumento some no caminho e o PostgREST procura uma função sem ele.

Causa 3: a função está em outro schema

O supabase.rpc() procura no schema public, o que a mensagem cita, a não ser que você diga outra coisa. Uma função criada em outro lugar nem está na lista do PostgREST: ele carrega funções só dos schemas que o seu projeto expõe pela API.

Isso aparece de dois jeitos:

  • a função foi criada num schema próprio, como private ou billing, e é chamada com um simples .rpc('get_orders')
  • o schema está escrito no nome, como .rpc('billing.get_orders'). O PostgREST lê a string inteira como um nome só e procura em public, então a mensagem diz public.billing.get_orders

Uma função de outro schema é chamada com .schema('billing').rpc('get_orders', …), e esse schema precisa estar em Exposed schemas, nas configurações de API do projeto, como mostra o guia do Supabase sobre schemas próprios. Até lá a resposta muda para PGRST106, Invalid schema: billing, com uma dica que lista os schemas expostos.

Expor um schema do jeito que esse guia faz abre todas as tabelas dele. O guia dá a anon e authenticated acesso a todas as tabelas do schema novo, e a partir daí só as suas regras de Row Level Security decidem o que um visitante pode ler ali. A versão deste erro para tabelas tem a consulta que lista quais tabelas de um schema estão com essas regras desligadas, para rodar antes de expor o schema. Algumas funções ficam num schema privado justamente para ninguém chamá-las de fora: nosso guia sobre recursão infinita numa policy coloca uma ali exatamente por isso. Uma chamada a uma função assim pelo navegador recebe PGRST202 de propósito, e o schema dela deve continuar sem ser exposto.

Causa 4: o nome da própria função, ou função nenhuma

O PostgREST procura o nome de forma exata, letra por letra, com maiúsculas e minúsculas.

  • um erro de digitação, ou getOrders na chamada contra get_orders no banco
  • uma função criada como getOrders sem aspas duplas, que fica salva como getorders pela mesma regra dos argumentos
  • uma procedure. O PostgREST só lista funções, então create procedure cria algo que o .rpc() não alcança, e o mesmo vale para uma função que returns trigger, que roda a partir de um trigger e nunca a partir do seu app

A dica ajuda aqui também. Quando não existe função com esse nome, o PostgREST sugere o nome mais próximo que tem, se algum for parecido o bastante.

Ou a função nunca foi criada no banco que o seu app chama. Se você constrói com o Lovable, volte pelo chat procurando um SQL que pediram para você aprovar. A documentação do Lovable sobre o Supabase diz que ele "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Até você aprovar, a função só existe no código que a chama. Se alguém criou na mão, confira se ela foi para o projeto com que o seu app conversa: uma branch é um projeto separado com banco próprio.

Causa 5: o cache está mesmo desatualizado

No Supabase este é o caso raro. Todo banco do Supabase tem um event trigger, pgrst_ddl_watch, que manda NOTIFY pgrst, 'reload schema' depois de cada CREATE FUNCTION e ALTER FUNCTION, e um segundo, pgrst_drop_watch, manda a mesma mensagem quando uma função é apagada. Os dois estão na configuração de banco do Supabase.

Quando essa mensagem não chega, recarregue a lista na mão no SQL Editor:

NOTIFY pgrst, 'reload schema';

Se nada mudar, a versão deste erro para colunas cobre os dois passos seguintes, a fila de notificações travada e o reinício do projeto. Os dois funcionam do mesmo jeito para funções.

Quando duas funções dividem um nome

O PostgREST não chuta entre elas. Quando duas funções têm o mesmo nome e as duas aceitam os argumentos que seu app enviou, a chamada falha com PGRST203 e HTTP 300:

Could not choose the best candidate function between: public.get_orders(user_id => uuid), public.get_orders(user_id => text)

Corrigir um PGRST202 é um jeito de chegar aqui. Seu builder muda o tipo de um argumento com CREATE OR REPLACE FUNCTION, e o Postgres não substitui nada. A documentação dele diz que os tipos dos argumentos não podem ser mudados assim: "if you tried, you would actually be creating a new, distinct function". Agora as duas versões existem lado a lado.

A dica do PostgREST para este caso diz "Try renaming the parameters or the function itself in the database so function overloading can be resolved". A correção mais simples é apagar a versão que você não quer, dizendo o tipo do argumento dela para o Postgres saber de qual se trata:

drop function public.get_orders(text);

Confira qual versão o seu app chama antes de apagar qualquer uma das duas.

Mudar o tipo de um argumento com CREATE OR REPLACE cria uma segunda função com o mesmo nome. Uma chamada que serve para as duas é recusada.

Como chamar uma função do Supabase do jeito certo

Passe um objeto cujas chaves são os nomes dos argumentos da função, escritos como o banco os salvou. No guia do Supabase sobre funções de banco, uma função declarada como add_planet(name text) é chamada com supabase.rpc('add_planet', { name: 'Jakku' }).

Assim que seu app consegue chamar a função, qualquer outra pessoa também consegue. O mesmo guia diz isso com todas as letras: "By default, any role can run a database function." Isso inclui anon, o papel por trás de cada requisição de um visitante que não fez login. Uma função que seu app chama pelo navegador pode ser chamada por qualquer pessoa que tenha sua chave pública, com os argumentos que quiser.

Para uma função que só usuários logados devem executar, siga o guia: tire o execute de public e de anon, e devolva para authenticated.

revoke execute on function public.get_orders from public, anon;
grant execute on function public.get_orders to authenticated;

Suas tabelas são a outra metade do que essa chave pública alcança. Nossa varredura gratuita verifica de fora se elas entregam linhas para quem tem a chave, e não pede conta.

Mudar uma função da qual seu app depende

Guarde uma cópia do banco de antes da mudança e fique sabendo quando mudar algo que seu app expõe.

O Reeve Care guarda uma cópia do seu banco do Supabase:

  • uma cópia criptografada num cronograma, no armazenamento próprio da Reeve na UE, fora da sua conta do Supabase
  • a cópia de antes da sua mudança tem suas funções como estavam definidas na época, ao lado das tabelas e linhas
  • cada cópia verificada antes de contar, contando as linhas de cada tabela
  • também seus arquivos enviados, assim que você conecta uma credencial do Storage
  • restauração com um clique, com uma cópia de segurança do estado atual feita antes
  • tudo o que o Monitor faz

O Reeve Monitor continua verificando de fora:

  • se o seu app está no ar, verificado a cada minuto
  • a verificação de segurança completa a cada hora, em até três apps
  • uma mensagem quando um resultado muda, para que um deploy que muda o que seu app expõe chegue até você sem você ir olhar
  • um relatório mensal, em linguagem simples, do que ele viu

O que fazer agora

O que fazer

  • Leia a mensagem: os nomes entre parênteses são o que seu app enviou, e a dica nomeia o que o banco tem.
  • Compare os nomes dos argumentos letra por letra, incluindo um prefixo p_. Um nome digitado em SQL sem aspas fica salvo em minúsculas.
  • Se os parênteses estiverem vazios ou com um nome a menos, procure um valor que estava undefined na hora da chamada.
  • Confira o schema na mensagem. Uma função em outro schema precisa de .schema('nome').rpc(…) e de um schema exposto, e expor um deixa as tabelas dele acessíveis também.
  • Se duas funções dividem o nome, apague a versão que seu app não chama.
  • Rode NOTIFY pgrst, 'reload schema'; só depois que os nomes baterem.
  • Tire o execute de anon em qualquer função que um visitante sem login nunca deva rodar.

Comece pelos parênteses, porque eles têm exatamente o que seu app enviou. Antes de apagar ou substituir uma função da qual seu app depende, guarde uma cópia do banco de antes da mudança.

Perguntas frequentes

O que significa PGRST202?

PGRST202 é o código que o PostgREST, a parte do Supabase que responde às chamadas supabase.rpc(), devolve quando nenhuma função corresponde a uma chamada. Ele vem com HTTP 404 e a mensagem "Could not find the function public.name(arguments) in the schema cache". Os nomes entre parênteses são os argumentos que seu app enviou, e public é o schema onde ele procurou.

A função existe. Por que minha chamada RPC diz que ela não existe?

O PostgREST reconhece uma função pelo nome e pelos nomes dos argumentos juntos. Envie todo argumento que não tem valor padrão, exatamente com o nome que o banco salvou, e nada que a função não declare. Um nome escrito em SQL sem aspas duplas é salvo em minúsculas, então userId num create function vira userid.

Por que o erro diz "without parameters"?

Seu app não enviou nenhum argumento. Com o supabase-js o motivo comum é um valor que estava undefined na hora da chamada: os argumentos viajam como JSON, que deixa de fora os valores undefined, então { user_id: undefined } chega como {}. Faça a chamada quando o valor existir, ou passe null se a função foi escrita para lidar com um valor ausente.

A função precisa estar no schema public?

O supabase.rpc() procura em public, a não ser que você escolha outro schema com .schema('nome'), e esse schema precisa estar em Exposed schemas, nas configurações de API do projeto. Expor um schema deixa o que está nele acessível pela API, então vale decidir isso de propósito.

Como recarrego o schema cache do Supabase?

Rode NOTIFY pgrst, 'reload schema'; no SQL Editor. O Supabase já manda essa mensagem sozinho sempre que uma função é criada, alterada ou apagada, então recarregar só ajuda quando a mensagem não chegou. Confira os nomes primeiro.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Faça backup do seu banco Supabase antes da próxima mudança

O Reeve guarda uma cópia criptografada em horários programados e a restaura para você se algo quebrar.

Ver backups do Supabase

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.