Поиск SQL запросов в тексте

PassExcel

Новичок
Поставлена задача, написать код, который будет искать в файлах sql запросы.
Для простоты, пусть это будут mysql запрос вида

SELECT
UPDATE
INSERT

Конструкции могут быть конечно разными
SELECT title, subtitle, body, DATE_FORMAT(post_date,'%M %e, %Y') as vdate FROM news WHERE news.id =2
SELECT 1;
INSERT INTO `news` VALUES ('1', 'content', 'admin')

и т.д.

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

Вурдалак

Продвинутый новичок
Это будет стоить дорого. Главное, что непонятно зачем оно надо.
 

freeek

Новичок
использовать всеобъемлющие регулярные выражения для каждого вида запросов
все по стандарту sql

да, согласен с тем, что это гемор и требуется достаточное время для написания чего то работающего
 

Вурдалак

Продвинутый новичок
Тут нужен автомат. Запрос будет найден в том случае, если будет достигнуто конечное состояние автомата и так до конца текста. В принципе, наверное, с помощью какого-либо генератора это сделать не очень сложно, но тем не менее уместнее поискать программиста за деньги, вряд ли ты сам с этим разберёшься.
 

PassExcel

Новичок
Если большая точность распознования не нужна, да и синтаксического разбора запроса в конечной цели нет, я думаю как все можно упростить.
На данный момент пробую такой алгоритм:
1) Начало запроса нам известно (вхождение select, update,insert)
2) Перебираем все впереди идущие операторы sql (from, where, like, group/order by, AND и т.д.)
3) Таким образом у нас есть начало запроса и есть начало последнего оператора. Например такой вид уже есть
SELECT 1 FROM news WHERE id=1 AND
Теперь нужно выявить концовку последнего оператора. Пока в мыслях вертится искать первый попавшийся пробел или знак ;
Естественно они не должны находится в ковычках и скобках. Собственно это и будет конец запроса.

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

PassExcel

Новичок
PHP:
// операторы. ищем самый последний из них и проверяем концовки
$operator = array('where', 'join', 'using', 'on', 'and', 'in', 'or', 'like', 'values', 'limit', 'group by', 'order by'); 

...


			if (preg_match('%^(.*?)\s*=\s*([^\s]+)%i', $end, $ret)) { // id=2
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^\((.*?)\)%i', $end, $ret)) { // values (1,2,3)
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^\'(.*?)\'%i', $end, $ret)) { // like '%%'
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^(\d+)\s*,\s*(\d+)%i', $end, $ret)) { // limit 1,2
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^(\d+)%i', $end, $ret)) { // where 1
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^(\w+)(.\w+)?%i', $end, $ret)) { // group by id, group by users.id
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^0x(\w+)%i', $end, $ret)) { // where 0x37
					$buffer[] = $start.$ret[0];
				}

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

Какие еще варианты концовки я упустил?
 

Вурдалак

Продвинутый новичок
Вариантов ты упустил бесконечное множество. В мануале прямо к каждому виду запросов есть описание. К примеру, в WHERE можно подставлять любое выражение из
expr:
expr OR expr
| expr || expr
| expr XOR expr
| expr AND expr
| expr && expr
| NOT expr
| ! expr
| boolean_primary IS [NOT] {TRUE | FALSE | UNKNOWN}
| boolean_primary

boolean_primary:
boolean_primary IS [NOT] NULL
| boolean_primary <=> predicate
| boolean_primary comparison_operator predicate
| boolean_primary comparison_operator {ALL | ANY} (subquery)
| predicate

comparison_operator: = | >= | > | <= | < | <> | !=

predicate:
bit_expr [NOT] IN (subquery)
| bit_expr [NOT] IN (expr [, expr] ...)
| bit_expr [NOT] BETWEEN bit_expr AND predicate
| bit_expr SOUNDS LIKE bit_expr
| bit_expr [NOT] LIKE simple_expr [ESCAPE simple_expr]
| bit_expr [NOT] REGEXP bit_expr
| bit_expr

...
http://dev.mysql.com/doc/refman/5.0/en/expressions.html

P.S. BTW, паттерн для строкового литерала должен выглядеть так:
PHP:
$pattern = '/"(\\\\.|[^"\\\\])*"/s';
(аналогично для апострофов).
 

AmdY

Пью пиво
Команда форума
а можно узнать, для достижения какой цели вы ищете запросы и в каких файлах?

советую посмотреть на реализацию в Doctrine, там есть DQL - их подвид SQL, изменения незначительные.
 

PassExcel

Новичок
Пишу на заказ и судя по тз, скрипт должен проверять пользовательские загруженные файлы и если там есть sql запросы, то выдавать предупреждение.

Алгоритм на текущий момент следующий:
1) Задаем список началов запроса и список операторов


PHP:
$start    = array('select', 'insert', 'delete', 'update', 'alter');
$operator = array('where', 'join', 'using', 'on', 'and', 'in', 'or', 'like', 'values', 'limit', 'group by', 'order by', 'xor', '||', '&&', 'regexp');
2) В тексте ищем все начала и делим текст по ним. Нам неизвестно длины запроса, так что берем отрезок текста от найденного начала до следующего sql начала.

3) У нас имеются куски текста, в которых мы точно знаем, что они начинаются именно с sql запроса. Наша задача сократить объем текста для анализа. Если файл html то ищем первые html теги от начала и все что после них - удаляем.
Потом нужно обрезать текст по символу ;
Вообщем ищем первый попавшийся символ ; который не в ковычках и не в скобках.

4) У нас уже чуть подчищенный кусок текста с началом с sql запросом. Явное мы почистили, теперь осталось самое сложное. Но сейчас нам нужно вообще проверить, является ли выбранный sql начал - вообще sql запросом. Вдруг там просто встретилось слово SELECT в тексте.

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

Пример код для проверки запросов
PHP:
		/*проверяем валидность запросов, у каждого типа запроса должна быть зависимость*/
		$tmp = array();
		for ($i = 0, $len = sizeof($querys); $i < $len; $i++) {
			if (preg_match('%^(SELECT|UPDATE|DELETE|INSERT|REPLACE|ALTER)%i', $querys[$i], $ret)) {
				$type = strtolower($ret[1]);
				if (($type == 'select' || $type == 'delete') && preg_match('%\bfrom\b%i', $querys[$i])) {
					$tmp[] = $querys[$i];
				} elseif ($type == 'insert' && preg_match('%values?\s*\(%i', $querys[$i])) {
					$tmp[] = $querys[$i];
				} elseif ($type == 'update' && preg_match('%\bset\b%i', $querys[$i])) {
					$tmp[] = $querys[$i];
				} elseif ($type == 'alter' && preg_match('%^alter\s+(DATABASE|TABLE|SERVER|EVENT|VIEW|FUNCTION|PROCEDURE)%i', $querys[$i])) {
					$tmp[] = $querys[$i];
				}
			}
		}
		$querys = $tmp;

У селекта и делите должен быть FROM
у инстера должны быть VALUES|VALUE
у альтера DATABASE|TABLE|SERVER|EVENT|VIEW|FUNCTION|PROCEDURE
у апдейта должен быть SET

5) Теперь перебираем все sql операторы и выявляем самый последний среди них. Делим текст по нему. В итоге получается у нас есть начало чистого sql запроса и надо выявить его концовку из другой части.

Кстати, из концовки удаляем апострофы, нагрузки не несет, а загоняться лишний раз под регулярки с ним нет никакой надобности.

Концовку немного переписал, прочитав 8 пост, теперь выглядит так

PHP:
				if (preg_match('%^(.*?)\s*(=|!=|<>|<|>|>=|<=)\s*([^\s]+)%i', $end, $ret)) { // id=2
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^\((.*?)\)%i', $end, $ret)) { // values,in (1,2,3)
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^[\'"](.*?)[\'"]%i', $end, $ret)) { // like '%%', like "%%", regexp ''
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^(\d+)\s*,\s*(\d+)%i', $end, $ret)) { // limit 1,2
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^(\d+)%i', $end, $ret)) { // where 1
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^(\w+)(.\w+)?%i', $end, $ret)) { // group by id, group by users.id
					$buffer[] = $start.$ret[0];
				} elseif (preg_match('%^0x(\w+)%i', $end, $ret)) { // where 0x37
					$buffer[] = $start.$ret[0];
				}
В принципе это весь алгоритм, тестирую его. Работает, глючно конечно, не все запросы обрабатывает, но работает и самые типичные и распространненые запросы выявляет на ура.

С регулярками немного запутался.

Нужно обработать такие случаи

id=2 - до любого пробельного символа
id='2'
id="2"

как-то типо так

PHP:
preg_match('%=([\'"]?)(?(1)[^\'"]+|\s+)[\'"]?%i');
в регуляках есть условия. Если ([\'"]?) вернула значение, значит ищем [^\'"]+ иначе \s+

Решение конечно можно довести до рабочего состояние, но мне кажется, что фигню делаю и можно проще.


И второй момент, нужно обрабатывать такие случаи

VALUES (1,2,3)
VALUES (1,2,3),(4,5,6),(7,8,9)

Но регулярку уже не смог составить. Поможете?
 

Andreika

"PHP for nubies" reader
Пишу на заказ и судя по тз, скрипт должен проверять пользовательские загруженные файлы и если там есть sql запросы, то выдавать предупреждение.
вопрос - является ли текст "запросом"

dklsjdklsjd dkls select dklskdls from dlklskdlsk where akdaaa = 111111 djksajdklas uiwueioqwe

а этот?
dklsjdklsjd dkls select dklskdls from dlklskdlsk where akdaaa = $a11111 djksajdklas uiwueioqwe
 

Andreika

"PHP for nubies" reader
чет неудачно отредактировал.. а этот?

dklsjdklsjd dkls select dklskdls from dlklskdlsk where 1akdaaa = $a11111 djksajdklas uiwueioqwe
?
мож зацитируешь, что там в тз по поводу запросов в тексе написано? нужен сам факт или полный текст запросов?
 

craz

Нестандартное звание
а я кажись наю зачем эт все) это чтоб программистов по рукам бить если не пользуют ORM)
 

PassExcel

Новичок
По ТЗ, пользователю должно выдаваться сообщение:
Загруженный вами файл содержит sql запросы: ...перечисляем их..
Прихоть не моя. Идеального решения не требуют, но типичные запросы должно распознавать.

dklsjdklsjd dkls select dklskdls from dlklskdlsk where 1akdaaa = $a11111
Этот запрос тоже распознается. По логике вещей он является ошибочным, но показывать его я думаю тоже необходимо. Это все таки запрос, пусть даже и без необходимых ковычек.
 

tf

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

tz-lom

Продвинутый новичок
Да согласен, единственное, что автоматы ни разу не писал, не было необходимости
https://github.com/wez/JLexPHP.git
https://github.com/tz-lom/lemon-php.git
если захотите разбираться

как минимум возьмите лексер и бейте на лексеммы им (регулярками это слишком тяжко)
поток лексем можно фильтровать и руками,но на самом деле автомат выдающий приблизительно правильный результат сделать не так и сложно
хотя если покопаться в доке по MySQL то и полноценный можно сделать
P.S.
ща глянул, в MySQL используется bison , можете из него дёрнуть все правила , там правда их на пол-мегабайта набралось (но там и код составления синтаксического дерева в комплекте)
правда в роли лексера какое то кастомное решение
 
Сверху