Tricket med PREPARE
fungerar inte, eftersom det inte tar en * textsträng* (ett värde) som CREATE FUNCTION
gör det, men ett giltigt påstående (kod).
För att konvertera data till körbar kod du måste använda dynamisk SQL, dvs EXECUTE
i en plpgsql-funktion eller DO
påstående. Detta fungerar utan problem så länge returtypen inte beror på resultatet av den första funktionen myresult()
. Annars är du tillbaka för att fånga 22 som beskrivs i mitt tidigare svar:
- Hur man kör ett strängresultat av en lagrad procedur i postgres
Den avgörande delen är att deklarera returtypen (radtyp i det här fallet) på något sätt. Du kan skapa en TABLE
, TEMP TABLE
eller TYPE
för syftet. Eller så kan du använda ett förberett uttalande eller en refkursor.
Lösning med förberett uttalande
Du har varit väldigt nära. Den saknade pusselbiten är att förbereda den genererade frågan med dynamisk SQL .
Funktion för att förbereda uttalande dynamiskt
Skapa den här funktionen en gång . Det är en optimerad och säker version av din funktion myresult()
:
CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
RETURNS void AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
DEALLOCATE stmt_dyn;
END IF; -- you my or may not need this safety check
EXECUTE (
SELECT 'PREPARE stmt_dyn AS SELECT '
|| string_agg(quote_ident(attname), ',' ORDER BY attname)
|| ' FROM ' || _tbl
FROM pg_catalog.pg_attribute
WHERE attrelid = _tbl
AND attname LIKE _prefix || '%'
AND attnum > 0
AND NOT attisdropped
);
END
$func$ LANGUAGE plpgsql;
Jag använder regclass
för tabellnamnsparametern _tbl
för att göra det entydigt och säkert mot SQLi. Detaljer:
- Tabellnamn som en PostgreSQL-funktionsparameter
Informationsschemat inkluderar inte oid-kolumnen i systemkataloger, så jag bytte till pg_catalog.pg_attribute
istället för information_schema.columns
. Det är snabbare också. Det finns för- och nackdelar med detta:
- Hur man kontrollerar om en tabell finns i ett givet schema
Om en förberedd sats med namnet stmt_dyn
existerade redan, PREPARE
skulle ta upp ett undantag. Om det är acceptabelt, ta bort bocken i systemvyn pg_prepared_statements
och följande DEALLOCATE
.
Mer sofistikerade algoritmer är möjliga för att hantera flera förberedda satser per session, eller ta namnet på den beredda satsen som ytterligare parameter, eller till och med använda en MD5-hash av frågesträngen som namn, men det är bortom omfattningen av denna fråga.
Tänk på att PREPARE
fungerar utanför ramen för transaktioner , en gång PREPARE
lyckas, existerar det förberedda uttalandet under sessionens livstid. Om paketeringstransaktionen avbryts, PREPARE
är opåverkad. ROLLBACK
kan inte ta bort förberedda uttalanden.
Körning av dynamisk fråga
Två frågor, men bara en ringa till servern. Och mycket effektiv också.
SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;
Enklare och mycket effektivare för de flesta enkla användningsfallen än att skapa en tillfällig tabell eller en markör och välja/hämta från det (vilket skulle vara andra alternativ).
SQL-fiol.