Как приблизиться к массовому преобразованию структуры кода, которое изменяет передачу параметров 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 строках.

2

Решение

То, что вам нужно, это Система трансформации программ (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-> выражение сообщает DMS, что правило применяется только к выражениям.

этот шаблон «\ s1. db_input (\ v). \ s2»; « является мета-цитатой, которая отделяет синтаксис правила перезаписи DMS от синтаксиса PHP. \ s1, \ v и \ s2 use \ для обозначения метавариабельной переменной, в действительности, шаблон говорит: «Если вы можете найти объединение двух литеральных строк с промежуточной функцией dbinput, имеющей имя переменной в качестве аргумента, то …»

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

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

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

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

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

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

тег поскольку 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]);

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

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]