Покритикуйте подход и реализацию?

Фанат

oncle terrible
Команда форума
PHP:
	private function prepareQuery($args)
	{
		$query = array_shift($args);
		preg_match_all('~[^\\\](\?[a-z])~',$query,$m,PREG_OFFSET_CAPTURE);
		$pholders = $m[1];
		if ( count($pholders) != count($args) )
		{
			trigger_error("Number of args doesn't match number of placeholders");
			return FALSE;
		}
		$shift = 0;
		foreach ($pholders as $i => $p)
		{
			$pholder = $p[0];
			$offset  = $p[1] + $shift;
			$value   = $args[$i];
			switch ($pholder)
			{
				case '?n':
					$value = "`".str_replace("`","``",$value)."`";
					break;
				case '?s':
					$value = "'".mysql_real_escape_string($value,$this->connect)."'";
					break;
				case '?i':
					$value = " ". number_format($value, 0, '.', '');
					break;
				case '?a':
					$value = $this->createIN($value);
					break;
				default:
					trigger_error("Unknown placeholder type");
					return FALSE;
			}
			$query = substr_replace($query,$value,$offset,2);
			$shift+= strlen($value) - strlen($pholder);
		}
		return $query;
	}
 

Вурдалак

Продвинутый новичок
Механизм экранирования символа «?» не доведён до конца.

PHP:
preg_match_all('~\?[a-z?]~', $query, $m, PREG_OFFSET_CAPTURE);

foreach ($pholders as $i => $p)
{
    // ...
    switch ($pholder)
    {
        // ...

        case '??':
            $value = '?';
            break;


        // ...
    }
}
Как-то так много проще. Когда вводишь сразу 2 служебных символа (бекслеш и ещё что-то), то и позволять экранировать приходится оба.

P.S. А зачем пробел перед number_format (при оформлении числа)?
P.P.S. Да и trigger_error() в баню, если говорить о реализации. Исключения нужны.
 

shelestov

я тут часто
Я у себя использую знак ":", вместо вопроса. Хотя разницы тут никакой.
И модификаторов накопилось больше. К примеру f (float), u (unsigned int), m (milti int для конструкций IN (1,2,3), a (multi strings IN ('a', 'b', 'c'))) и т.д.
 

Фанат

oncle terrible
Команда форума
Вурдалак
Вот спасибо! Действительно косяк, да какой. И решение красивое.
Только теперь надо будет подумать, как с проверкой количества совместить. Но это уже мелочи


По остальному отвечу.
пробел перед числом - чтобы не было http://habrahabr.ru/blogs/postgresql/120128/
триггер еррор поймается еррор хендлером и поднимет исключение. причём уже с бэктрейсом и прочими делами.
number_format все и так уже знают - intval косячит с большими числами.
 

Lirik

Новичок
Я бы блок switch-case выделил бы в отдельный метод, что-то вроде getPlaceHolder
 

fixxxer

К.О.
Партнер клуба
[
пробел перед числом - чтобы не было http://habrahabr.ru/blogs/postgresql/120128/
в мыскле комментарий - "-- " (с пробелом) как раз чтобы такого не было.
а для постгреса ?n и ?s надо по другому :)

а проблему с intval и пр. я для себя решил отсутствием 32-битных систем. ;)
 

fixxxer

К.О.
Партнер клуба
Куда мне столько )

Кстати про unsigned - строго говоря, надо два разных плейсхолдера, для signed и unsigned. Иначе рискуем получить в лучшем случае (если озаботились надлежащей настройкой strict mode) sql error, в худшем - фигню в базе. А еще строже говоря, надо точно знать тип поля, и сколько в него влезает, иначе то же самое. Так что по большому счету, практически весь написанный кем-либо код работает исходя из предположения, что до лимитов не доберемся =)

Про строки, кстати, тоже вопрос - гарантирует ли mysql_real_escape_string etc отсутствие невалидных utf-8 последовательностей, на которые хорошая база (или правильно настроенный мыскль) матом изойдет? mysql_escape_string точно не гарантирует, pg_escape_string - тоже. Еще вопрос про длину - вот есть у нас varchar(255), много ли кто проверяет при составлении запроса mb_strlen? ;)

Итд итп.

И, кстати, тут выходит забавное следствие - про то, что ActiveRecord рулит. =)
 

Вурдалак

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

fixxxer

К.О.
Партнер клуба
Да понятно что у всех всё в моделях. Я к тому что задача генерации валидного SQL запроса сложнее чем кажется
 

MiksIr

miksir@home:~$
К слову, ungisned int вроде как тоже не фига не ansi и в том же постгресе его нет... другое дело, кому нужны самописные плейсхолдеры в постгресе...
 

fixxxer

К.О.
Партнер клуба
Ну иногда нужны. :)

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

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
К слову, ungisned int вроде как тоже не фига не ansi и в том же постгресе его нет...
Причём, ЕМНИП, его там нет не потому, что так уж сложно добавить, а потому, что сложно нормально сделать неявное приведение типов. Т.е. та же проблема что здесь, придётся всё время указывать, что речь у нас об unsigned.

Что касается исходного вопроса, мне кажется выгрызание placeholder'ов "малость" недоработано: что будет, если у нас запрос вида
Код:
select *, '?a' where somefield = ?a
?
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Например, нормального способа сделать мультиинсерт
что имеется в виду под "мультиинсерт"ом?
или IN через нативные плейсхолдеры нет.
Если не настаивать именно на IN, а обойтись ANY, то:
PHP:
$ids  = array(1, 2, 3, 4);

$conn = pg_connect('...');

$stmt = pg_prepare($conn, 'arytest', 'select user_login from users where user_id = any($1)');

$res = pg_execute($conn, 'arytest', array('{' . implode(',', $ids) . '}'));

for ($i = 0, $rows = pg_numrows($res); $i < $rows; $i++) {
    echo pg_fetch_result($res, $i, 0) . "\n";
}
замечательно работает ;)
 

fixxxer

К.О.
Партнер клуба
Я PDO имел в виду.

Под мультиинсертом - ну самый обычный insert ...values (),(),(),()

Ну и join(,) - это не круто :) По уму еще и array_filter надо, что становится малозабавным писать руками.
 

fixxxer

К.О.
Партнер клуба
btw ты не в курсах, на merge там совсем забили или таки в планах? А то для его эмуляции (ну или можно сказать, для эмуляции мысклевких replace и on duplicate key update, которые по сути частные случаи) такие извращения приходится делать, что самому страшно. ;)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Я PDO имел в виду.
PDO != "нативные плейсхолдеры". Что и как он там делает с запросом и подставляемыми значениями --- х.з., но вариант с массивом действительно не работает.

Ну и честно говоря, у меня не вызывает восхищения библиотека якобы "абстрактного доступа", в которой есть метод lastInsertId(). Как обычно, доступ абстрактен, если вы пользуетесь одной из версий мыскля.

Под мультиинсертом - ну самый обычный insert ...values (),(),(),()
Во-первых, это синтаксический сахар, никто не мешает делать
Код:
begin;
insert ...
...
insert ...
commit;
Во-вторых
PHP:
$stmt = pg_prepare($conn, 'multitest', 'insert into roles (role_name) values ($1), ($2), ($3)');

$res = pg_execute($conn, 'multitest', array('Тестовый админ', 'Тестовый юзер', 'Тестовый придурок'));

echo "Записей вставлено: " . pg_affected_rows($res) . "\n";
опять же работает.

Ну и join(,) - это не круто :) По уму еще и array_filter надо, что становится малозабавным писать руками.
В реальности для этого нужна библиотека приведения типов наподобие Котеровской DB_Pgsql_Type, здесь был просто пример.

btw ты не в курсах, на merge там совсем забили или таки в планах? А то для его эмуляции (ну или можно сказать, для эмуляции мысклевких replace и on duplicate key update, которые по сути частные случаи) такие извращения приходится делать, что самому страшно. ;)
Не знаю, похоже после прошлого года всё несколько затихло.
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
PDO != "нативные плейсхолдеры". Что и как он там делает с запросом и подставляемыми значениями --- х.з., но вариант с массивом действительно не работает.
Тьфу, вру, неправильно проверял, всё работает и там:
PHP:
$db = new PDO('...');

$stmt = $db->prepare('select user_login from users where user_id = any(?)');
$stmt->execute(array('{' . implode(',', $ids) . '}'));

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['user_login'] . "\n";
}
 

Фанат

oncle terrible
Команда форума
Однако, я проснулся.
Механизм экранирования символа «?» не доведён до конца.
Как-то я сразу не въехал в то, что ты написал. Ты имел в виду случай, когда в коде запроса встречается последовательность \?abracadabra, которая не плейсхолдер?
Тогда я бы сделал по-своему, просто заменив экранирующий символ.
PHP:
preg_match_all('~[^?](\?[a-z])~',$query,$m,PREG_OFFSET_CAPTURE);
$query = str_replace('??','?',$query);
так нормально?
соответственно, запрос будет вида
PHP:
SELECT ?n FROM ?n WHERE a='??a' AND b=?s LIMIT ?i,?i
 
Сверху