Vai al contenuto

Basi della sicurezza

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

Supabase "Could not find the function in the schema cache" riporta i nomi degli argomenti inviati dalla tua app. Confrontali con la funzione.

Vlad Tkachenko12 min di lettura
Un modulo stampato appeso al muro con una fila di caselle etichettate, e davanti una copia compilata con una casella illuminata in ambra.

In breve

  • Supabase "Could not find the function in the schema cache" (PGRST202) significa che nessuna funzione ha quel nome e accetta esattamente i nomi degli argomenti inviati dalla tua app. I nomi tra parentesi nel messaggio sono quelli che ha inviato la tua app.
  • Controlla prima i nomi degli argomenti: userId inviato a una funzione che dichiara user_id, un prefisso p_ mancante, o un valore rimasto undefined e quindi mai inviato.
  • Supabase aggiorna da solo il suo elenco di funzioni ogni volta che se ne crea, modifica o elimina una, quindi controlla i nomi e lo schema prima di ricaricare qualsiasi cosa.

Il tuo builder ha scritto una funzione del database che carica gli ordini di un cliente, e la pagina che la chiama resta vuota. La console del browser mostra questo:

{
  "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 che PostgREST non ha nessuna funzione con quel nome che accetti esattamente gli argomenti inviati dalla tua app. PostgREST è la parte di Supabase che risponde alle chiamate supabase.rpc() della tua app, e tiene un proprio elenco di tutte le funzioni che può chiamare, chiamato schema cache.

Ecco la parte che quasi tutte le risposte sbagliano: ti dicono di ricaricare lo schema cache. Su Supabase quell'elenco si aggiorna da solo ogni volta che una funzione viene creata o modificata. Il messaggio ti ha già detto che cosa non va. userId, tra parentesi, è quello che ha inviato la tua app. user_id, nel suggerimento, è quello che la funzione si aspetta.

Che cosa significa "Could not find the function in the schema cache"?

Che nessuna funzione nello schema in cui ha cercato PostgREST ha quel nome insieme a esattamente quei nomi di argomenti.

Pensa a ogni funzione del database come a un modulo stampato appeso dietro uno sportello: il suo nome in alto e una casella etichettata per ogni argomento. Quando la tua app chiama supabase.rpc('get_orders', { userId: id }), consegna una copia compilata. PostgREST cerca sul muro un modulo con lo stesso nome e le stesse etichette. Se nessuno le ha, la copia viene respinta allo sportello, e il messaggio ti rilegge le etichette che hai compilato.

Quattro dettagli sono fissati nel codice di PostgREST:

  • il codice è PGRST202 e lo stato HTTP 404, quello di una pagina che non esiste
  • le parentesi elencano i nomi degli argomenti che ha inviato la tua app. Quando non ne ha inviato nessuno, al loro posto il messaggio dice without parameters
  • public. davanti al nome è lo schema in cui ha cercato PostgREST
  • il suggerimento, quando c'è, nomina la funzione più vicina che PostgREST conosce, con i nomi degli argomenti di quella funzione

Confronta le parentesi con il suggerimento prima di qualsiasi altra cosa.

PostgREST abbina una chiamata per il nome della funzione e ogni nome di argomento. userId e user_id sono due etichette diverse, quindi nessun modulo sul muro corrisponde.

Causa 1: i nomi degli argomenti non corrispondono

PostgREST sceglie una funzione dal suo nome e dai nomi dei suoi argomenti insieme, quindi una funzione chiamata con i nomi sbagliati è, per PostgREST, una funzione che non esiste.

Le regole, dalla ricerca nel planner di PostgREST:

  • ogni argomento senza valore predefinito va inviato, con il suo nome esatto
  • un argomento con un valore predefinito si può tralasciare
  • un nome che la funzione non dichiara la esclude, anche quando tutti gli altri nomi corrispondono

Un nome può essere sbagliato in tre modi facili da non notare. Il primo è JavaScript che incontra SQL: la tua app scrive userId e la funzione dichiara user_id.

Il secondo è un prefisso. Alcune funzioni chiamano i loro argomenti p_user_id, così non si confondono con una colonna chiamata user_id, e allora anche la chiamata deve portare il p_. In una discussione su Supabase la chiamata inviava id, nombre e altri tre, la funzione dichiarava p_id, p_nombre e il resto, e il suggerimento stampava tutti i nomi giusti.

Il terzo è la regola che rinomina anche le colonne. Un nome scritto in SQL senza virgolette doppie viene portato in minuscolo, quindi create function get_orders(userId uuid) salva l'argomento come userid. Un utente di FlutterFlow ci è finito proprio così: la chiamata inviava userId e il suggerimento rispondeva (userid). La versione per le colonne di questo errore ha una tabella di quello che crea ogni grafia.

Per vedere i nomi che il database ha salvato, esegui questo nel 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 stampa gli argomenti come sono stati dichiarati, con i loro valori predefiniti, quindi mostra anche quali puoi tralasciare.

La correzione più sicura è nell'app, perché non cambia niente nel database. Chiedi al tuo builder di chiamare la funzione con i nomi salvati: "get_orders prende user_id; passa { user_id: … } in ogni chiamata .rpc() a questa funzione". Rinominare l'argomento è più difficile di quanto sembri. La documentazione di Postgres dice di CREATE OR REPLACE FUNCTION: "You cannot change the name already assigned to any input parameter". La funzione va eliminata e ricreata, e ogni altra schermata che la chiama con il vecchio nome poi fallisce allo stesso modo.

Causa 2: il valore era undefined, quindi il nome non è mai partito

Un argomento il cui valore è undefined non arriva mai a PostgREST, e la funzione viene cercata senza di esso.

supabase-js invia i tuoi argomenti come JSON, e JSON non ha undefined. Il riferimento MDN su JSON.stringify dice che questi valori "are either omitted (when found in an object) or changed to null (when found in an array)". Quindi questa chiamata, fatta prima che l'utente connesso sia caricato:

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

invia {}, e la risposta è Could not find the function public.get_orders without parameters in the schema cache. Con due argomenti di cui uno undefined, le parentesi elencano solo quello che è arrivato.

Il nome nel tuo codice è giusto, ed è per questo che leggere il codice non lo trova. Il messaggio sì: le parentesi sono vuote, oppure manca un nome rispetto a quello che passa il codice.

La correzione è fare la chiamata quando il valore c'è. Se la funzione è scritta per gestire un valore mancante, passa null apposta, che JSON conserva.

Un filtro fa il contrario con lo stesso valore. .eq('user_id', user?.id) lo mette nella richiesta come la parola undefined, e una colonna uuid risponde invalid input syntax for type uuid.

JSON lascia fuori un valore undefined, quindi l'argomento sparisce per strada e PostgREST cerca una funzione senza di esso.

Causa 3: la funzione è in un altro schema

supabase.rpc() cerca nello schema public, quello che nomina il messaggio, a meno che tu non gli dica altro. Una funzione creata altrove non è proprio nell'elenco di PostgREST: carica le funzioni solo dagli schemi che il tuo progetto espone tramite la sua API.

Si presenta in due forme:

  • la funzione è stata creata in uno schema tutto suo, come private o billing, e viene chiamata con un semplice .rpc('get_orders')
  • lo schema è scritto nel nome, come .rpc('billing.get_orders'). PostgREST legge l'intera stringa come un unico nome e lo cerca in public, quindi il messaggio dice public.billing.get_orders

Una funzione in un altro schema si chiama con .schema('billing').rpc('get_orders', …), e quello schema deve comparire in Exposed schemas nelle impostazioni API del progetto, come mostra la guida di Supabase sugli schemi personalizzati. Finché non c'è, la risposta diventa PGRST106, Invalid schema: billing, con un suggerimento che elenca gli schemi esposti.

Esporre uno schema come fa quella guida apre ogni tabella che contiene. La guida dà ad anon e ad authenticated l'accesso a ogni tabella del nuovo schema, e da quel momento solo le tue regole di Row Level Security decidono che cosa può leggere lì un visitatore. La versione per tabelle di questo errore ha la query che elenca le tabelle di uno schema con quelle regole spente, da eseguire prima di esporlo. Alcune funzioni stanno in uno schema privato proprio perché nessuno possa chiamarle dall'esterno: la nostra guida alla ricorsione infinita in una policy ne mette una lì esattamente per questo. Una chiamata dal browser a una funzione così riceve PGRST202 di proposito, e il suo schema deve restare non esposto.

Causa 4: il nome della funzione stessa, o nessuna funzione

PostgREST cerca il nome in modo esatto, lettera per lettera e con maiuscole e minuscole.

  • un refuso, oppure getOrders nella chiamata contro get_orders nel database
  • una funzione creata come getOrders senza virgolette doppie, che viene salvata come getorders per la stessa regola dei suoi argomenti
  • una procedura. PostgREST elenca solo funzioni, quindi create procedure crea qualcosa che .rpc() non può raggiungere, e lo stesso vale per una funzione che returns trigger, che viene eseguita da un trigger e mai dalla tua app

Anche qui il suggerimento aiuta. Quando non esiste nessuna funzione con quel nome, PostgREST suggerisce il nome più vicino che ha, se uno è abbastanza simile.

Oppure la funzione non è mai stata creata nel database che la tua app chiama. Se costruisci con Lovable, ripercorri la chat cercando SQL che ti è stato chiesto di approvare. La documentazione Supabase di Lovable dice che "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Finché non approvi, la funzione esiste solo nel codice che la chiama. Se qualcuno l'ha creata a mano, controlla che sia finita nel progetto con cui parla la tua app: un branch è un progetto a parte con il suo database.

Causa 5: la cache è davvero vecchia

Su Supabase è il caso raro. Ogni database Supabase ha un event trigger, pgrst_ddl_watch, che invia NOTIFY pgrst, 'reload schema' dopo ogni CREATE FUNCTION e ALTER FUNCTION, e un secondo, pgrst_drop_watch, lo invia dopo che una funzione è stata eliminata. Si trovano entrambi nella configurazione del database di Supabase.

Quando quel messaggio non arriva, ricarica l'elenco a mano nel SQL Editor:

NOTIFY pgrst, 'reload schema';

Se non cambia niente, la versione per le colonne di questo errore copre i due passi successivi, la coda di notifiche bloccata e il riavvio del progetto. Valgono allo stesso modo per le funzioni.

Quando due funzioni condividono un nome

PostgREST non tira a indovinare tra le due. Quando due funzioni hanno lo stesso nome ed entrambe accettano gli argomenti inviati dalla tua app, la chiamata fallisce con PGRST203 e HTTP 300:

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

Correggere un PGRST202 è uno dei modi per arrivarci. Il tuo builder cambia il tipo di un argomento con CREATE OR REPLACE FUNCTION, e Postgres non sostituisce niente. La sua documentazione dice che i tipi degli argomenti non si possono cambiare così: "if you tried, you would actually be creating a new, distinct function". Adesso esistono entrambe le versioni, una accanto all'altra.

Il suggerimento di PostgREST per questo caso dice "Try renaming the parameters or the function itself in the database so function overloading can be resolved". La correzione più semplice è eliminare la versione che non vuoi, indicando il tipo del suo argomento perché Postgres sappia di quale si tratta:

drop function public.get_orders(text);

Controlla quale versione chiama la tua app prima di eliminarne una.

Cambiare il tipo di un argomento con CREATE OR REPLACE aggiunge una seconda funzione con lo stesso nome. Una chiamata che va bene per entrambe viene respinta.

Chiamare nel modo giusto una funzione Supabase

Passa un oggetto le cui chiavi sono i nomi degli argomenti della funzione, scritti come li ha salvati il database. Nella guida di Supabase alle funzioni del database, una funzione dichiarata come add_planet(name text) si chiama con supabase.rpc('add_planet', { name: 'Jakku' }).

Appena la tua app può chiamare la funzione, può farlo anche chiunque altro. La stessa guida lo dice chiaramente: "By default, any role can run a database function." Questo include anon, il ruolo dietro ogni richiesta di un visitatore che non ha fatto l'accesso. Una funzione che la tua app chiama dal browser può chiamarla chiunque abbia la tua chiave pubblica, con gli argomenti che preferisce.

Per una funzione che devono eseguire solo gli utenti connessi, segui la guida: togli execute a public e ad anon, e restituiscilo ad authenticated.

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

Le tue tabelle sono l'altra metà di quello che raggiunge quella chiave pubblica. La nostra scansione gratuita controlla dall'esterno se consegnano righe a chiunque la possieda, e non chiede un account.

Modificare una funzione da cui dipende la tua app

Tieni una copia del database da prima della modifica, e vieni a saperlo quando cambia qualcosa che la tua app espone.

Reeve Care tiene una copia del tuo database Supabase:

  • una copia cifrata secondo una pianificazione, sullo spazio di archiviazione di Reeve nell'UE, fuori dal tuo account Supabase
  • la copia da prima della tua modifica contiene le tue funzioni come erano definite allora, accanto a tabelle e righe
  • ogni copia verificata prima di contare, contando le righe di ogni tabella
  • anche i file caricati, appena colleghi una credenziale Storage
  • un ripristino con un clic, con una copia di sicurezza dello stato attuale presa prima
  • tutto quello che fa Monitor

Reeve Monitor continua a controllare dall'esterno:

  • se la tua app è raggiungibile, controllato ogni minuto
  • il controllo di sicurezza completo ogni ora, su un massimo di tre app
  • un messaggio quando un risultato cambia, così un deploy che cambia ciò che la tua app espone ti raggiunge senza che tu debba andare a guardare
  • un report mensile, in linguaggio semplice, di quello che ha visto

Che cosa fare adesso

Cosa fare

  • Leggi il messaggio: i nomi tra parentesi sono quelli che ha inviato la tua app, e il suggerimento nomina quello che ha il database.
  • Confronta i nomi degli argomenti lettera per lettera, prefisso p_ compreso. Un nome scritto in SQL senza virgolette viene salvato in minuscolo.
  • Se le parentesi sono vuote o manca un nome, cerca un valore rimasto undefined al momento della chiamata.
  • Controlla lo schema nel messaggio. Una funzione in un altro schema richiede .schema('nome').rpc(…) e uno schema esposto, ed esporne uno rende raggiungibili anche le sue tabelle.
  • Se due funzioni condividono il nome, elimina la versione che la tua app non chiama.
  • Esegui NOTIFY pgrst, 'reload schema'; solo quando i nomi corrispondono.
  • Togli execute ad anon su qualsiasi funzione che un visitatore non connesso non deve mai eseguire.

Parti dalle parentesi, perché contengono esattamente quello che ha inviato la tua app. Prima di eliminare o sostituire una funzione da cui dipende la tua app, tieni una copia del database da prima della modifica.

FAQ

Che cosa significa PGRST202?

PGRST202 è il codice che PostgREST, la parte di Supabase che risponde alle chiamate supabase.rpc(), restituisce quando nessuna funzione corrisponde a una chiamata. Arriva con HTTP 404 e il messaggio "Could not find the function public.name(arguments) in the schema cache". I nomi tra parentesi sono gli argomenti che ha inviato la tua app, e public è lo schema in cui ha cercato.

La funzione esiste. Perché la mia chiamata RPC dice che manca?

PostgREST riconosce una funzione dal suo nome e dai nomi dei suoi argomenti insieme. Invia ogni argomento senza valore predefinito, esattamente con il nome che il database ha salvato, e niente che la funzione non dichiari. Un nome scritto in SQL senza virgolette doppie viene salvato in minuscolo, quindi userId in un create function diventa userid.

Perché l'errore dice "without parameters"?

La tua app non ha inviato nessun argomento. Con supabase-js il motivo abituale è un valore rimasto undefined al momento della chiamata: gli argomenti viaggiano come JSON, che lascia fuori i valori undefined, quindi { user_id: undefined } arriva come {}. Fai la chiamata quando il valore c'è, oppure passa null se la funzione è scritta per gestire un valore mancante.

La funzione deve stare nello schema public?

supabase.rpc() cerca in public a meno che tu non scelga un altro schema con .schema('nome'), e quello schema deve comparire in Exposed schemas nelle impostazioni API del progetto. Esporre uno schema rende raggiungibile dall'API quello che contiene, quindi conviene deciderlo apposta.

Come ricarico lo schema cache di Supabase?

Esegui NOTIFY pgrst, 'reload schema'; nel SQL Editor. Supabase invia già quel messaggio da solo ogni volta che una funzione viene creata, modificata o eliminata, quindi ricaricare aiuta solo quando il messaggio non è arrivato. Controlla prima i nomi.

Scritto da

Vlad Tkachenko

Fondatore di Reeve

Passo le giornate a guardare app costruite con Lovable, Bolt, v0, Cursor e Replit, e la breve lista di errori che vi ricompaiono di continuo.

Altro sull'autore

Da leggere dopo

Tutti gli articoli

Fai il backup del database Supabase prima della prossima modifica

Reeve ne tiene una copia cifrata a intervalli programmati e la ripristina per te se qualcosa va storto.

Vedi i backup Supabase

Controllo esterno automatizzato, non un audit completo. L'assenza di risultati non è una garanzia di sicurezza.