Et ord til advarsel :denne stil med dynamisk SQL i SECURITY DEFINER
funktioner kan være elegante og bekvemme. Men overbrug det ikke. Indlejr ikke flere niveauer af funktioner på denne måde:
- Stilen er meget mere udsat for fejl end almindelig SQL.
- Kontekstskiftet med
SECURITY DEFINER
har et prisskilt. - Dynamisk SQL med
EXECUTE
kan ikke gemme og genbruge forespørgselsplaner. - Ingen "funktion inlining".
- Og jeg vil hellere slet ikke bruge det til store forespørgsler på store borde. Den ekstra sofistikering kan være en præstationsbarriere. Ligesom:parallelisme er deaktiveret for forespørgselsplaner på denne måde.
Når det er sagt, så ser din funktion godt ud, jeg ser ingen mulighed for SQL-injektion. format() har vist sig at være god til at sammenkæde og citere værdier og identifikatorer for dynamisk SQL. Tværtimod kan du måske fjerne noget redundans for at gøre det billigere.
Funktionsparametre offset__i
og limit__i
er integer
. SQL-injektion er umulig gennem heltal, der er virkelig ingen grund til at citere dem (selvom SQL tillader citerede strengkonstanter for LIMIT
og OFFSET
). Så bare:
format(' OFFSET %s LIMIT %s', offset__i, limit__i)
Også efter at have verificeret, at hver key__v
er blandt dine juridiske kolonnenavne - og selvom det alle er lovlige kolonnenavne uden anførselstegn - er det ikke nødvendigt at køre det gennem %I
. Kan bare være %s
Jeg vil hellere bruge text
i stedet for varchar
. Ikke en big deal, men text
er den "foretrukne" strengtype.
Relateret:
- Formatspecifikation for heltalsvariabler i format() for EXECUTE?
- Funktion til at returnere dynamisk sæt af kolonner for en given tabel
COST 1
synes for lavt. Manualen:
Medmindre du ved bedre, så lad COST
stå ved standard 100
.
Enkelt sæt-baseret betjening i stedet for al looping
Hele loopingen kan erstattes med en enkelt SELECT
udmelding. Skal være mærkbart hurtigere. Opgaver er forholdsvis dyre i PL/pgSQL. Sådan:
CREATE OR REPLACE FUNCTION goods__list_json (_options json, _limit int = NULL, _offset int = NULL, OUT _result jsonb)
RETURNS jsonb
LANGUAGE plpgsql SECURITY DEFINER AS
$func$
DECLARE
_tbl CONSTANT text := 'public.goods_full';
_cols CONSTANT text[] := '{id, id__category, category, name, barcode, price, stock, sale, purchase}';
_oper CONSTANT text[] := '{<, >, <=, >=, =, <>, LIKE, "NOT LIKE", ILIKE, "NOT ILIKE", BETWEEN, "NOT BETWEEN"}';
_sql text;
BEGIN
SELECT concat('SELECT jsonb_agg(t) FROM ('
, 'SELECT ' || string_agg(t.col, ', ' ORDER BY ord) FILTER (WHERE t.arr->>0 = 'true')
-- ORDER BY to preserve order of objects in input
, ' FROM ' || _tbl
, ' WHERE ' || string_agg (
CASE WHEN (t.arr->>1)::int BETWEEN 1 AND 10 THEN
format('%s %s %L' , t.col, _oper[(arr->>1)::int], t.arr->>2)
WHEN (t.arr->>1)::int BETWEEN 11 AND 12 THEN
format('%s %s %L AND %L', t.col, _oper[(arr->>1)::int], t.arr->>2, t.arr->>3)
-- ELSE NULL -- = default - or raise exception for illegal operator index?
END
, ' AND ' ORDER BY ord) -- ORDER BY only cosmetic
, ' OFFSET ' || _offset -- SQLi-safe, no quotes required
, ' LIMIT ' || _limit -- SQLi-safe, no quotes required
, ') t'
)
FROM json_each(_options) WITH ORDINALITY t(col, arr, ord)
WHERE t.col = ANY(_cols) -- only allowed column names - or raise exception for illegal column?
INTO _sql;
IF _sql IS NULL THEN
RAISE EXCEPTION 'Invalid input resulted in empty SQL string! Input: %', _options;
END IF;
RAISE NOTICE 'SQL: %', _sql;
EXECUTE _sql INTO _result;
END
$func$;
db<>fiddle her
Kortere, hurtigere og stadig sikker mod SQLi.
Citater tilføjes kun, hvor det er nødvendigt for syntaks eller for at forsvare sig mod SQL-injektion. Brænder kun ned til filterværdier. Kolonnenavne og operatører verificeres i forhold til den fastkablede liste over tilladte muligheder.
Input er json
i stedet for jsonb
. Rækkefølgen af objekter er bevaret i json
, så du kan bestemme rækkefølgen af kolonner i SELECT
liste (som er meningsfuld) og WHERE
forhold (som er rent kosmetisk). Funktionen observerer begge nu.
Output _result
er stadig jsonb
. Brug af en OUT
parameter i stedet for variablen. Det er helt valgfrit, bare for nemheds skyld. (Ingen eksplicit RETURN
erklæring påkrævet.)
Bemærk den strategiske brug af concat()
for lydløst at ignorere NULL og sammenkædningsoperatoren ||
så NULL gør den sammenkædede streng til NULL. På denne måde FROM
, WHERE
, LIMIT
, og OFFSET
indsættes kun hvor det er nødvendigt. En SELECT
statement fungerer uden nogen af disse. En tom SELECT
liste (også lovlig, men jeg formoder uønsket) resulterer i en syntaksfejl. Alt tilsigtet.
Ved brug af format()
kun for WHERE
filtre, for nemheds skyld og for at citere værdier. Se:
Funktionen er ikke STRICT
længere. _limit
og _offset
har standardværdien NULL
, så kun den første parameter _options
er påkrævet. _limit
og _offset
kan være NULL eller udeladt, så fjernes hver enkelt fra sætningen.
Brug af text
i stedet for varchar
.
Lavede faktiske konstante variabler CONSTANT
(mest til dokumentation).
Bortset fra det gør funktionen, hvad din original gør.