テーブルに数式を保存し、関数でその数式を使用する


10

PostgreSQL 9.1データベースがあり、その一部がエージェントの手数料を処理しています。各エージェントには、彼らが得る手数料の計算式があります。エージェントごとのコミッションを生成する機能を持っていますが、エージェント数が増えると使えなくなります。非常に長いケースステートメントと繰り返しコードを実行することを余儀なくされたため、私の機能が非常に大きくなりました。

すべての数式には定数変数があります:

d ..その月に働いた日数
r ..獲得した新しいノード
l ..ロイヤルティスコア
s ..サブエージェント手数料
b ..基本レート
i ..獲得した収益

式は次のようになります。

d*b+(l*4+r)+(i/d)+s

各エージェントは、HR部門と支払い式を交渉します。では、式をエージェントテーブルに保存して、テーブルから式を取得して値で変換し、金額を計算するだけの小さな関数のようにできますか?

回答:


6

準備する

数式は次のようになります。

d*b+(l*4+r)+(i/d)+s

変数を$n表記法に置き換えて、plpgsqlで直接値に置き換えられるようにしますEXECUTE(以下を参照)。

$1*$5+($3*4+$2)+($6/$1)+$4

(人間の目のために)オリジナルの数式を追加で保存するか、次のような式でこのフォームを動的に生成できます。

SELECT regexp_replace(regexp_replace(regexp_replace(
       regexp_replace(regexp_replace(regexp_replace(
      'd*b+(l*4+r)+(i/d)+s'
      , '\md\M', '$1', 'g')
      , '\mr\M', '$2', 'g')
      , '\ml\M', '$3', 'g')
      , '\ms\M', '$4', 'g')
      , '\mb\M', '$5', 'g')
      , '\mi\M', '$6', 'g');

確かに、あなたの翻訳は健全です。正規表現の説明:

\ m ..単語の先頭で
のみ一致します\ M .. 単語の末尾でのみ一致します

4番目のパラメータ'g'..グローバルに置換

コア機能

CREATE OR REPLACE FUNCTION f_calc(
    d int         --  days worked that month
   ,r int         --  new nodes accuired
   ,l int         --  loyalty score
   ,s numeric     --  subagent commission
   ,b numeric     --  base rate
   ,i numeric     --  revenue gained
   ,formula text
   ,OUT result numeric
)  RETURNS numeric AS
$func$
BEGIN    
   EXECUTE 'SELECT '|| formula
   INTO   result
   USING  $1, $2, $3, $4, $5, $6;                                          
END
$func$ LANGUAGE plpgsql SECURITY DEFINER IMMUTABLE; 

コール:

SELECT f_calc(1, 2, 3, 4.1, 5.2, 6.3, '$1*$5+($3*4+$2)+($6/$1)+$4');

戻り値:

29.6000000000000000

主なポイント

  • この関数は、6つの値のパラメーターとformula text7番目のパラメーターを取ります。数式を最後に置くので、の$1 .. $6代わりに使用できます$2 .. $7。読みやすさのためだけに。
    私は適切だと思った値にデータ型を割り当てました。適切なタイプを割り当てるか(基本的な健全性チェックを実装するため)、またはすべてをすべてにしますnumeric

  • USING句を使用して動的実行の値を渡します。これにより、キャストのやり取りが回避され、すべてがシンプル、安全、高速になります。

  • OUTパラメータを使用しているのは、それがよりエレガントで、より短くて明確な構文になるためです。ファイナルRETURNは必要ありません。OUTパラメータの値が自動的に返されます。

  • @Chrisによるセキュリティに関する講義と、マニュアルの「SECURITY DEFINER関数の安全な記述の章を検討してください。私の設計では、注入の単一のポイントは式自体です。

  • 一部のパラメーターのデフォルトを使用して、呼び出しをさらに単純化できます。


5

セキュリティに関する考慮事項について、これを注意深くお読みください。基本的に、関数に任意のSQLを挿入しようとしています。したがって、非常に制限された権限を持つユーザーの下でこれを実行する必要があります。

  1. ユーザーを作成し、ユーザーからすべての権限を取り消します。これを行う場合と同じdbでpublicに権限を付与しないでください。

  2. 式を評価する関数を作成し、式を作成してsecurity definer、所有者をその制限されたユーザーに変更します。

  3. 式を前処理してから、上記で作成したeval()関数に渡します。必要であれば、別の関数でこれを行うことができます。

繰り返しになりますが、これはセキュリティに重大な影響を及ぼします。

編集:簡単なサンプルコード(テストされていませんが、ドキュメントに従っている場合はそこにアクセスできます):

CREATE OR REPLACE FUNCTION eval_numeric(text) returns numeric language plpgsql security definer immutable as
$$
declare retval numeric;
begin

execute $e$ SELECT ($1)::numeric$e$ into retval;
return retval;
end;
$$;

ALTER FUNCTION eval_numeric OWNER TO jailed_user;

CREATE OR REPLACE FUNCTION foo(expression text, a numeric, b numeric) returns numeric language sql immutable as $$
select eval(regexp_replace(regexp_replace($1, 'a', $2, 'g'), 'b', '$3', 'g'));
$$; -- can be security invoker, but eval needs to be jailed.

「セキュリティを明確にする」は本当に混乱しますが、説明できますか?
jcolebrand

1
PostgreSQLには、関数を実行できる2つのセキュリティモードがあります。SECURITY INVOKERがデフォルトです。SECURITY DEFINERは、「関数の所有者のセキュリティコンテキストで実行する」という意味で、* nixのSETUIDビットのようなものです。関数のセキュリティ定義を作成するには、これを関数宣言(CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...)で指定するか、または次のように指定できますALTER FUNCTION foo(text) SECURITY DEFINER
Chris Travers

ああ、それは特定のPGリノです。ゴッチャ。回答はバッククォートを使用する必要があった;-)
jcolebrand

@ChrisTravers私はいくつかのサンプルコードが式を評価することを期待していました。つまりa+b、テーブルのテキストタイプの列に格納されているfoo(a int, b int,formula text)場合、式がa + bである場合、関数を使用します。考えられるすべての数式について非常に長いケースステートメントを用意し、すべてのセグメントでコードを繰り返す必要がありますか?
インダゴ2013

1
@indago、私はあなたがセキュリティ上の懸念からこれを2つの層に分けたいと思います。1つは補間レイヤーです。これを行うには、PostgreSQLの正規表現を使用できます。下位レベルでは、基本的にこれをjailされたSQL関数で実行しています。ただし、これを行う場合は、セキュリティに細心の注意を払う必要があり、戻り値にも細心の注意を払う必要があります。詳細を知らなければ、samopleコードで多くを行うことは困難ですが、答えは修正されます。
Chris Travers、2013

2

式を保存してから実行する以外の方法(Chrisが述べたように、これにはセキュリティの問題があります)別個のテーブルが呼び出されformula_steps、基本的に変数と演算子、およびそれらが実行されるシーケンスが含まれます。これはもう少し作業になりますが、より安全です。テーブルは次のようになります。

formula_steps
-------------
  formula_step_id
  formula_id(エージェントテーブルによって参照されるFK)
  input_1
  input_2
  演算子(演算子記号を直接保存したくない場合は、許可された演算子のテーブルのIDにすることもできます)
  シーケンス

別のオプションは、サードパーティのライブラリ/ツールを使用して数式を評価することです。これにより、データベースがSQLインジェクションの影響を受けにくくなりますが、考えられるセキュリティの問題を外部ツールにシフトしました(まだかなり安全かもしれません)。


最後のオプションは、数式を評価するプロシージャを作成(またはダウンロード)することです。この問題には既知のアルゴリズムがあるため、オンラインで情報を見つけるのは難しくありません。


1
3番目のオプションの+1。可能性のあるすべての入力がわかっている場合は、各入力の選択をハードコードし、それらを(必要に応じて)テキストとして格納されている数式に代入してから、ライブラリルーチンを使用して演算を評価します。SQLインジェクションのリスクが排除されました。
Joel Brown、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.