Zum Inhalt springen

Sicherheitsgrundlagen

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

Supabase "Could not find the function in the schema cache" nennt die Argumente, die deine App geschickt hat. Vergleich sie mit der Funktion.

Vlad Tkachenko11 Min. Lesezeit
Ein an die Wand gehängtes Formular mit beschrifteten Feldern, davor eine ausgefüllte Kopie mit einem bernsteinfarben markierten Feld.

Kurz gesagt

  • Supabase "Could not find the function in the schema cache" (PGRST202) heißt: Keine Funktion hat diesen Namen und nimmt genau die Argumentnamen an, die deine App geschickt hat. Die Namen in Klammern in der Meldung sind die, die deine App geschickt hat.
  • Prüf zuerst die Argumentnamen: userId an eine Funktion geschickt, die user_id deklariert, ein fehlendes Präfix p_ oder ein Wert, der undefined war und darum nie geschickt wurde.
  • Supabase aktualisiert seine Liste der Funktionen von selbst, sobald eine angelegt, geändert oder gelöscht wird. Prüf also die Namen und das Schema, bevor du irgendetwas neu lädst.

Dein Builder hat eine Datenbankfunktion geschrieben, die die Bestellungen eines Kunden lädt, und die Seite, die sie aufruft, bleibt leer. Die Browserkonsole zeigt das hier:

{
  "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" heißt, dass PostgREST keine Funktion mit diesem Namen hat, die genau die Argumente annimmt, die deine App geschickt hat. PostgREST ist der Teil von Supabase, der die Aufrufe supabase.rpc() deiner App beantwortet, und es führt eine eigene Liste aller Funktionen, die es aufrufen kann: den Schema-Cache.

Hier liegt der Teil, den die meisten Antworten falsch machen: Sie sagen dir, du sollst den Schema-Cache neu laden. Bei Supabase aktualisiert sich diese Liste von selbst, sobald eine Funktion angelegt oder geändert wird. Die Meldung hat dir schon gesagt, was nicht stimmt. userId in den Klammern ist das, was deine App geschickt hat. user_id im Hinweis ist das, was die Funktion erwartet.

Was bedeutet "Could not find the function in the schema cache"?

Keine Funktion in dem Schema, das PostgREST durchsucht hat, hat diesen Namen zusammen mit genau diesen Argumentnamen.

Stell dir jede Datenbankfunktion als gedrucktes Formular vor, das hinter einem Schalter an der Wand hängt: oben ihr Name, darunter ein beschriftetes Feld für jedes Argument. Wenn deine App supabase.rpc('get_orders', { userId: id }) aufruft, reicht sie eine ausgefüllte Kopie über den Schalter. PostgREST sucht ein Formular an der Wand mit demselben Namen und denselben Feldbeschriftungen. Hat keines sie, wird die Kopie am Schalter zurückgegeben, und die Meldung liest dir die Beschriftungen vor, die du ausgefüllt hast.

Vier Dinge stehen fest im eigenen Code von PostgREST:

  • der Code ist PGRST202 und der Status HTTP 404, der Status einer Seite, die es nicht gibt
  • die Klammern nennen die Argumentnamen, die deine App geschickt hat. Hat sie keine geschickt, steht an ihrer Stelle without parameters
  • public. vor dem Namen ist das Schema, das PostgREST durchsucht hat
  • der Hinweis nennt, wenn es einen gibt, die ähnlichste Funktion, die PostgREST kennt, mit deren eigenen Argumentnamen

Vergleich die Klammern mit dem Hinweis, bevor du irgendetwas anderes tust.

PostgREST ordnet einen Aufruf über den Namen der Funktion und jeden Argumentnamen zu. userId und user_id sind zwei verschiedene Beschriftungen, also passt kein Formular an der Wand.

Ursache 1: Die Argumentnamen stimmen nicht überein

PostgREST wählt eine Funktion über ihren Namen und ihre Argumentnamen zusammen aus. Eine Funktion, die mit den falschen Namen aufgerufen wird, ist für PostgREST also eine Funktion, die es nicht gibt.

Die Regeln, aus der Suche im Planer von PostgREST:

  • jedes Argument ohne Standardwert muss geschickt werden, unter seinem genauen Namen
  • ein Argument mit Standardwert darf fehlen
  • ein Name, den die Funktion nicht deklariert, schließt diese Funktion aus, auch wenn alle anderen Namen passen

Ein Name kann auf drei Arten falsch sein, die leicht zu übersehen sind. Die erste ist JavaScript, das auf SQL trifft: Deine App schreibt userId, und die Funktion deklariert user_id.

Die zweite ist ein Präfix. Manche Funktionen nennen ihre Argumente p_user_id, damit man sie nicht mit einer Spalte namens user_id verwechselt, und dann muss auch der Aufruf das p_ tragen. In einer Diskussion bei Supabase schickte der Aufruf id, nombre und drei weitere, die Funktion deklarierte p_id, p_nombre und den Rest, und der Hinweis druckte jeden der richtigen Namen.

Die dritte ist die Regel, die auch Spalten umbenennt. Ein Name, der in SQL ohne doppelte Anführungszeichen geschrieben wird, wird in Kleinbuchstaben umgewandelt, also speichert create function get_orders(userId uuid) das Argument als userid. Ein Nutzer von FlutterFlow ist genau darauf gestoßen: Der Aufruf schickte userId, und der Hinweis antwortete (userid). Die Spaltenversion dieses Fehlers hat eine Tabelle dazu, was jede Schreibweise anlegt.

Um die Namen zu sehen, die die Datenbank gespeichert hat, führ das im SQL Editor aus:

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 gibt die Argumente so aus, wie sie deklariert wurden, mit ihren Standardwerten, und zeigt damit auch, welche du weglassen darfst.

Die sicherere Lösung liegt in der App, weil sie an der Datenbank nichts ändert. Bitte deinen Builder, die Funktion mit ihren gespeicherten Namen aufzurufen: "get_orders nimmt user_id; übergib { user_id: … } in jedem Aufruf von .rpc() an diese Funktion". Das Argument umzubenennen ist schwieriger, als es aussieht. Die Dokumentation von Postgres sagt über CREATE OR REPLACE FUNCTION: "You cannot change the name already assigned to any input parameter". Die Funktion muss gelöscht und neu angelegt werden, und jede andere Seite, die sie unter dem alten Namen aufruft, scheitert danach auf dieselbe Weise.

Ursache 2: Der Wert war undefined, also wurde der Name nie geschickt

Ein Argument mit dem Wert undefined kommt nie bei PostgREST an, und die Funktion wird dann ohne dieses Argument gesucht.

supabase-js schickt deine Argumente als JSON, und JSON kennt kein undefined. Die MDN-Referenz zu JSON.stringify sagt, solche Werte "are either omitted (when found in an object) or changed to null (when found in an array)". Dieser Aufruf, gemacht, bevor der angemeldete Nutzer geladen ist:

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

schickt also {}, und die Antwort lautet Could not find the function public.get_orders without parameters in the schema cache. Bei zwei Argumenten, von denen eines undefined ist, nennen die Klammern nur das, das angekommen ist.

Der Name in deinem Code stimmt, und deshalb findet man den Fehler nicht, indem man den Code liest. Die Meldung zeigt ihn: Die Klammern sind leer oder einen Namen kürzer als das, was der Code übergibt.

Die Lösung ist, den Aufruf erst zu machen, wenn der Wert da ist. Wenn die Funktion mit einem fehlenden Wert umgehen kann, übergib bewusst null, das JSON behält.

Ein Filter macht mit demselben Wert das Gegenteil. .eq('user_id', user?.id) setzt ihn als das Wort undefined in die Anfrage, und eine uuid-Spalte antwortet mit invalid input syntax for type uuid.

JSON lässt einen Wert weg, der undefined ist. Das Argument verschwindet unterwegs, und PostgREST sucht eine Funktion ohne es.

Ursache 3: Die Funktion liegt in einem anderen Schema

supabase.rpc() sucht im Schema public, dem, das die Meldung nennt, solange du nichts anderes sagst. Eine Funktion, die woanders angelegt wurde, steht gar nicht auf der Liste von PostgREST: Es lädt Funktionen nur aus den Schemas, die dein Projekt über seine API freigibt.

Das zeigt sich auf zwei Arten:

  • die Funktion wurde in einem eigenen Schema angelegt, etwa private oder billing, und wird mit einem schlichten .rpc('get_orders') aufgerufen
  • das Schema steht im Namen, als .rpc('billing.get_orders'). PostgREST liest die ganze Zeichenkette als einen Namen und sucht ihn in public, also sagt die Meldung public.billing.get_orders

Eine Funktion in einem anderen Schema rufst du mit .schema('billing').rpc('get_orders', …) auf, und dieses Schema muss in den API-Einstellungen des Projekts unter Exposed schemas stehen, wie Supabases Anleitung zu eigenen Schemas zeigt. Bis dahin ändert sich die Antwort zu PGRST106, Invalid schema: billing, mit einem Hinweis, der die freigegebenen Schemas aufzählt.

Ein Schema so freizugeben, wie diese Anleitung es tut, öffnet jede Tabelle darin. Die Anleitung gibt anon und authenticated Zugriff auf jede Tabelle im neuen Schema, und von da an entscheiden allein deine Row Level Security-Regeln, was ein Besucher dort lesen kann. Die Tabellenversion dieses Fehlers hat die Abfrage, die vor der Freigabe zeigt, bei welchen Tabellen eines Schemas diese Regeln ausgeschaltet sind. Manche Funktionen liegen in einem privaten Schema, damit niemand sie von außen aufrufen kann: Unsere Anleitung zu unendlicher Rekursion in einer Policy legt eine genau aus diesem Grund dorthin. Ein Aufruf einer solchen Funktion aus dem Browser bekommt PGRST202 mit Absicht, und ihr Schema sollte nicht freigegeben werden.

Ursache 4: Der Name der Funktion selbst, oder gar keine Funktion

PostgREST schlägt den Namen genau nach, Buchstabe für Buchstabe, mit Groß- und Kleinschreibung.

  • ein Tippfehler, oder getOrders im Aufruf gegen get_orders in der Datenbank
  • eine Funktion, die ohne doppelte Anführungszeichen als getOrders angelegt wurde und nach derselben Regel wie ihre Argumente als getorders gespeichert ist
  • eine Prozedur. PostgREST führt nur Funktionen, also legt create procedure etwas an, das .rpc() nicht erreicht, und dasselbe gilt für eine Funktion mit returns trigger, die von einem Trigger ausgeführt wird und nie von deiner App

Auch hier hilft der Hinweis. Gibt es keine Funktion mit diesem Namen, schlägt PostgREST den nächstliegenden Namen vor, den es hat, wenn einer nah genug ist.

Oder die Funktion wurde in der Datenbank, die deine App aufruft, nie angelegt. Wenn du mit Lovable baust, such im Chat nach SQL, das du freigeben solltest. Die Supabase-Dokumentation von Lovable sagt, Lovable "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Bis du freigibst, existiert die Funktion nur im Code, der sie aufruft. Hat jemand sie von Hand angelegt, prüf, dass sie in dem Projekt gelandet ist, mit dem deine App spricht: Ein Branch ist ein eigenes Projekt mit eigener Datenbank.

Ursache 5: Der Cache ist wirklich veraltet

Bei Supabase ist das der seltene Fall. Jede Supabase-Datenbank hat einen Event Trigger, pgrst_ddl_watch, der nach jedem CREATE FUNCTION und ALTER FUNCTION die Nachricht NOTIFY pgrst, 'reload schema' schickt, und ein zweiter, pgrst_drop_watch, schickt sie, nachdem eine Funktion gelöscht wurde. Beide stehen in Supabases Datenbank-Setup.

Kommt diese Nachricht nicht an, lade die Liste im SQL Editor von Hand neu:

NOTIFY pgrst, 'reload schema';

Ändert das nichts, behandelt die Spaltenversion dieses Fehlers die nächsten zwei Schritte, die festhängende Benachrichtigungswarteschlange und den Neustart des Projekts. Beide funktionieren für Funktionen genauso.

Wenn zwei Funktionen einen Namen teilen

PostgREST rät nicht zwischen ihnen. Wenn zwei Funktionen denselben Namen haben und beide die Argumente annehmen, die deine App geschickt hat, scheitert der Aufruf mit PGRST203 und HTTP 300:

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

Eine Reparatur von PGRST202 ist ein Weg hierher. Dein Builder ändert den Typ eines Arguments mit CREATE OR REPLACE FUNCTION, und Postgres ersetzt gar nichts. Seine Dokumentation sagt, dass sich die Argumenttypen so nicht ändern lassen: "if you tried, you would actually be creating a new, distinct function". Jetzt gibt es beide Versionen nebeneinander.

Der Hinweis von PostgREST lautet hier "Try renaming the parameters or the function itself in the database so function overloading can be resolved". Die schlichtere Lösung ist, die Version zu löschen, die du nicht willst, und dabei ihren Argumenttyp zu nennen, damit Postgres weiß, welche gemeint ist:

drop function public.get_orders(text);

Prüf, welche Version deine App aufruft, bevor du eine der beiden löschst.

Wer den Typ eines Arguments mit CREATE OR REPLACE ändert, legt eine zweite Funktion mit demselben Namen an. Ein Aufruf, der zu beiden passt, wird abgelehnt.

Eine Supabase-Funktion richtig aufrufen

Übergib ein Objekt, dessen Schlüssel die Argumentnamen der Funktion sind, so geschrieben, wie die Datenbank sie gespeichert hat. In Supabases Anleitung zu Datenbankfunktionen wird eine Funktion, die als add_planet(name text) deklariert ist, mit supabase.rpc('add_planet', { name: 'Jakku' }) aufgerufen.

Sobald deine App die Funktion aufrufen kann, kann es jeder andere auch. Dieselbe Anleitung sagt es klar: "By default, any role can run a database function." Dazu gehört anon, die Rolle hinter jeder Anfrage eines Besuchers, der nicht angemeldet ist. Eine Funktion, die deine App aus dem Browser aufruft, kann jeder aufrufen, der deinen öffentlichen Schlüssel hat, mit beliebigen Argumenten.

Für eine Funktion, die nur angemeldete Nutzer ausführen sollen, folg der Anleitung: Nimm public und anon das Recht execute weg und gib es authenticated zurück.

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

Deine Tabellen sind die andere Hälfte dessen, was dieser öffentliche Schlüssel erreicht. Unser kostenloser Scan prüft von außen, ob sie jedem, der ihn hat, Zeilen herausgeben, und er braucht kein Konto.

Eine Funktion ändern, von der deine App abhängt

Behalte eine Kopie der Datenbank von vor der Änderung, und erfahre es, wenn sich etwas ändert, das deine App preisgibt.

Reeve Care bewahrt eine Kopie deiner Supabase-Datenbank auf:

  • eine verschlüsselte Kopie nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
  • die Kopie von vor deiner Änderung enthält deine Funktionen so, wie sie damals definiert waren, neben deinen Tabellen und Zeilen
  • jede Kopie wird geprüft, bevor sie zählt, indem die Zeilen in jeder Tabelle gezählt werden
  • auch deine hochgeladenen Dateien, sobald du eine Storage-Zugangsberechtigung verbindest
  • Wiederherstellung mit einem Klick, vorher wird eine Sicherheitskopie des aktuellen Stands angelegt
  • alles, was Monitor macht

Reeve Monitor prüft weiter von außen:

  • ob deine App erreichbar ist, jede Minute geprüft
  • der vollständige Sicherheitscheck jede Stunde, für bis zu drei Apps
  • eine Nachricht, wenn sich ein Ergebnis ändert, sodass ein Deploy, der ändert, was deine App preisgibt, dich erreicht, ohne dass du nachsehen musst
  • ein monatlicher Bericht in klarer Sprache über das, was er gesehen hat

Was du jetzt tun solltest

Was zu tun ist

  • Lies die Meldung: Die Namen in Klammern hat deine App geschickt, und der Hinweis nennt, was die Datenbank hat.
  • Vergleich die Argumentnamen Buchstabe für Buchstabe, samt einem Präfix wie p_. Ein Name, der in SQL ohne Anführungszeichen geschrieben wurde, wird kleingeschrieben gespeichert.
  • Sind die Klammern leer oder einen Namen zu kurz, such nach einem Wert, der beim Aufruf undefined war.
  • Prüf das Schema in der Meldung. Eine Funktion in einem anderen Schema braucht .schema('name').rpc(…) und ein freigegebenes Schema, und wer eines freigibt, macht auch seine Tabellen erreichbar.
  • Teilen sich zwei Funktionen den Namen, lösch die Version, die deine App nicht aufruft.
  • Führ NOTIFY pgrst, 'reload schema'; erst aus, wenn die Namen passen.
  • Nimm anon das Recht execute bei jeder Funktion weg, die ein nicht angemeldeter Besucher nie ausführen soll.

Fang bei den Klammern an, denn sie enthalten genau das, was deine App geschickt hat. Bevor du eine Funktion löschst oder ersetzt, von der deine App abhängt, behalte eine Kopie der Datenbank von vor der Änderung.

FAQ

Was bedeutet PGRST202?

PGRST202 ist der Code, den PostgREST, der Teil von Supabase, der Aufrufe von supabase.rpc() beantwortet, zurückgibt, wenn keine Funktion zu einem Aufruf passt. Er kommt mit HTTP 404 und der Meldung "Could not find the function public.name(arguments) in the schema cache". Die Namen in Klammern sind die Argumente, die deine App geschickt hat, und public ist das Schema, in dem gesucht wurde.

Die Funktion existiert. Warum meldet mein RPC-Aufruf, dass sie fehlt?

PostgREST erkennt eine Funktion an ihrem Namen und ihren Argumentnamen zusammen. Schick jedes Argument ohne Standardwert, unter genau dem Namen, den die Datenbank gespeichert hat, und nichts, was die Funktion nicht deklariert. Ein Name, der in SQL ohne doppelte Anführungszeichen geschrieben wurde, wird kleingeschrieben gespeichert, aus userId in einem create function wird also userid.

Warum steht in der Meldung "without parameters"?

Deine App hat überhaupt keine Argumente geschickt. Mit supabase-js ist der übliche Grund ein Wert, der beim Aufruf undefined war: Die Argumente reisen als JSON, und JSON lässt undefined-Werte weg, also kommt { user_id: undefined } als {} an. Ruf die Funktion erst auf, wenn der Wert da ist, oder übergib null, wenn die Funktion mit einem fehlenden Wert umgehen kann.

Muss die Funktion im Schema public liegen?

supabase.rpc() sucht in public, solange du mit .schema('name') kein anderes Schema wählst, und dieses Schema muss in den API-Einstellungen des Projekts unter Exposed schemas stehen. Ein freigegebenes Schema macht seinen Inhalt über die API erreichbar, also lohnt es sich, das bewusst zu entscheiden.

Wie lade ich den Supabase-Schema-Cache neu?

Führ NOTIFY pgrst, 'reload schema'; im SQL Editor aus. Supabase schickt diese Nachricht schon von selbst, sobald eine Funktion angelegt, geändert oder gelöscht wird, Neuladen hilft also nur, wenn die Nachricht nicht angekommen ist. Prüf zuerst die Namen.

Geschrieben von

Vlad Tkachenko

Gründer, Reeve

Ich schaue mir den ganzen Tag Apps an, die mit Lovable, Bolt, v0, Cursor und Replit gebaut wurden, und die kurze Liste von Fehlern, die darin immer wieder auftauchen.

Mehr über den Autor

Weiterlesen

Alle Artikel

Sichere deine Supabase-Datenbank vor der nächsten Änderung

Reeve sichert sie verschlüsselt nach Zeitplan und stellt sie für dich wieder her, wenn etwas schiefgeht.

Supabase-Backups ansehen

Automatisierte externe Prüfung, kein vollständiges Audit. Das Fehlen von Funden ist keine Garantie für Sicherheit.