PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak bezpieczne jest format() dla zapytań dynamicznych wewnątrz funkcji?

słowo ostrzeżenia :ten styl z dynamicznym SQL w SECURITY DEFINER funkcje mogą być eleganckie i wygodne. Ale nie nadużywaj tego. Nie zagnieżdżaj wielu poziomów funkcji w ten sposób:

  • Styl jest znacznie bardziej podatny na błędy niż zwykły SQL.
  • Przełącznik kontekstu z SECURITY DEFINER ma metkę z ceną.
  • Dynamiczny SQL z EXECUTE nie można zapisywać i ponownie wykorzystywać planów zapytań.
  • Brak „wbudowywania funkcji”.
  • I wolałbym w ogóle nie używać go do dużych zapytań na dużych tabelach. Dodatkowe wyrafinowanie może być barierą wydajności. Na przykład:w ten sposób równoległość jest wyłączona dla planów zapytań.

To powiedziawszy, twoja funkcja wygląda dobrze, nie widzę sposobu na wstrzyknięcie SQL. format() sprawdza się przy łączeniu i cytowaniu wartości i identyfikatorów dla dynamicznego SQL. Wręcz przeciwnie, możesz usunąć pewną nadmiarowość, aby było taniej.

Parametry funkcji offset__i i limit__iinteger . Wstrzyknięcie SQL jest niemożliwe przez liczby całkowite, tak naprawdę nie ma potrzeby ich cytowania (mimo że SQL zezwala na cytowane stałe łańcuchowe dla LIMIT i OFFSET ). Więc po prostu:

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

Ponadto po sprawdzeniu, że każdy key__v znajduje się wśród twoich legalnych nazw kolumn - i chociaż wszystkie są legalnymi, nie cytowanymi nazwami kolumn - nie ma potrzeby przechodzenia ich przez %I . Może być po prostu %s

Wolałbym używać text zamiast varchar . Nic wielkiego, ale text jest "preferowanym" typem ciągu.

Powiązane:

COST 1 wydaje się zbyt niski. Podręcznik:

Jeśli nie wiesz lepiej, zostaw COST domyślnie 100 .

Operacja oparta na pojedynczym zestawie zamiast całego zapętlenia

Całą pętlę można zastąpić jednym SELECT oświadczenie. Powinien być zauważalnie szybszy. Zadania są stosunkowo drogie w PL/pgSQL. Tak:

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 tutaj

Krótszy, szybszy i nadal bezpieczny dla SQLi.

Cytaty są dodawane tylko tam, gdzie jest to konieczne dla składni lub obrony przed wstrzyknięciem SQL. Spala się tylko do wartości filtra. Nazwy kolumn i operatory są weryfikowane na podstawie ustalonej listy dozwolonych opcji.

Wejście to json zamiast jsonb . Kolejność obiektów jest zachowana w json , dzięki czemu można określić kolejność kolumn w SELECT lista (co ma znaczenie) i WHERE warunki (co jest czysto kosmetyczne). Funkcja obserwuje teraz oba.

Wypisz _result nadal jest jsonb . Korzystanie z OUT parametr zamiast zmiennej. To całkowicie opcjonalne, tylko dla wygody. (Brak wyraźnego RETURN wymagane oświadczenie.)

Zwróć uwagę na strategiczne użycie concat() dyskretnie ignorować NULL i operator konkatenacji || tak, że NULL powoduje, że połączony łańcuch ma wartość NULL. W ten sposób FROM , WHERE , LIMIT i OFFSET są wkładane tylko tam, gdzie jest to potrzebne. SELECT oświadczenie działa bez żadnego z nich. Pusty SELECT lista (również legalna, ale przypuszczam, że niechciana) powoduje błąd składni. Wszystko zamierzone.
Korzystanie z format() tylko dla WHERE filtry, dla wygody i cytowania wartości. Zobacz:

Funkcja nie jest STRICT nie więcej. _limit i _offset mają wartość domyślną NULL , więc tylko pierwszy parametr _options jest wymagane. _limit i _offset może być NULL lub być pominięty, a następnie każdy jest usuwany z instrukcji.

Używanie text zamiast varchar .

Utworzono zmienne stałe w rzeczywistości CONSTANT (głównie dla dokumentacji).

Poza tym funkcja robi to, co oryginał.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. PostgreSQL:konwertuj ciąg szesnastkowy o bardzo dużej liczbie na NUMERYCZNĄ

  2. Postgres Zmień liczbę całkowitą kolumny na Boolean

  3. Jak wybrać id z maksymalną grupą dat według kategorii w PostgreSQL?

  4. Zapytanie rekurencyjne w PostgreSQL. WYBIERZ *

  5. Jak połączyć się z bazą danych postgres Heroku z połączenia lokalnego w php