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__i
są integer
. 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:
- Specyfikator formatu dla zmiennych całkowitych w format() dla EXECUTE?
- Funkcja zwracania dynamicznego zestawu kolumn dla danej tabeli
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ł.