2

我需要使用旧方式将 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 行中有两个块不同。

4

1 回答 1

1

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

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

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

其中thisthat是使用正在转换的语言的语法编写的模式,并且 condition 可以检查匹配的模式是否有其他约束。

在 OP 的情况下,我猜他正在使用 PHP(telltales:“$”作为变量名的前缀,“.”用于连接运算符)。所以他需要一个好的 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 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以变​​量名作为参数的函数然后..."

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

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

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

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

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

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

DMS的标签是一棵树,它包含我们希望它包含的任何内容;它通常表明我们打算做进一步的工作,我们需要额外的重写规则来完成这项工作。首先我们介绍一下标签树的定义:

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

这使得accumulated_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]);

如果他的代码的可变性超出了允许的范围,他可能必须编写额外的规则。

最后,我们需要将这组规则粘合在一起并为其命名:

规则集 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 回答