Ich möchte einen Tabellennamen als Parameter in einer Postgres-Funktion übergeben. Ich habe diesen Code ausprobiert:
CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer
AS $$
BEGIN
IF EXISTS (select * from quote_ident($1) where quote_ident($1).id=1) THEN
return 1;
END IF;
return 0;
END;
$$ LANGUAGE plpgsql;
select some_f('table_name');
Und ich habe folgendes:
ERROR: syntax error at or near "."
LINE 4: ...elect * from quote_ident($1) where quote_ident($1).id=1)...
^
********** Error **********
ERROR: syntax error at or near "."
Und hier ist der Fehler, den ich beim Ändern erhalten habe select * from quote_ident($1) tab where tab.id=1
:
ERROR: column tab.id does not exist
LINE 1: ...T EXISTS (select * from quote_ident($1) tab where tab.id...
Funktioniert wahrscheinlich, quote_ident($1)
weil ohne den where quote_ident($1).id=1
Teil, den ich bekomme 1
, etwas ausgewählt wird. Warum darf das erste quote_ident($1)
und das zweite nicht gleichzeitig funktionieren? Und wie könnte das gelöst werden?
function
postgresql
plpgsql
dynamic-sql
identifier
John Doe
quelle
quelle
Antworten:
Dies kann weiter vereinfacht und verbessert werden:
Aufruf mit schemaqualifiziertem Namen (siehe unten):
Oder:
Hauptpunkte
Verwenden Sie einen
OUT
Parameter , um die Funktion zu vereinfachen. Sie können das Ergebnis des dynamischen SQL direkt auswählen und fertig. Keine zusätzlichen Variablen und Code erforderlich.EXISTS
macht genau das, was Sie wollen. Sie erhalten,true
ob die Zeile vorhanden ist oderfalse
nicht. Es gibt verschiedene Möglichkeiten, dies zu tun,EXISTS
ist in der Regel am effizientesten.Sie scheinen eine ganze Zahl zurück zu wollen , also habe ich das
boolean
Ergebnis vonEXISTS
bis umgewandeltinteger
, was genau das ergibt, was Sie hatten. Ich würde stattdessen boolean zurückgeben .Ich verwende den Objektkennungstyp
regclass
als Eingabetyp für_tbl
. Das macht allesquote_ident(_tbl)
oderformat('%I', _tbl)
würde es tun, aber besser, weil:.. es verhindert genauso gut die SQL-Injection .
.. es schlägt sofort und eleganter fehl, wenn der Tabellenname ungültig ist / nicht existiert / für den aktuellen Benutzer unsichtbar ist. (Ein
regclass
Parameter gilt nur für vorhandene Tabellen.).. funktioniert mit schemaqualifizierten Tabellennamen, bei denen eine einfache
quote_ident(_tbl)
oderformat(%I)
fehlschlagen würde, weil sie die Mehrdeutigkeit nicht auflösen können. Sie müssten Schema- und Tabellennamen separat übergeben und maskieren.Ich benutze immer noch
format()
, weil es die Syntax vereinfacht (und um zu demonstrieren, wie es verwendet wird), aber mit%s
statt%I
. In der Regel sind Abfragen komplexer undformat()
helfen daher mehr. Für das einfache Beispiel könnten wir auch einfach verketten:Die
id
Spalte muss nicht tabellenqualifiziert werden, solange dieFROM
Liste nur eine einzige Tabelle enthält . In diesem Beispiel ist keine Mehrdeutigkeit möglich. (Dynamische) SQL-Befehle im InnerenEXECUTE
haben einen separaten Bereich , Funktionsvariablen oder Parameter sind dort nicht sichtbar - im Gegensatz zu einfachen SQL-Befehlen im Funktionskörper.Hier ist , warum Sie immer richtig Benutzereingabe für die dynamische SQL entkommen:
db <> fiddle demonstriert hier die SQL-Injection
Old sqlfiddle
quelle
DO $$BEGIN EXECUTE 'ANALYZE mytbl'; END$$;
regclass
Werte werden bei der Ausgabe als Text automatisch maskiert.%L
wäre in diesem Fall falsch .CREATE OR REPLACE FUNCTION table_rows(_tbl regclass, OUT result integer) AS $func$ BEGIN EXECUTE 'SELECT (SELECT count(1) FROM ' || _tbl || ' )::int' INTO result; END $func$ LANGUAGE plpgsql;
Erstellen Sie eine Tabellenzeilenzahl,select table_rows('nf_part1');
Wenn überhaupt möglich, tu das nicht.
Das ist die Antwort - es ist ein Anti-Muster. Wenn der Client die Tabelle kennt, aus der er Daten erhalten möchte, dann
SELECT FROM ThatTable
. Wenn eine Datenbank so gestaltet ist, dass dies erforderlich ist, scheint sie nicht optimal gestaltet zu sein. Wenn eine Datenzugriffsschicht wissen muss, ob ein Wert in einer Tabelle vorhanden ist, ist es einfach, SQL in diesem Code zu erstellen, und das Verschieben dieses Codes in die Datenbank ist nicht gut.Für mich scheint dies die Installation eines Geräts in einem Aufzug zu sein, in dem man die Nummer der gewünschten Etage eingeben kann. Nachdem die Go-Taste gedrückt wurde, bewegt sie eine mechanische Hand auf die richtige Taste für den gewünschten Boden und drückt sie. Dies führt zu vielen potenziellen Problemen.
Bitte beachten Sie: Hier besteht keine Spottabsicht. Mein albernes Beispiel für einen Aufzug war * das beste Gerät, das ich mir vorstellen konnte *, um kurz und bündig auf Probleme mit dieser Technik hinzuweisen. Es fügt eine nutzlose Indirektionsebene hinzu und verschiebt die Auswahl von Tabellennamen aus einem Aufruferbereich (unter Verwendung eines robusten und gut verstandenen DSL, SQL) in einen Hybrid, der obskuren / bizarren serverseitigen SQL-Code verwendet.
Eine solche Aufteilung der Verantwortung durch die Verlagerung der Abfragekonstruktionslogik in dynamisches SQL erschwert das Verständnis des Codes. Es verstößt gegen eine standardmäßige und zuverlässige Konvention (wie eine SQL-Abfrage auswählt, was ausgewählt werden soll) im Namen von benutzerdefiniertem Code, der mit potenziellen Fehlern behaftet ist.
Hier finden Sie detaillierte Punkte zu einigen potenziellen Problemen bei diesem Ansatz:
Dynamic SQL bietet die Möglichkeit der SQL-Injection, die im Front-End-Code oder nur im Back-End-Code schwer zu erkennen ist (man muss sie zusammen überprüfen, um dies zu sehen).
Gespeicherte Prozeduren und Funktionen können auf Ressourcen zugreifen, für die der SP- / Funktionsbesitzer Rechte hat, der Aufrufer jedoch nicht. Soweit ich weiß, führt die Datenbank ohne besondere Sorgfalt standardmäßig, wenn Sie Code verwenden, der dynamisches SQL erzeugt und ausführt, das dynamische SQL unter den Rechten des Aufrufers aus. Dies bedeutet, dass Sie entweder überhaupt keine privilegierten Objekte verwenden können oder sie für alle Clients öffnen müssen, um die Oberfläche potenzieller Angriffe auf privilegierte Daten zu vergrößern. Das Festlegen der SP / Funktion zur Erstellungszeit, um immer als ein bestimmter Benutzer (in SQL Server
EXECUTE AS
) ausgeführt zu werden, kann dieses Problem lösen, macht die Dinge jedoch komplizierter. Dies erhöht das im vorherigen Punkt erwähnte Risiko einer SQL-Injection, indem das dynamische SQL zu einem sehr verlockenden Angriffsvektor gemacht wird.Wenn ein Entwickler verstehen muss, was der Anwendungscode tut, um ihn zu ändern oder einen Fehler zu beheben, fällt es ihm sehr schwer, die genaue SQL-Abfrage auszuführen. SQL-Profiler kann verwendet werden, dies erfordert jedoch besondere Berechtigungen und kann negative Auswirkungen auf die Leistung von Produktionssystemen haben. Die ausgeführte Abfrage kann vom SP protokolliert werden, dies erhöht jedoch die Komplexität für fragwürdige Vorteile (das Aufnehmen neuer Tabellen, das Löschen alter Daten usw.) und ist nicht offensichtlich. Tatsächlich sind einige Anwendungen so aufgebaut, dass der Entwickler nicht über Datenbankanmeldeinformationen verfügt, sodass er die übermittelte Abfrage kaum mehr sehen kann.
Wenn ein Fehler auftritt, z. B. wenn Sie versuchen, eine nicht vorhandene Tabelle auszuwählen, wird in der Datenbank eine Meldung mit dem Titel "Ungültiger Objektname" angezeigt. Das wird genauso passieren, egal ob Sie das SQL im Back-End oder in der Datenbank erstellen, aber der Unterschied ist, dass ein armer Entwickler, der versucht, das System zu beheben, eine Ebene tiefer in eine weitere Höhle unterhalb der Höhle spelunk muss Es gibt ein Problem, sich mit der Wunderprozedur zu befassen, die es tut, um herauszufinden, wo das Problem liegt. In den Protokollen wird nicht "Fehler in GetWidget", sondern "Fehler in OneProcedureToRuleThemAllRunner" angezeigt. Diese Abstraktion wird ein System im Allgemeinen verschlechtern .
Ein Beispiel in Pseudo-C # für das Umschalten von Tabellennamen basierend auf einem Parameter:
Dies beseitigt zwar nicht alle denkbaren Probleme, aber die Fehler, die ich mit der anderen Technik skizziert habe, fehlen in diesem Beispiel.
quelle
Innerhalb des plpgsql-Codes muss die EXECUTE- Anweisung für Abfragen verwendet werden, bei denen Tabellennamen oder Spalten von Variablen stammen. Auch das
IF EXISTS (<query>)
Konstrukt ist nicht erlaubt wennquery
es dynamisch generiert wird.Hier ist Ihre Funktion mit beiden Problemen behoben:
quelle
quote_ident()
weil es zusätzliche Anführungszeichen hinzufügte, was mich ein wenig überraschte , weil es in den meisten Beispielen verwendet wird.IF EXISTS <query>
Konstrukt nicht existiert? Ich bin mir ziemlich sicher, dass ich so etwas als funktionierendes Codebeispiel gesehen habe.IF EXISTS (<query>) THEN ...
ist ein perfekt gültiges Konstrukt in plpgsql. Nur nicht mit dynamischem SQL für<query>
. Ich benutze es oft. Auch diese Funktion kann erheblich verbessert werden. Ich habe eine Antwort gepostet.if exists(<query>)
, es ist im allgemeinen Fall gültig. Einfach überprüft und die Antwort entsprechend geändert.Das erste "funktioniert" nicht in dem Sinne, wie Sie es meinen, es funktioniert nur insoweit, als es keinen Fehler erzeugt.
Versuchen Sie es
SELECT * FROM quote_ident('table_that_does_not_exist');
, und Sie werden sehen, warum Ihre Funktion 1 zurückgibt: Die Auswahl gibt eine Tabelle mit einer Spalte (benanntquote_ident
) mit einer Zeile (der Variablen$1
oder in diesem speziellen Falltable_that_does_not_exist
) zurück.Was Sie tun möchten, erfordert dynamisches SQL. Dies ist eigentlich der Ort, an dem die
quote_*
Funktionen verwendet werden sollen.quelle
table_that_does_not_exist
gab das gleiche Ergebnis, Sie haben Recht.Wenn die Frage lautete, ob die Tabelle leer ist oder nicht (id = 1), finden Sie hier eine vereinfachte Version von Erwins gespeichertem Prozess:
quelle
Ich weiß, dass dies ein alter Thread ist, aber ich bin kürzlich darauf gestoßen, als ich versucht habe, dasselbe Problem zu lösen - in meinem Fall für einige ziemlich komplexe Skripte.
Es ist nicht ideal, das gesamte Skript in dynamisches SQL umzuwandeln. Es ist mühsam und fehleranfällig, und Sie verlieren die Fähigkeit zur Parametrisierung: Parameter müssen in SQL in Konstanten interpoliert werden, was sich negativ auf Leistung und Sicherheit auswirkt.
Hier ist ein einfacher Trick, mit dem Sie SQL intakt halten können, wenn Sie nur aus Ihrer Tabelle auswählen müssen - verwenden Sie dynamisches SQL, um eine temporäre Ansicht zu erstellen:
quelle
Wenn Sie möchten, dass Tabellenname, Spaltenname und Wert dynamisch als Parameter übergeben werden
Verwenden Sie diesen Code
quelle
Ich habe eine 9.4-Version von PostgreSQL und verwende immer diesen Code:
Und dann:
Es funktioniert gut für mich.
Beachtung! Das obige Beispiel ist eines davon, das zeigt: "Wie geht das nicht, wenn wir die Sicherheit beim Abfragen der Datenbank gewährleisten wollen?": P.
quelle
new
Tabelle unterscheidet sich vom Arbeiten mit dem Namen einer vorhandenen Tabelle. In beiden Fällen sollten Sie als Code ausgeführte Textparameter maskieren, oder Sie sind offen für SQL-Injection.