sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur säkert är format() för dynamiska frågor i en funktion?

Ett varningsord :denna stil med dynamisk SQL i SECURITY DEFINER funktioner kan vara eleganta och bekväma. Men överanvänd det inte. Kapsla inte flera nivåer av funktioner på detta sätt:

  • Stilen är mycket mer felbenägen än vanlig SQL.
  • Kontextväxeln med SECURITY DEFINER har en prislapp.
  • Dynamisk SQL med EXECUTE kan inte spara och återanvända frågeplaner.
  • Ingen "function inlining".
  • Och jag skulle helst inte använda det för stora frågor på stora bord alls. Den extra sofistikeringen kan vara en prestandabarriär. Som:parallellism är inaktiverat för frågeplaner på detta sätt.

Som sagt, din funktion ser bra ut, jag ser inget sätt för SQL-injektion. format() har visat sig vara bra för att sammanfoga och citera värden och identifierare för dynamisk SQL. Tvärtom kan du ta bort en del redundans för att göra det billigare.

Funktionsparametrar offset__i och limit__i är integer . SQL-injektion är omöjlig genom heltal, det finns egentligen inget behov av att citera dem (även om SQL tillåter strängkonstanter i citattecken för LIMIT och OFFSET ). Så bara:

format(' OFFSET %s LIMIT %s', offset__i, limit__i)

Dessutom, efter att ha verifierat att varje key__v är bland dina juridiska kolumnnamn - och även om de alla är lagliga kolumnnamn utan citattecken - finns det ingen anledning att köra den genom %I . Kan bara vara %s

Jag använder hellre text istället för varchar . Inte en stor sak, men text är den "föredragna" strängtypen.

Relaterat:

COST 1 verkar för lågt. Handboken:

Lämna COST om du inte vet bättre till standard 100 .

Single set-baserad operation istället för all looping

Hela loopingen kan ersättas med en enda SELECT påstående. Bör vara märkbart snabbare. Uppdrag är jämförelsevis dyra i PL/pgSQL. Så här:

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<>fiol här

Kortare, snabbare och fortfarande säker mot SQLi.

Citat läggs endast till när det är nödvändigt för syntax eller för att försvara sig mot SQL-injektion. Brinner endast till filtervärden. Kolumnnamn och operatörer verifieras mot den fasta listan över tillåtna alternativ.

Indata är json istället för jsonb . Ordningen på objekten bevaras i json , så att du kan bestämma sekvensen av kolumner i SELECT lista (vilket är meningsfullt) och WHERE förhållanden (som är rent kosmetiska). Funktionen observerar båda nu.

Utdata _result är fortfarande jsonb . Använda en OUT parameter istället för variabeln. Det är helt valfritt, bara för bekvämlighets skull. (Ingen uttrycklig RETURN uttalande krävs.)

Notera den strategiska användningen av concat() för att tyst ignorera NULL och sammanlänkningsoperatorn || så att NULL gör den sammanlänkade strängen NULL. På det här sättet, FROM , WHERE , LIMIT och OFFSET sätts endast in där det behövs. En SELECT statement fungerar utan någon av dessa. En tom SELECT list (också laglig, men jag antar att det är oönskat) resulterar i ett syntaxfel. Allt avsett.
Med format() endast för WHERE filter, för bekvämlighet och för att citera värden. Se:

Funktionen är inte STRICT längre. _limit och _offset har standardvärdet NULL , så bara den första parametern _options krävs. _limit och _offset kan vara NULL eller utelämnas, sedan tas var och en från satsen.

Använder text istället för varchar .

Gjorde konstantvariabler faktiskt CONSTANT (mest för dokumentation).

Annat än att funktionen gör vad ditt original gör.



  1. C#/NHibernate - Få de första 10 posterna sorterade efter grupperad summa

  2. Hur man inkluderar exkluderade rader i ÅTERKOMMANDE från INFOGA ... VID KONFLIKT

  3. Använda Easysoft ODBC-drivrutiner med Informatica PowerCenter

  4. konfigurera php med oracle