警告の言葉 :SECURITY DEFINERの動的SQLを使用したこのスタイル 機能はエレガントで便利です。しかし、それを使いすぎないでください。この方法で複数レベルの関数をネストしないでください:
- このスタイルは、プレーンSQLよりもはるかにエラーが発生しやすくなっています。
-
SECURITY DEFINERを使用したコンテキストスイッチ 値札があります。 -
EXECUTEを使用した動的SQL クエリプランを保存して再利用することはできません。 - 「関数のインライン化」はありません。
- そして、大きなテーブルでの大きなクエリにはまったく使用したくありません。追加された洗練度は、パフォーマンスの障壁になる可能性があります。例:この方法では、クエリプランの並列処理が無効になります。
そうは言っても、あなたの関数は見栄えが良く、SQLインジェクションの方法がわかりません。 format() 動的SQLの値と識別子を連結して引用するのに適していることが証明されています。逆に、冗長性を削除して安価にすることもできます。
関数パラメーターoffset__i およびlimit__i integerです 。整数を介したSQLインジェクションは不可能であり、実際にはそれらを引用する必要はありません(SQLではLIMITに引用符で囲まれた文字列定数が許可されていますが およびOFFSET )。だからただ:
format(' OFFSET %s LIMIT %s', offset__i, limit__i)
また、各key__v は正当な列名の1つであり、これらはすべて正当な引用符で囲まれていない列名ですが、%Iを実行する必要はありません。 。 %sにすることができます
textを使用したい varcharの代わりに 。大したことではありませんが、text は「優先」文字列タイプです。
関連:
COST 1 低すぎるようです。 マニュアル:
よくわからない場合は、COSTのままにしてください デフォルトの100 。
すべてのループではなく、単一のセットベースの操作
ループ全体を単一のSELECTに置き換えることができます 声明。著しく速くなるはずです。 PL/pgSQLでは割り当ては比較的高価です。このように:
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 こちら
より短く、より速く、SQLiに対して安全です。
引用符は、構文に必要な場合、またはSQLインジェクションを防ぐために必要な場合にのみ追加されます。フィルタ値のみにバーンダウンします。列名と演算子は、許可されたオプションのハードワイヤードリストと照合されます。
入力はjsonです jsonbの代わりに 。オブジェクトの順序はjsonで保持されます 、したがって、SELECTの列の順序を決定できます リスト(意味のあるもの)とWHERE 条件(これは純粋に表面的なものです)。関数は両方を監視します。
_resultを出力します まだjsonbです 。 OUTの使用 変数の代わりにパラメータ。便宜上、これは完全にオプションです。 (明示的なRETURNはありません ステートメントが必要です。)
concat()の戦略的な使用に注意してください NULLと連結演算子||を黙って無視します そのため、NULLは連結された文字列をNULLにします。このように、FROM 、WHERE 、LIMIT 、およびOFFSET 必要な場所にのみ挿入されます。 SELECT ステートメントは、これらのいずれも使用せずに機能します。空のSELECT list(これも合法ですが、不要だと思います)は構文エラーになります。すべて意図されています。format()の使用 WHEREの場合のみ 便宜上、値を引用するためのフィルター。参照:
関数はSTRICTではありません もう。 _limit および_offset デフォルト値はNULL 、したがって、最初のパラメータ_optionsのみ 必要とされている。 _limit および_offset NULLにすることも省略してもかまいません。その後、それぞれがステートメントから削除されます。
textの使用 varcharの代わりに 。
定数変数を実際にCONSTANTにしました (主にドキュメント用)。
それ以外は、関数は元の関数と同じように動作します。