2

SQL を古い方法で変換するか、パラメーターをクエリに挿入する方法を新しい方法に変換する必要があります。この方法では、パラメーターは疑問符 (?) に置き換えられ、個別にクエリ ハンドラーに渡されます。以下の「古い」と「新しい」の例を参照してください。

さまざまなパラメーターとさまざまな数のパラメーターを持つこのような SQL ステートメントが 1200 程度ありますが、それらをすべて新しいものに変換したいと考えています。

これはカスタム パーサーを作成する必要があるものですか、それとも簡単に一括変換できるツールはありますか?

パラメーター化されていないクエリ (旧)

$product = "widget";
$price = 10.00;
$sql = "SELECT description
    FROM resource.product
    WHERE
        product.model = '" . db_input($product) . "'
        and product.price = '" . db_input($price) . "'
    ";
$result = db_query($sql);

パラメータ化されたクエリ (別名 New)

$product = "widget";
$price = 10.00;
$sql = "SELECT description
    FROM resource.product
    WHERE
        product.model = ?
        and product.price = ?
    ";
$result = db_param_query($sql, [$product, $price]);

下の 4 行で 2 つのブロックが異なることに注意してください。

4

1 に答える 1

1

必要なのはプログラム変換システム (PTS)です。PTS は、ソース コードをコンパイラ データ構造 (抽象構文ツリーまたは AST など) に解析し、必要な変更を表す変換を AST に適用し、変更された AST から有効なソース コードを再生成できるツールです。

優れた PTS では、文法を使用して変換する言語を指定でき、ソースからソースへの書き換え規則を使用してコード ツリーを変更できます。これは基本的に次の形式です。

**when** you see *this*, replace it by *that*, **if** condition(*this*)

thisthatは、変換される言語の構文を使用して記述されたパターンであり、条件は、一致したパターンを検査して追加の制約を見つけることができます。

OPの場合、私は彼がPHPを使用していると推測しています(変数名の接頭辞として「$」、連結演算子に使用される「.」)。そのため、適切な PTS と PHP の正確な文法が必要になります。

OP では、二重の文法問題があります。SQL 文字列のフラグメントを結合する PHP コードを変換するだけでなく、SQL 文字列自体も変更したいのです。おそらく彼は、PTS で SQL 文字列の断片を解析し、PHP と SQL 文字列の両方を同時に変更する変換を適用する必要があります。パラメータ間の SQL チャンクを常に表す文字列フラグメントを連結することにより、レガシー プログラムによってSQL 文字列が常にアセンブルされると仮定すると、この二重解析の問題を回避できます。

2 番目の問題は、文字列が SQL 文字列の断片を表していることを認識していることです。次のコードを検討してください。

  $A=1; $B=10;
  echo  "SELECT number from '" . $A . "' to '" . $B . "'";

これは実際の select ステートメントに非常によく似ていますが、そうではありません。このコードに変換を適用したくありません。一般に、アセンブルされた文字列が実際に SQL 文字列なのか、それとも SQL 文字列に似たものなのかを知ることはできません。それぞれが「'」で終了し、開始するすべての連結文字列は SQL 文字列であると想定します。

当社の DMS Software Reengineering Toolkit は、この問題を解決できる PTS です。利用可能な PHP 文法もあります。おおよそ次の DMS 書き換えルールが必要です。

rule fix_legacy_SQL_parameter_passing(s1: STRING, v, DOLLARVAR, s2:STRING):
          expression -> expression=
 " \s1 . db_input(\v) . \s2 " 
 -> " \concatenate3\(\allbutlastcharacter\(\s1\)\,"?"\
                     \allbutfirstcharacter\(\s2\)"
    if last_character_is(s1,"'") and first_character_is(s2,"'");

ルールfix_legacy_SQL_parameter_passingという名前で、他の多くのルールと区別できるようになっています。パラメータ s1 および s2 は、指定された (非) ターミナル タイプのサブツリーに一致するメタ変数を表します。expression->expressionは、ルールが式のみに適用されることを DMS に伝えます。

このパターン" \s1 . db_input(\v) . \s2 " です。"は、DMS 書き換えルール構文を PHP 構文から分離するメタクォートです。\s1、\v、および \s2 は\を使用してメタ変数を示します。実際のパターンは、"dbinput が介在する 2 つのリテラル文字列の連結を見つけることができる場合引数として変数名を持つ関数は...」

2 番目の->に続くのはそのパターンです。一致した文字列に対して何らかの計算を行う必要があるため、かなり複雑です。そのために、次のように記述されたメタ関数を使用します。

\fnname\( arg1 \,  arg2 \, ...  \)

一致によってパターン変数にバインドされたツリーから新しいツリーを計算します。メタ関数呼び出しの要素をターゲット言語の構文と区別するための\エスケープに注意してください。私が提案する一連のメタ関数の目的が明確であることを願っています。このルールのカスタム補助サポートとしてコーディングする必要があります。ルールは末尾の「;」で終わります。

このルールが SQL 文字列にパッチを適用して、スクオートを「?」に置き換えることは明らかです。構築された文字列で。

しかし、待ってください... db_input 変数を収集していません

これを行うには 2 つの方法があります。隠しアキュムレータ (魔法のように見えるため、ここでは示していません)、または不器用ですが、より簡単にtagを書き換えることができます。

DMSのタグは、含めたいものをすべて含むツリーです。これは通常、さらなる作業を行う意図があることを示しており、その作業を行うには追加の書き換えルールが必要になります。まず、タグ ツリーの定義を紹介します。

 pattern accumulated_db_variable( vars:expression, computed:expression) :expression = TAG;

これにより、accumulated_db_variableは 2 つの子を持つタグになり、最初は変数名のリストで、2 番目は任意の式になります。

ここで、上記のルールを修正します。

rule fix_legacy_SQL_parameter_passing(s1: STRING, v, DOLLARVAR, s2:STRING):
          expression -> expression=
 " \s1 . db_input(\v) . \s2 " 
 -> " \accumulated_dbinputs\([\v]\, 
                             \concatenate3\(\allbutlastcharacter\(\s1\)\,"?"\
                                            \allbutfirstcharacter\(\s2\)\)"
    if last_character_is(s1,"'") and first_character_is(s2,"'");

このルールは、改訂された SQL 文字列を計算しますが、その文字列で見つかった dbinput 変数のセットも計算し、このツリーのペアをタグでラップします。悪いニュースは、式の途中にタグがあることです。ただし、タグが互いに近い場合はタグを組み合わせることで、それらを取り除く追加のルールを作成できます。

 rule merge_accumulated_dbinputs(vars: element_list,
                                 v: DOLLARVAR,
                                 e: expression):
     expression -> expression =
  " \accumulated_dbinputs\([\vars]\,
                           \accumulated_db_inputs\([\v]\,e\)\)"
   -> "\accumulated_dbinputs\([vars,v]\,\e)";

OPで提案されているように、収集された変数のセットを次のステートメントに移動するルールが必要です。

 rule finalize_accumlated_dbinputs(lhs1: DOLLARVAR,
                                    vars: element_list,
                                    query: expression,
                                    lhs2: DOLLARVAR)
     statements -> statements =
  " \lhs1 = \accumulated_dbinputs\([\vars],\query);
    \lsh2 = db_param_query(\lhs1,[\vars]);

彼のコードにこれが許容する以上の可変性がある場合、彼は追加のルールを書かなければならないかもしれません。

最後に、この一連のルールをまとめて名前を付ける必要があります。

ruleset fix_legacy_SQL { fix_legacy_SQL_parameter_passing, merge_accumulated_dbinputs, finalize_accumlated_dbinputs }

これにより、ファイルに対して DMS を呼び出し、ルールセットを使い果たすまで適用するように指示できます。

この一連のルールが OP の例に対して行うべきこと [期待される出力を示しています] は、一連の手順を経てそれを変換することです。

$sql = "SELECT description
        FROM resource.product
        WHERE
           product.model = '" . db_input($product) . "'
           and product.price = '" . db_input($price) . "'
        ";
$result = db_query($sql);

-> (「変換」):

$sql =  TAG_accumulated_dbinputs([$product],
       "SELECT description
        FROM resource.product
        WHERE
           product.model = ?
           and product.price = '" . db_input($price) . "'
        ");
$result = db_query($sql);

-> (「変換」):

$sql =  TAG_accumulated_dbinputs([$product],
           TAG_accumulated_dbinputs([$price],
        "SELECT description
        FROM resource.product
        WHERE
           product.model = ?
           and product.price = ?
        "));
$result = db_query($sql);

-> (「変換」):

$sql =  TAG_accumulated_dbinputs([$product,$price],
        "SELECT description
        FROM resource.product
        WHERE
           product.model = ?
           and product.price = ?
        ");
$result = db_query($sql);

-> (「変換」):

$sql =  "SELECT description
        FROM resource.product
        WHERE
           product.model = ?
           and product.price = ?
        ";
$result = db_param_query($sql,[$product,$price]);

うふふ。テストされていませんが、これはかなり正しいと思います。

于 2016-10-12T04:13:28.420 に答える