Как приблизиться к массовому преобразованию структуры кода, которое изменяет передачу параметров SQL?

Мне нужно преобразовать 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);

Параметризованные запросы (иначе 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 строках.

1 ответ

Что вам нужно, так это система трансформации программ (PTS). PTS - это инструмент, который может анализировать исходный код для структур данных компилятора (например, абстрактных синтаксических деревьев или AST), может применять преобразования к AST, которые представляют желаемые изменения, а затем может повторно генерировать действительный исходный код из модифицированных AST.

Хороший PTS позволит вам указать язык для преобразования с помощью грамматики и позволит вам кодировать модификации дерева с правилами перезаписи от источника к источнику, которые по существу имеют вид:

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

где это и это шаблоны, написанные с использованием синтаксиса преобразуемого языка, и условие может проверять сопоставленный шаблон на наличие дополнительных ограничений.

В случае с OP, я предполагаю, что он использует PHP (контрольные цифры: "$" в качестве префикса для имен переменных, "." Используется для оператора конкатенации). Поэтому ему понадобится хороший PTS и точная грамматика для PHP.

В OP у него есть проблема двойной грамматики: он хочет преобразовать не только код PHP, который склеивает фрагменты строк SQL, но он также хочет изменить сами строки SQL. Возможно, ему нужно, чтобы PTS также проанализировал фрагменты строки SQL, а затем применил преобразование, которое одновременно модифицирует строки PHP и SQL. Если мы сделаем предположение, что строки SQL всегда собираются устаревшей программой путем объединения фрагментов строк, которые всегда представляют фрагменты SQL между параметрами, то мы можем избежать этой проблемы двойного анализа.

Вторая задача - знать, что строка представляет фрагменты строки SQL. Рассмотрим следующий код:

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

Это выглядит очень похоже на реальное предложение выбора, но это не так; мы не хотим применять какие-либо преобразования к этому коду. В общем, вы не можете знать, что собранная строка действительно является строкой 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 "; " является мета-цитатой, которая отделяет синтаксис правила перезаписи DMS от синтаксиса PHP. \s1, \v и \s2 используют \ для обозначения метабарьера, в действующем паттерне говорится" если вы можете найти объединение двух литеральных строк с промежуточным входом dbinput функция, имеющая имя переменной в качестве аргумента затем..."

После второго -> это тот шаблон; это довольно сложно, потому что мы хотим сделать некоторые вычисления для соответствующих строк. Для этого используются метафункции, записанные как

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

вычислить новое дерево из деревьев, связанных с переменными шаблона путем совпадения. Обратите внимание на \ escapes, чтобы отличить элементы вызова метафункции от синтаксиса целевого языка. Я надеюсь, что цель набора метафункций, которые я предлагаю использовать, ясна; они должны быть закодированы как пользовательская вспомогательная поддержка для этого правила. Правило заканчивается трейлингом ";".

Должно быть понятно, что это правило исправляет строку SQL, заменяя символы кавычки на "?". в построенной строке.

Но, подождите, упс... мы не собирали переменные db_input.

Мы можем сделать это двумя способами: скрытый аккумулятор (здесь не показан, потому что он будет выглядеть как магия), или неуклюжий, но более простой в переписывании тег.

Тег для DMS - это дерево, которое содержит все, что мы хотим, чтобы оно содержало; обычно это означает, что у нас есть намерение продолжить работу, и для этой работы нам понадобятся дополнительные правила переписывания. Сначала введем определение дерева тегов:

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

В результате с этим параметром аккумуляторная переменная granaged_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]);

Wooof. Не проверено, но я думаю, что это довольно близко к праву.

Другие вопросы по тегам