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.

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 é
PGRST202e 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 parametersno 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.
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.
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
privateoubilling, 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 empublic, então a mensagem dizpublic.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
getOrdersna chamada contraget_ordersno banco - uma função criada como
getOrderssem aspas duplas, que fica salva comogetorderspela mesma regra dos argumentos - uma procedure. O PostgREST só lista funções, então
create procedurecria algo que o.rpc()não alcança, e o mesmo vale para uma função quereturns 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.
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
undefinedna 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
executedeanonem 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.