警告の言葉 :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
にしました (主にドキュメント用)。
それ以外は、関数は元の関数と同じように動作します。