Поиск 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')

и т.д.

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

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

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

Глючно, не универсально, но должно работать. Как думаете? От этого алгоритма можно отталкиваться и шлифовать его или стоит еще подумать..больно уж не хочется полный анализатор делать
 
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';
(аналогично для апострофов).
 
а можно узнать, для достижения какой цели вы ищете запросы и в каких файлах?

советую посмотреть на реализацию в Doctrine, там есть DQL - их подвид SQL, изменения незначительные.
 
Пишу на заказ и судя по тз, скрипт должен проверять пользовательские загруженные файлы и если там есть 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)

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

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

а этот?
dklsjdklsjd dkls select dklskdls from dlklskdlsk where akdaaa = $a11111 djksajdklas uiwueioqwe
 
чет неудачно отредактировал.. а этот?

dklsjdklsjd dkls select dklskdls from dlklskdlsk where 1akdaaa = $a11111 djksajdklas uiwueioqwe
?
мож зацитируешь, что там в тз по поводу запросов в тексе написано? нужен сам факт или полный текст запросов?
 
а я кажись наю зачем эт все) это чтоб программистов по рукам бить если не пользуют ORM)
 
По ТЗ, пользователю должно выдаваться сообщение:
Загруженный вами файл содержит sql запросы: ...перечисляем их..
Прихоть не моя. Идеального решения не требуют, но типичные запросы должно распознавать.

dklsjdklsjd dkls select dklskdls from dlklskdlsk where 1akdaaa = $a11111

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

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