Mysql Проверка целых чисел для вставки в SQL запрос

Фанат

oncle terrible
Команда форума
В результате долгих размышлений родил вот это:
PHP:
	function escapeInt($value)
	{
		if (is_int($value) || is_float($value))
		{
			return number_format($value, 0, '.', '');
		} 
		elseif(preg_match('/^-?[0-9]+$/', $value))
		{
			return $value;
		}
		else
		{
			$this->error("Invalid value");
		}
	}
Для максимального значения bigint оно вернёт
PHP:
$var=18446744073709551615;
var_dump($var,gettype($var),escapeint($var));

float(1.844674407371E+19)
string(6) "double"
string(20) "18446744073709551616"

$var="18446744073709551615";
var_dump($var,gettype($var),escapeint($var));

string(20) "18446744073709551615"
string(6) "string"
string(20) "18446744073709551615"
Собственно, из-за значений, превышающих размер ПХП-шного инта, но валидных для мускуля, и сделаны все эти пляски с бубном.
Только регом проверять нельзя, поскольку значение будет приведено к строке со всеми вытекающимися:
PHP:
$var=18446744073709551615;
var_dump($var,gettype($var),preg_match('/^-?[0-9]+$/', $var));

float(1.844674407371E+19)
string(6) "double"
int(0)
Вопрос. Чоязабыл?
Можно ли сделать лучше/красивше?
 

Фанат

oncle terrible
Команда форума
Не совсем понял, это к чему?

Во всяком случае отвечу, почему не использую просто
PHP:
if (is_numeric($value)) {
    return $value;
}
при числах больше инта но меньше потери точности оно будет превращаться в тыкву:
PHP:
$i = 184467440737095;
var_dump("$i");
var_dump(number_format($i, 0, '.', ''));

string(18) "1.844674407371E+14"
string(15) "184467440737095"
следовательно, всё равно надо проверять числа и строки по отдельности.
В принципе, наверное, можно было бы заменить регулярку на is_umeric, но регулярка, всё же, строже.
 

HEm

Сетевой бобер
т.е. если число будет в строковом эквиваленте "1.1E+14" то тыква неминуема
 

HEm

Сетевой бобер
может стоит добавить такой вариант с выводом "Very big value"
 

~WR~

Новичок
Возможно, странный вопрос, но какой смысл разделять строки и числа при сборке SQL-запросов?
Почему нельзя работать со всеми типами данных как со строками?

Все СУБД прекрасно понимают числа в кавычках.

В случае попытки записать некорректное значение, нормальные СУБД выдадут ошибку SQL.
Причем корректность должна проверяться именно на уровне базы, т.к. значение может не влазить в поле, либо не удовлетворять constraint'ам или foreign key'ям.
Скрипт по определению не может и не должен всё это знать. Задача скрипта - собрать валидный запрос без инъекций.

Отдельная тема с очень большим int'ом, который превращается во float.
Это просто фишка PHP. Хорошая или плохая - черт знает - но оно вот так.
Почему же это дает право драйверу неявно изменять переданное ему значение?

Если в приложении действительно нужно работать с большими числами, то все равно не избежать использования модулей типа bcmath.
И все равно числа у вас будут храниться в виде строк и процеситься в виде строк. А если нет, то вы попадёте на все проблемы с потерей точности float'ов, описанные здесь: http://php.net/manual/en/language.types.float.php

Так зачем впиливать костыли в работу с базой? Это не её проблемы.
 

~WR~

Новичок
Хотя да, есть один случай, когда кавычки имеют значение.
Это тип double в mysql. В insert'ах и update'ах значения записываются корректно, но когда пишешь в условии:
PHP:
WHERE foo = '0.07'
- это не работает.

У типа decimal такой проблемы нет.

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

--------------------

И еще один момент про mysql. Ему нельзя просто так делать проверку типа is_numeric, т.к. если вы делаете поиск по текстовой колонке вот так:
PHP:
WHERE text_column=12345
- то mysql не может использовать индекс. Хотя мог бы просто написать ошибку о невозможности адекватно сравнить строку с числом.

И это будет замечено не сразу, а только когда продакшн база ляжет, потому что кто-то захотел поискать число и получил Seq scan.
 

Фанат

oncle terrible
Команда форума
т.е. если число будет в строковом эквиваленте "1.1E+14" то тыква неминуема
Число не может быть в таком виде. Это уже строка. И если число таким образом испортили, то оно в любом случае ни на что не годно. Этот вариант рассматривать нет смысла.
А что за вариант с выводом "Very big value"? я не понял совсем.
 

Фанат

oncle terrible
Команда форума
Возможно, странный вопрос, но какой смысл разделять строки и числа при сборке SQL-запросов?
Ну, как минимум, это параметры LIMIT.
Хотя по сути, ими всё и заканчивается.
Почему же это дает право драйверу неявно изменять переданное ему значение?
Где ты здесь увидел, что драйвер что-то меняет?
Меняет, как раз, PHP, при преобразовании в ту самую строку, за которую ты ратуешь. Василий Соломонович, вы или крестик снимите, или трусики наденьте.
все равно числа у вас будут храниться в виде строк и процеситься в виде строк.
Отлично! Строки этот код пропускает вообще без изменения!
У типа decimal такой проблемы нет.
Разумеется. Потому что флот хранится в базе не как 0.07, а как 0.0699999999999
 

~WR~

Новичок
Согласен про LIMIT. Не подумал о нем.

Где ты здесь увидел, что драйвер что-то меняет?
PHP:
return number_format($value, 0, '.', '');
- вот тут драйвер взял и поменял настоящий PHP'шный float 1.844674407371E+14 на строку 184467440737095. При этом, для каких-то значений, еще и потерял precision.

Идея в том, что если для вставки на самом деле нужна строка, то и в драйвер должна придти строка.
Как её правильно получить - забота той сущности, которая с этими большими числами работает и знает, как их правильно сохранить.

Иначе можем дойти до ситуации, когда кто-то реально захочет сохранить текст 1.844674407371E+14, а не сможет. Потому что драйвер потенциально содержит логику, которая конвертирует данные.
Понятно, что типизация плейсхолдеров поможет избежать этой проблемы, но потенциальное окно для косяков уже приоткрылось.

Разумеется. Потому что флот хранится в базе не как 0.07, а как 0.0699999999999
Разумеется. Но какого черта:
PHP:
SELECT '0.07' = 0.07 // true
UPDATE ... SET double_field='0.07' //save is okay!

WHERE double_field = 0.07 // true
WHERE double_field = '0.07' // false
На этапе разбора запроса это всё просто токены, строки. Чисел нет никаких, только чистый текст пришел от клиента.
Совершенно легко понять, что в обоих случаях идет сравнение с полем типа double, и константы в запросе должны трактоваться как double.

Afaik, ни в одной СУБД больше такой проблемы нет. Хотя, возможно, здесь я не прав.

Upd: Впрочем, можно просто забить на double. Всё равно для реальных бизнес-задач он не должен использоваться. А если кому-то действительно нужен именно double, а не numeric, то человек наверняка хорошо знает, что делает.
 

~WR~

Новичок
Еще подумалось.

Видимо, по той же самой причине все данные из функций *_fetch_row() всегда приходят в виде строк, либо null'ов
Даже если на первый взгляд кажется, что это неудобно, как в случае с постгресовским типом boolean. Никаких int, никаких float, никогда.

Видимо, строки - это единственный способ нормально передать данные без потерь. Как из базы, так и обратно в неё.
Всё остальное - игры с огнем и с особенностями нестрогой типизации.

Пора спать. >__<
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
при числах больше инта но меньше потери точности оно будет превращаться в тыкву:
PHP:
$i = 184467440737095;
var_dump("$i");
var_dump(number_format($i, 0, '.', ''));
прости, торможу, видимо, но не понимаю, как var_dump("$i") относится к is_numeric()

у меня на x64 результат работы другой, как-раз number_format($i, 0, '.', '') портит значение
PHP:
$i = 1844674407370955654;
var_dump("$i");
var_dump(number_format($i, 0, '.', ''));
# php test.php
string(19) "1844674407370955654"
string(19) "1844674407370955776"
размерность int - 19 символов, а точность float - 14!
 

WMix

герр M:)ller
Партнер клуба
Фанат
случаем не звезды считаешь с лимитом то 1.844.674.407.370.955.654 это один секстиллион, нет?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
WMix неважно, это может быть случайно сгенеренный ID
 

WMix

герр M:)ller
Партнер клуба
так проблема лимита, id его складывать не надо, строка и строка...
 

Фанат

oncle terrible
Команда форума
вот тут драйвер взял и поменял настоящий PHP'шный float 1.844674407371E+14 на строку 184467440737095.
Потому что SQL запрос - это строка. То есть, нам по-любому надо сделать из него строку.
У тебя есть другие варианты, если в драйвер пришел флоат?
Если нет - к чему все эти споры?
Идея в том, что если для вставки на самом деле нужна строка, то и в драйвер должна придти строка.
Ради бога! Повторюсь - если у тебя приходит строка, то и проблем нету.
Я не пойму суть твоих претензий. Предложи свой вариант?
Почему попытка обработать все возможные варианты, а не один-единственный, так тебе поперек горла встаёт?
Или твой вариант - "Драйвер не должен ничего форматировать, ему всё должно приходить уже отформатированное"? Шёл бы ты, мальчик, отсюда, с такими предложениями. А то ведь зашибу ненароком.

У тебя есть конкретные претензии к текущему коду? Не вида "а зачем обрабатывать такой-то случай - он в природе не встречается?", а претензии к безопасности и корректности кода? Если нет - спасибо за попытку помочь, всего хорошего.
 

Фанат

oncle terrible
Команда форума
у меня на x64 результат работы другой, как-раз number_format($i, 0, '.', '') портит значение
Ну, не ожидал я от него такой подлянки.
Ага! Ну, значит, код буде такой
PHP:
	private function escapeInt($value)
	{
		if (is_float($value))
		{
			return number_format($value, 0, '.', '');
		} 
		elseif(is_numeric($value))
		{
			return (string)$value;
		}
		else
		{
			$this->error("Invalid value for ?i (int) placeholder: [$value](".gettype($value).")");
		}
	}
Тут мы, кажется, покрыли все возможные случаи?
И регу здесь уже можно заменить на is_numeric, кажись
 

флоппик

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

fixxxer

К.О.
Партнер клуба
А кто сказал что метод error не кидает исключение?
 
Сверху