Aller au contenu

Bases de la sécurité

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

Supabase "Could not find the function in the schema cache" cite les noms d'arguments envoyés par votre application. Comparez-les à la fonction.

Vlad Tkachenko12 min de lecture
Un formulaire imprimé épinglé au mur avec une rangée de cases légendées, et devant lui une copie remplie dont une case est éclairée en ambre.

En bref

  • Supabase "Could not find the function in the schema cache" (PGRST202) signifie qu'aucune fonction ne porte ce nom en acceptant exactement les noms d'arguments que votre application a envoyés. Les noms entre parenthèses dans le message sont ceux que votre application a envoyés.
  • Vérifiez d'abord les noms d'arguments : userId envoyé à une fonction qui déclare user_id, un préfixe p_ oublié, ou une valeur restée undefined et donc jamais envoyée.
  • Supabase met à jour sa liste de fonctions tout seul dès qu'une fonction est créée, modifiée ou supprimée. Vérifiez donc les noms et le schéma avant de recharger quoi que ce soit.

Votre builder a écrit une fonction de base de données qui charge les commandes d'un client, et la page qui l'appelle reste vide. La console du navigateur affiche ceci :

{
  "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" signifie que PostgREST n'a aucune fonction de ce nom qui accepte exactement les arguments envoyés par votre application. PostgREST est la partie de Supabase qui répond aux appels supabase.rpc() de votre application, et il tient sa propre liste de toutes les fonctions qu'il peut appeler, appelée cache de schéma.

Voici le point sur lequel la plupart des réponses se trompent : elles vous disent de recharger le cache de schéma. Sur Supabase, cette liste se met à jour toute seule dès qu'une fonction est créée ou modifiée. Le message vous a déjà dit ce qui ne va pas. userId, entre parenthèses, est ce que votre application a envoyé. user_id, dans l'indice, est ce que la fonction attend.

Que signifie "Could not find the function in the schema cache" ?

Aucune fonction du schéma que PostgREST a parcouru ne porte ce nom avec exactement ces noms d'arguments.

Imaginez chaque fonction de la base comme un formulaire imprimé épinglé derrière un guichet : son nom en haut, et une case légendée par argument. Quand votre application appelle supabase.rpc('get_orders', { userId: id }), elle tend une copie remplie. PostgREST cherche au mur un formulaire qui a le même nom et les mêmes légendes. Si aucun ne les a, la copie est refusée au guichet, et le message vous relit les légendes que vous avez remplies.

Quatre détails sont fixés dans le code de PostgREST :

  • le code est PGRST202 et le statut HTTP 404, celui d'une page qui n'existe pas
  • les parenthèses listent les noms d'arguments que votre application a envoyés. Quand elle n'en a envoyé aucun, le message dit without parameters à leur place
  • public. devant le nom est le schéma que PostgREST a parcouru
  • l'indice, quand il y en a un, nomme la fonction la plus proche que PostgREST connaît, avec ses propres noms d'arguments

Comparez les parenthèses avec l'indice avant toute autre chose.

PostgREST rapproche un appel par le nom de la fonction et chaque nom d'argument. userId et user_id sont deux légendes différentes, donc aucun formulaire du mur ne correspond.

Cause 1 : les noms d'arguments ne correspondent pas

PostgREST choisit une fonction par son nom et ses noms d'arguments pris ensemble. Une fonction appelée avec les mauvais noms est donc, pour PostgREST, une fonction qui n'existe pas.

Les règles, tirées de la recherche dans le planificateur de PostgREST :

  • chaque argument sans valeur par défaut doit être envoyé, sous son nom exact
  • un argument qui a une valeur par défaut peut être omis
  • un nom que la fonction ne déclare pas l'écarte, même quand tous les autres noms correspondent

Un nom peut être faux de trois façons faciles à manquer. La première, c'est JavaScript qui rencontre SQL : votre application écrit userId et la fonction déclare user_id.

La deuxième est un préfixe. Certaines fonctions nomment leurs arguments p_user_id, pour qu'on ne les confonde pas avec une colonne appelée user_id, et l'appel doit alors porter le p_ lui aussi. Dans une discussion chez Supabase, l'appel envoyait id, nombre et trois autres, la fonction déclarait p_id, p_nombre et le reste, et l'indice affichait chacun des bons noms.

La troisième est la règle qui renomme aussi les colonnes. Un nom tapé en SQL sans guillemets doubles est ramené en minuscules, donc create function get_orders(userId uuid) enregistre l'argument sous le nom userid. Un utilisateur de FlutterFlow est tombé exactement là-dessus : l'appel envoyait userId, et l'indice répondait (userid). La version colonne de cette erreur a un tableau de ce que crée chaque graphie.

Pour voir les noms que la base a enregistrés, exécutez ceci dans le 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 affiche les arguments tels qu'ils ont été déclarés, avec leurs valeurs par défaut, ce qui montre aussi lesquels vous pouvez omettre.

La correction la plus sûre se fait dans l'application, parce qu'elle ne change rien dans la base. Demandez à votre builder d'appeler la fonction avec ses noms enregistrés : "get_orders prend user_id ; passez { user_id: … } dans chaque appel .rpc() vers elle". Renommer l'argument est plus difficile qu'il n'y paraît. La documentation de Postgres dit de CREATE OR REPLACE FUNCTION : "You cannot change the name already assigned to any input parameter". Il faut supprimer la fonction et la recréer, et chaque autre écran qui l'appelle sous l'ancien nom échoue alors de la même façon.

Cause 2 : la valeur était undefined, donc le nom n'est jamais parti

Un argument dont la valeur est undefined n'arrive jamais à PostgREST, et la fonction est alors cherchée sans lui.

supabase-js envoie vos arguments en JSON, et JSON n'a pas de undefined. La référence MDN de JSON.stringify dit que ces valeurs "are either omitted (when found in an object) or changed to null (when found in an array)". Cet appel, fait avant que l'utilisateur connecté soit chargé :

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

envoie donc {}, et la réponse est Could not find the function public.get_orders without parameters in the schema cache. Avec deux arguments dont l'un est undefined, les parenthèses ne listent que celui qui est arrivé.

Le nom dans votre code est juste, et c'est pourquoi lire le code ne trouve rien. Le message, lui, le montre : les parenthèses sont vides, ou il leur manque un nom par rapport à ce que le code passe.

La correction consiste à faire l'appel une fois la valeur disponible. Quand la fonction est écrite pour gérer une valeur absente, passez null exprès, que JSON conserve.

Un filtre fait l'inverse avec la même valeur. .eq('user_id', user?.id) la place dans la requête sous la forme du mot undefined, et une colonne uuid répond invalid input syntax for type uuid.

JSON laisse de côté une valeur undefined. L'argument disparaît en route, et PostgREST cherche une fonction sans lui.

Cause 3 : la fonction est dans un autre schéma

supabase.rpc() cherche dans le schéma public, celui que nomme le message, sauf si vous lui dites autre chose. Une fonction créée ailleurs ne figure pas du tout sur la liste de PostgREST : il ne charge les fonctions que depuis les schémas que votre projet expose par son API.

Cela se présente sous deux formes :

  • la fonction a été créée dans un schéma à elle, comme private ou billing, et elle est appelée avec un simple .rpc('get_orders')
  • le schéma est écrit dans le nom, sous la forme .rpc('billing.get_orders'). PostgREST lit toute la chaîne comme un seul nom et le cherche dans public, donc le message dit public.billing.get_orders

Une fonction d'un autre schéma s'appelle avec .schema('billing').rpc('get_orders', …), et ce schéma doit figurer sous Exposed schemas dans les paramètres d'API du projet, comme le montre le guide de Supabase sur les schémas personnalisés. Tant que ce n'est pas le cas, la réponse devient PGRST106, Invalid schema: billing, avec un indice qui liste les schémas exposés.

Exposer un schéma comme le fait ce guide ouvre chaque table qu'il contient. Le guide donne à anon et à authenticated l'accès à chaque table du nouveau schéma, et dès lors seules vos règles de Row Level Security décident de ce qu'un visiteur peut y lire. La version table de cette erreur donne la requête qui liste les tables d'un schéma où ces règles sont désactivées, à lancer avant de l'exposer. Certaines fonctions sont rangées dans un schéma privé pour que personne ne puisse les appeler de l'extérieur : notre guide sur la récursion infinie dans une policy en place une là précisément pour cette raison. Un appel à une telle fonction depuis le navigateur reçoit PGRST202 à dessein, et son schéma doit rester non exposé.

Cause 4 : le nom de la fonction elle-même, ou aucune fonction

PostgREST cherche le nom exactement, lettre pour lettre, majuscules comprises.

  • une faute de frappe, ou getOrders dans l'appel face à get_orders dans la base
  • une fonction créée sous le nom getOrders sans guillemets doubles, enregistrée comme getorders par la même règle que ses arguments
  • une procédure. PostgREST ne liste que des fonctions, donc create procedure crée quelque chose que .rpc() ne peut pas atteindre, et il en va de même d'une fonction qui returns trigger, exécutée par un trigger et jamais par votre application

L'indice aide ici aussi. Quand aucune fonction de ce nom n'existe, PostgREST suggère le nom le plus proche qu'il connaît, s'il y en a un d'assez proche.

Ou bien la fonction n'a jamais été créée dans la base que votre application appelle. Si vous construisez avec Lovable, remontez le chat à la recherche d'un SQL qu'on vous a demandé d'approuver. La documentation Supabase de Lovable dit qu'il "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Tant que vous n'avez pas approuvé, la fonction n'existe que dans le code qui l'appelle. Si quelqu'un l'a créée à la main, vérifiez qu'elle est allée dans le projet auquel votre application parle : une branche est un projet à part, avec sa propre base.

Cause 5 : le cache est vraiment périmé

Sur Supabase, c'est le cas rare. Chaque base Supabase a un event trigger, pgrst_ddl_watch, qui envoie NOTIFY pgrst, 'reload schema' après chaque CREATE FUNCTION et ALTER FUNCTION, et un second, pgrst_drop_watch, l'envoie après la suppression d'une fonction. Les deux figurent dans la mise en place de la base par Supabase.

Quand ce message n'arrive pas, rechargez la liste à la main dans le SQL Editor :

NOTIFY pgrst, 'reload schema';

Si rien ne change, la version colonne de cette erreur couvre les deux étapes suivantes, la file de notifications bloquée et le redémarrage du projet. Les deux marchent de la même façon pour les fonctions.

Quand deux fonctions partagent un nom

PostgREST ne choisit pas au hasard entre elles. Quand deux fonctions ont le même nom et acceptent toutes deux les arguments envoyés par votre application, l'appel échoue avec PGRST203 et HTTP 300 :

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

Corriger un PGRST202 est une façon d'en arriver là. Votre builder change le type d'un argument avec CREATE OR REPLACE FUNCTION, et Postgres ne remplace rien. Sa documentation dit que les types d'arguments ne peuvent pas être changés ainsi : "if you tried, you would actually be creating a new, distinct function". Les deux versions existent désormais côte à côte.

L'indice de PostgREST pour ce cas dit "Try renaming the parameters or the function itself in the database so function overloading can be resolved". La correction la plus simple est de supprimer la version dont vous ne voulez pas, en nommant le type de son argument pour que Postgres sache de laquelle il s'agit :

drop function public.get_orders(text);

Vérifiez quelle version votre application appelle avant de supprimer l'une ou l'autre.

Changer le type d'un argument avec CREATE OR REPLACE ajoute une seconde fonction du même nom. Un appel qui convient aux deux est refusé.

Appeler correctement une fonction Supabase

Passez un objet dont les clés sont les noms d'arguments de la fonction, écrits comme la base les a enregistrés. Dans le guide de Supabase sur les fonctions de base de données, une fonction déclarée add_planet(name text) s'appelle avec supabase.rpc('add_planet', { name: 'Jakku' }).

Dès que votre application peut appeler la fonction, n'importe qui d'autre le peut aussi. Le même guide le dit clairement : "By default, any role can run a database function." Cela inclut anon, le rôle derrière chaque requête d'un visiteur non connecté. Une fonction que votre application appelle depuis le navigateur peut être appelée par quiconque possède votre clé publique, avec les arguments de son choix.

Pour une fonction que seuls les utilisateurs connectés doivent exécuter, suivez le guide : retirez execute à public et à anon, et rendez-le à authenticated.

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

Vos tables sont l'autre moitié de ce que cette clé publique atteint. Notre scan gratuit vérifie depuis l'extérieur si elles livrent des lignes à quiconque la détient, et il ne demande aucun compte.

Modifier une fonction dont votre application dépend

Gardez une copie de la base d'avant la modification, et soyez prévenu quand quelque chose que votre application expose change.

Reeve Care garde une copie de votre base Supabase :

  • une copie chiffrée selon un calendrier, sur le stockage propre de Reeve dans l'UE, en dehors de votre compte Supabase
  • la copie d'avant votre modification contient vos fonctions telles qu'elles étaient définies alors, à côté de vos tables et de vos lignes
  • chaque copie vérifiée avant de compter, en comptant les lignes de chaque table
  • vos fichiers envoyés aussi, dès que vous connectez un identifiant Storage
  • une restauration en un clic, avec une copie de sécurité de l'état actuel prise d'abord
  • tout ce que fait Monitor

Reeve Monitor continue de vérifier depuis l'extérieur :

  • si votre application répond, vérifié chaque minute
  • le contrôle de sécurité complet toutes les heures, sur trois applications au plus
  • un message quand un résultat change, pour qu'un déploiement qui modifie ce que votre application expose vous parvienne sans que vous alliez voir
  • un rapport mensuel, en langage clair, de ce qu'il a vu

Que faire maintenant

Que faire

  • Lisez le message : les noms entre parenthèses sont ce que votre application a envoyé, et l'indice nomme ce que la base possède.
  • Comparez les noms d'arguments lettre pour lettre, préfixe p_ compris. Un nom tapé en SQL sans guillemets est enregistré en minuscules.
  • Si les parenthèses sont vides ou qu'il manque un nom, cherchez une valeur restée undefined au moment de l'appel.
  • Vérifiez le schéma dans le message. Une fonction d'un autre schéma demande .schema('nom').rpc(…) et un schéma exposé, et exposer un schéma rend aussi ses tables accessibles.
  • Si deux fonctions partagent le nom, supprimez la version que votre application n'appelle pas.
  • N'exécutez NOTIFY pgrst, 'reload schema'; qu'une fois les noms en accord.
  • Retirez execute à anon sur toute fonction qu'un visiteur non connecté ne doit jamais exécuter.

Commencez par les parenthèses, parce qu'elles contiennent exactement ce que votre application a envoyé. Avant de supprimer ou de remplacer une fonction dont votre application dépend, gardez une copie de la base d'avant la modification.

FAQ

Que signifie PGRST202 ?

PGRST202 est le code que PostgREST, la partie de Supabase qui répond aux appels supabase.rpc(), renvoie quand aucune fonction ne correspond à un appel. Il arrive avec HTTP 404 et le message "Could not find the function public.name(arguments) in the schema cache". Les noms entre parenthèses sont les arguments que votre application a envoyés, et public est le schéma où la recherche a eu lieu.

La fonction existe. Pourquoi mon appel RPC dit-il qu'elle manque ?

PostgREST reconnaît une fonction à son nom et à ses noms d'arguments pris ensemble. Envoyez chaque argument qui n'a pas de valeur par défaut, sous le nom exact que la base a enregistré, et rien que la fonction ne déclare pas. Un nom écrit en SQL sans guillemets doubles est enregistré en minuscules, donc userId dans un create function devient userid.

Pourquoi le message dit-il "without parameters" ?

Votre application n'a envoyé aucun argument. Avec supabase-js, la raison habituelle est une valeur restée undefined au moment de l'appel : les arguments voyagent en JSON, qui laisse de côté les valeurs undefined, donc { user_id: undefined } arrive sous la forme {}. Faites l'appel une fois la valeur disponible, ou passez null si la fonction est écrite pour gérer une valeur absente.

La fonction doit-elle être dans le schéma public ?

supabase.rpc() cherche dans public, sauf si vous choisissez un autre schéma avec .schema('nom'), et ce schéma doit figurer sous Exposed schemas dans les paramètres d'API du projet. Exposer un schéma rend son contenu accessible par l'API, alors mieux vaut le décider en connaissance de cause.

Comment recharger le cache de schéma de Supabase ?

Exécutez NOTIFY pgrst, 'reload schema'; dans le SQL Editor. Supabase envoie déjà ce message tout seul dès qu'une fonction est créée, modifiée ou supprimée, donc un rechargement n'aide que si le message n'est pas arrivé. Vérifiez d'abord les noms.

Écrit par

Vlad Tkachenko

Fondateur de Reeve

Je passe mes journées à examiner des applications créées avec Lovable, Bolt, v0, Cursor et Replit, et la courte liste d'erreurs qui y reviennent sans cesse.

En savoir plus sur l'auteur

À lire ensuite

Tous les articles

Sauvegardez votre base Supabase avant la prochaine modification

Reeve en garde une copie chiffrée à intervalles réguliers et la restaure pour vous en cas de problème.

Voir les sauvegardes Supabase

Vérification externe automatisée, pas un audit complet. L'absence de problème n'est pas une garantie de sécurité.