Замена по регулярному выражению с поиском по частям строки?

Есть строка с запросом:
SELECT * FROM {table} WHERE name="123 {qwerty} 789" AND id IN (SELECT id FROM {other_table})


Хотелось бы как-то заменять "{table}" на «prefix_table». Однако
preg_replace('/\{([\w\d_]+)\}/', 'prefix_$1', $query);


будет заменять вообще все фигурные скобки, включая "{qwerty}" в строке внутри запроса. Можно ли как-то сделать замену, не касаясь строк? Может, можно сделать проверку на четность или еще как-то?


UPD:

Кому интересно решение, я сделал так:
function ReplaceTableNames($sql)
{
	$prefix = 'prefix_';

	$escape = false;
	$quot = false;
	$table = false;
	for ($i = 0; $i < strlen($sql); $i++)
	{
		switch($sql[$i])
		{
		case '\\':
			if ($quot !== false)
				$escape = !$escape;
			break;
		case '"':
		case '\'':
			if (!$escape)
			{
				if ($quot !== false)
				{
					if ($quot == $sql[$i])
					{
						$quot = false;
					}
				}
				else
				{
					$quot = $sql[$i];
					$table = false;
				}
			}
			else
			{
				$escape = false;
			}
			break;
		case '{':
			if ($quot === false)
			{
				$table = '';
			}
			break;
		case '}':
			if ($table !== false)
			{
				$sql = substr_replace($sql, $prefix.$table, $i - strlen($table) - 1, strlen($table) + 2);
				$i += strlen($prefix) - 1;
				$table = false;
			}
			break;
		default:
			if ($table !== false)
			{
				if (preg_match('|[\w\d_\.]+|i', $sql[$i]))
				{
					$table .= $sql[$i];
				}
				else
				{
					$table = false;
				}
			}
		}
	}

	return $sql;
}


В результате — правильный парсинг строки запроса, с игнорированием экранирования строковых символов и неправильных имен таблиц.
  • Вопрос задан
  • 3155 просмотров
Решения вопроса 1
shushu
@shushu
А почему именно на регекспах?
Можно сделать на Конечных автоматах чтото типа:

define( 'STATE_QUOTE_OPEN', 1 );
define( 'STATE_QUOTE_CLOSE', 2 );

define( 'STATE_LQUOTE_OPEN', 3 );
define( 'STATE_LQUOTE_CLOSE', 4 );

define( 'STATE_BRACE_OPEN', 5 );
define( 'STATE_BRACE_CLOSE', 6 );

function parse( $str, $repl ){
    $state = 0; // default
    $buf = '';
    $brace_pos_start = 0;
    for( $i = 0; $i < strlen( $str ); $i++ ){
        switch( $str[$i] ){
            case '"':
                $state = $state == STATE_QUOTE_OPEN ? STATE_QUOTE_CLOSE : STATE_QUOTE_OPEN;
            break;
            case "'":
                $state = $state == STATE_LQUOTE_OPEN ? STATE_LQUOTE_CLOSE : STATE_LQUOTE_OPEN;
            break;
            case '{':
                if( $state != STATE_QUOTE_OPEN && $state != STATE_LQUOTE_OPEN ){
                    $state = STATE_BRACE_OPEN;
                    $buf = '';
                    $brace_pos_start = $i;
                }
            break;
            case '}':
                if( $state == STATE_BRACE_OPEN ){
                    $state = STATE_BRACE_CLOSE;
                    $str = substr_replace( $str, $repl[ $buf ], $brace_pos_start, $i - $brace_pos_start + 1 );
                    $i = $brace_pos_start + strlen( $repl[ $buf ] );
                }
            break;
            default:
                if( $state == STATE_BRACE_OPEN ) $buf .= $str[$i];
            break;
        }
    }
    return $str;
}

echo parse( 'SELECT * FROM {table} WHERE name="123 {qwerty} 789" AND id IN (SELECT id FROM {other_table})',
array( 'table' => 'tbl1', 'other_table' => 'tbl2' ) );

вывод:
SELECT * FROM tbl1 WHERE name=«123 {qwerty} 789» AND id IN (SELECT id FROM tbl2)

Сильно не пинайте, писалось на коленке в час ночи :)
Суть следующая, если открыта кавычка, то {...} не учитываем.
После замены сдвигаем курсор.
Ответ написан
Пригласить эксперта
Ответы на вопрос 5
seriyPS
@seriyPS
Вообще очень подозрительный вопрос. Вы точно уверены что оно вам надо? Мне кажется что нет)

Но если даже уверены — сами поглядите. Вам предлагают регулярку /(FROM|UPDATE|ALTER)\s+{([\w\d_]+)\}/iU а вы приводите ломающий ее пример UPDATE {table} SET `text`="пример запроса: SELECT * FROM {table}"… При том, что запросы, включающие в себя данные, уже года 4 как нормальные люди не используют. Есть же плейсхолдеры (в похапе их поддерживает как минимум PDO). Т.е. будете писать UPDATE {table} SET `text`=:text WHERE id IN( SELECT id FROM {other_table}), проводить все ваши замечательные замены и потом уже средствами PDO биндить данные к запросу.

Если такой подход чем-то не устраивает, нужно еще раз хорошенько задуматься. Если не помогло — то тогда делайте какие-то более уникальные метки для выделения имен таблиц в стиле SELECT * FROM {#$table$#}. Ну и крайний случай — пишите полноценный парсер SQL по всяким BNF правилам. Хотя тогда скорее всего просто зря потеряете кучу времени.
Ответ написан
adminimus
@adminimus
preg_replace('/\FROM\s+{([\w\d_]+)\}/i', 'FROM {prefix_$1}', $query);

чем не устраивает?
Ответ написан
@IDMan
preg_replace('/^(.*)\{([\w\d_]+)\}/iU', '$1 prefix_$2', $query);

Мы заменим только первые скобки. Но я смотрю, у вас в конце запроса есть «other_table». Его вы тоже хотите заменить?
Ответ написан
Если вам очень дорог ваш текущий подход, то используйте в запросах `{table}`, а в данных экранируйте символ ` через html-entity (да и остальные кавычки не помешает). А если без извращений, то вам выше подсказали про плейсхолдеры в PDO.

P.S. У меня лично свой велосипед, но там опять же таблицы подставляются вместе с данными, а в самом запросе никакого произвольного текста быть не может, рекомендую вам пересмотреть вашу концепцию в этом направлении.
Ответ написан
Комментировать
@runcore
если нужно просто заменять "{table}" на «prefix_table» то зачем тут регекспы?
$query = str_replace('{table}','`prefix_table`',$query);
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы