如何处理更改SQL参数传递的海量代码结构转换?

时间:2016-10-11 22:04:58

标签: php replace refactoring automated-refactoring findandmodify

我需要使用旧方法转换SQL或将参数插入查询中,以新的方式将参数替换为问号(?)并分别传递给查询处理程序 - 请参阅“旧”和“新”的示例“下面。

我有1200个这样的SQL语句,带有各种参数和不同数量的参数,我想将它们全部转换为新的。

我是否需要创建自定义解析器,或者是否有工具可以让我轻松进行批量转换?

非参数化查询(又名旧查询)

$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);

参数化查询(又名新)

$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行中有两个块不同。

1 个答案:

答案 0 :(得分:1)

您需要的是Program Transformation System (PTS)。 PTS是一种可以将源代码解析为编译器数据结构(例如,抽象语法树或AST)的工具,可以将转换应用于表示所需更改的AST,然后可以从修改后的AST重新生成有效的源代码。 / p>

一个好的PTS将允许您使用语法指定要转换的语言,并允许您使用源到源重写规则对树修改进行编码,这些规则基本上是以下形式:

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

其中 是使用正在转换的语言的语法编写的模式,条件可以检查匹配的模式以获取其他约束。

在OP的情况下,我猜测他正在使用PHP(告密者:" $"作为变量名的前缀,"。"使用用于连接运算符)。所以他需要一个好的PTS和一个准确的PHP语法。

在OP中,他有一个双重语法问题:他不仅希望改变将SQL字符串片段粘合在一起的PHP代码,而且他还希望自己修改SQL字符串。可以说他需要PTS来解析SQL字符串片段,然后应用一个同时修改PHP和SQL字符串的转换。如果我们通过连接始终表示参数之间的SQL块的字符串片段来假设遗留程序始终组装SQL字符串,那么我们可以避免这种双解析问题。

第二个探测器知道字符串表示SQL字符串片段。请考虑以下代码:

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

这看起来非常像真正的select语句,但它不是;我们不想将任何转换应用于此代码。通常,您不能知道汇编的字符串实际上是一个SQL字符串,或者只是看起来像一个字符串。我们假设所有连接的字符串分别以"'"""""""""是SQL字符串。

我们的DMS软件再造工具包是一个可以解决这个问题的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&#34 ;; " 是一个metaquote,它将DMS重写规则语法与PHP语法分开。 \ s1,\ v和\ s2使用 \ 来表示一个元变量,该效果模式表示"如果你能找到两个文字字符串的串联,其中插入的dbinput函数有一个变量名作为一个论点然后......"

在第二个 - > 之后是那个模式;它非常复杂,因为我们想对匹配的字符串进行一些计算。为此,它使用元函数编写为

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

通过匹配从绑定到模式变量的树计算新树。请注意 \ 转义以区分元函数调用的元素和目标语言的语法。我希望我建议使用的元函数集的目的是明确的;它们必须被编码为此规则的自定义辅助支持。该规则以尾随&#34 ;;"结束。

应该清楚的是,此规则修补SQL字符串以替换"?"在构造的字符串中。

但是,等等,哎呀...我们没有收集db_input变量

我们可以通过两种方式做到这一点:一个隐藏的累加器(这里没有显示它看起来像魔术),或者更笨拙但更容易重写标签

DMS的标记是一个包含我们想要包含的内容的树;它通常表明我们有意做进一步的工作,我们需要额外的重写规则来完成这项工作。首先,我们介绍标记树的定义:

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

这使 cumulative_db_variable 这样的标签有两个孩子,第一个是变量名列表,第二个是任意表达式。

现在我们修改上面的规则:

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]);

Wooof。未经测试,但我认为这非常接近正确。