Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 72 additions & 28 deletions Parser.php
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
<?php

namespace Intersvyaz\SqlParser;

/**
* Разбор текста запроса.
* Получение текста по имени файла sql, парсинг специальных комментариев с именами переменных вида
* /*name AND t.name = :name...
* или
* --*name AND t.name = :name
* Замена комментариев-массивов на строку переменных с уникальными именами:
* --*names AND t.name IN (:@names)
* =>
* AND t.name IN (:names_1, :names_2, :names_3, ...)
*/
class Parser
{

/**
* @var string Текст sql запроса, который надо преобразовать
*/
/** @var string Текст sql запроса, который надо преобразовать, либо имя файла с запросом */
private $sql;
/**
* @var array параметры, влияющие на парсинг sql запроса
*/
/** @var array Параметры, влияющие на парсинг sql запроса */
private $params;
/**
* @var array "упрощённый" список параметров, для кеширования
*/
/** @var array "Упрощённый" список параметров (для кеширования) */
private $simplifiedParams;

/**
Expand All @@ -34,7 +39,8 @@ public function __construct($sql, $params = [])
}

/**
* @return string готовый sql запрос
* Готовый sql запрос
* @return string
*/
public function getSql()
{
Expand All @@ -50,7 +56,8 @@ public function __toString()
}

/**
* @return array "упрощённый" список параметров
* "Упрощённый" список параметров
* @return array
*/
public function getSimplifiedParams()
{
Expand All @@ -66,7 +73,7 @@ public function getSimplifiedParams()
* @param array $params Параметры построения запроса.
* @return array
*/
private function simplifyParams($params)
private function simplifyParams(array $params)
{
if (empty($params)) {
return $params;
Expand All @@ -75,6 +82,7 @@ private function simplifyParams($params)
$newParams = [];
foreach ($params as $key => $value) {
$key = ':' . ltrim($key, ':');

if (is_array($value)) {
if (!isset($value['bind'])) {
$value['bind'] = true;
Expand All @@ -93,9 +101,8 @@ private function simplifyParams($params)
}
}
} elseif ($value['bind'] === 'tuple') {

if (isset($value[0]) && is_array($value[0])) {
//скинем индексы
// Скинем индексы
$value[0] = array_values($value[0]);

foreach ($value[0] as $valKey => $valVal) {
Expand Down Expand Up @@ -127,9 +134,17 @@ private function simplifyParams($params)
*/
private function parseSql()
{
$matches = null;

// Разбор многострочных комментариев
if (preg_match_all('#/\*([\w|]+)(.+?)\*/#s', $this->sql, $matches)) {
// ВАЖНО: "(.*?)" а не "(.+?)" на случай, если просто написали код такого типа:
// WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
// В случае "+" как комментарий будет распознана строка, включающая код:
// "*/ THEN 24 /*FLT_MANT_DIG"
// который в итоге пропадет потом из результирующей строки запроса.
if (preg_match_all('#/\*([\w|]+)(.*?)\*/#s', $this->sql, $matches)) {
$count = count($matches[0]);

for ($i = 0; $i < $count; $i++) {
$this->replaceComment($matches[0][$i], $matches[2][$i], $matches[1][$i]);
}
Expand All @@ -139,6 +154,7 @@ private function parseSql()
while (true) {
if (preg_match_all('#--\*([\w|]+)(.+)#', $this->sql, $matches)) {
$count = count($matches[0]);

for ($i = 0; $i < $count; $i++) {
$this->replaceComment($matches[0][$i], $matches[2][$i], $matches[1][$i]);
}
Expand All @@ -147,9 +163,10 @@ private function parseSql()
}
}

// разбор переменных - массивов, которые находились изначально вне комментариев
// Разбор переменных-массивов, которые находились изначально вне комментариев
if (preg_match_all('#:@(\w+)#', $this->sql, $matches)) {
$count = count($matches[0]);

for ($i = 0; $i < $count; $i++) {
$this->replaceComment($matches[0][$i], $matches[0][$i], $matches[1][$i], false);
}
Expand All @@ -159,15 +176,15 @@ private function parseSql()
}

/**
* Заменяем коментарий или некоторую другую подстроку в запросе на соответствующе преобразованный блок или удаляем,
* Заменяем комментарий или некоторую другую подстроку в запросе на соответствующе преобразованный блок или удаляем,
* если указан соответствующий параметр (делается по умолчанию - для комментариев).
* Используется также для замены параметра-массива - :@<param_name> не помещенного в комментарий, но только если такой
* параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый.
* Используется также для замены параметра-массива - :@<param_name> не помещенного в комментарий, но только если
* такой параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый.
* Либо можно переименовать функцию.
* @param string $comment Заменямый комментарий.
* @param string $comment Заменяемый комментарий.
* @param string $queryInComment Текст внутри комментария.
* @param string $paramName Имя параметра.
* @param boolean $replaceNotFoundParam заменять ли комментарий, если не нашли соответствующего параметра в списке
* @param boolean $replaceNotFoundParam Заменять ли комментарий, если не нашли соответствующего параметра в списке
*/
private function replaceComment($comment, $queryInComment, $paramName, $replaceNotFoundParam = true)
{
Expand All @@ -189,46 +206,72 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN
} elseif ($param) {
$paramName = $param[0];
$paramValue = $param[1];
if (is_array($paramValue)) {

if (is_array($paramValue) && array_key_exists('bind', $paramValue)) {
/** Пришла пара bind/value, либо просто один bind (тогда он задается = false). */
$bind = $paramValue['bind'];

if ($paramValue['bind'] !== false) {
/**
* Если указано, что надо что-то биндить, то "value" обязателен.
* Но в некоторых местах его используют неправильно для "bind" => "text".
* По-правильному надо было бы там использовать "bind" => false.
*/
$value = isset($paramValue['value']) ? $paramValue['value'] : null;
}
} elseif (is_array($paramValue)) {
/**
* Значение параметра - это массив, но почему-то без элемента "bind".
* Это случай, когда у нас элементы для списка значений IN.
* Там массив значений внутри массива - т.е. первым элементом массива является массив значений
* "связанной" переменной.
*/
$value = isset($paramValue[0]) ? $paramValue[0] : null;
$bind = isset($paramValue['bind']) ? $paramValue['bind'] : true;
$bind = true;
} else {
$value = $paramValue;
$bind = true;
}

if ($bind === true && is_array($value)) {
$valArr = [];

foreach (array_keys($value) as $keyVal) {
$valArr[] = ':' . $paramName . '_' . $keyVal;
}

$replacement = implode(',', $valArr);
$queryInComment = preg_replace('/:@' . preg_quote($paramName) . '/i', $replacement, $queryInComment);
} elseif ($bind === 'text') {
$queryInComment = preg_replace('/' . preg_quote($paramName) . '/i', $value, $queryInComment);
} elseif ($bind === 'tuple') {
if (is_array($paramValue[0])) {
$replacements = [];
//скинем индексы
// Скинем индексы
$paramValue[0] = array_values($paramValue[0]);

foreach ($paramValue[0] as $keyParam => $val) {
$name = ':' . $paramName . '_' . $keyParam;
if (is_array($val)) {
$valArr = [];

foreach (array_keys($val) as $keyVal) {
$valArr[] = $name . '_' . $keyVal;
}

$valName = implode(',', $valArr);
} else {
$valName = $name;
}

$replacements[] = '(' . $valName . ')';
}

$replacement = implode(',', $replacements);
} else {
$replacement = $paramValue;
}

$queryInComment = preg_replace('/:@' . preg_quote($paramName) . '/i', $replacement, $queryInComment);
}
} elseif ($replaceNotFoundParam) {
Expand All @@ -240,9 +283,9 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN

/**
* Ищет параметр в массиве $this->params
* @param string $name имя параметра
* @return array|bool массив ['имя_параметра_без_ведущего_двоеточия', 'значение_параметра'] или ложь если параметра
* нет
* @param string $name Имя параметра
* @return array|bool Массив ['имя_параметра_без_ведущего_двоеточия', 'значение_параметра']
* или ложь, если параметра нет
*/
private function getParam($name)
{
Expand All @@ -251,6 +294,7 @@ private function getParam($name)
// Формируем имя параметра на выход точно такое же, какое и забиндено в параметры.
foreach ($this->params as $key => $value) {
$key = ltrim($key, ':');

if (mb_strtolower($key) == $name) {
$outName = $key;
break;
Expand Down