Oznámení
Bug report: Rozpoznávání datových typů
před 8 lety
- maarlin
- Člen | 207
Pokud mám zapnuté rozpoznávání datových typů a z databáze přijde nějaké velké číslo (využívající bigint), dibi ho překonvertuje na standardní PHPčkový int, což skončí u 2147483647 (max. hodnota integeru).
Příklad (v PostgreSQL, v MySQL je to hodně podobné):
CREATE TABLE posts
(
id bigint NOT NULL,
"name" character varying
);
INSERT INTO posts ("id", "name") VALUES (10150321138350001, "Lorem Ipsum");
config.ini – část s nastavením dibi
database.resultDetectTypes = true
Volání skrz dibi:
$conn = dibi::getConnection();
$post = $conn->select('id')->from('posts')->fetchAll();
NDebug::dump($post);
Vydumpovaný záznam ($post
):
array(1) [
0 => DibiRow(1) {
"id" => 2147483647
}
]
Řešení se dá hledat možná v BCMath nebo v GMP ale pochopitelně nejde na dostupnost těchto knihoven v žádném případě spoléhat…
Podstatné je, že jiný rozsah „intu“ může být na 32bit systému a jiný na 64bit… Tedy přesněji na 64bit systému tento problém nastat nemusí.
http://www.php.net/….integer.php
Rozsah intu by ale měl jednoduše jít zjistit z konstanty PHP_INT_SIZE, příp. PHP_INT_MAX (vizte odkaz výše).
Navíc PHP samo kovertuje int na float, pokud by mělo přetéci přes tyhle konstanty:
If PHP encounters a number beyond the bounds of the integer type, it will be interpreted as a float instead.
Spolehlivě zjistit, jaký ale má float v PHP rozsah je prakticky nemožné:
The size of a float is platform-dependent, although a maximum of ~1.8e308 with a precision of roughly 14 decimal digits is a common value (the 64 bit IEEE format).
A v praxi většinou osobně nepotřebuju s tak velkými čísly provádět nějaké operace… to už bych asi ty knihovny měl nainstalované… jediné co potřebuju, je mít jistotu, že z databáze opravdu přijde číslo, a ne text, nebo jiný balast a pokud přišlo opravdu číslo, tak ho korektně vypsat (klidně pak zase jako string).
Editoval maarlin (9. 4. 2011 12:33)
před 8 lety
- Milo
- Nette Core | 1119
EDIT: Následující patch nepoužívejte, díky PHP zaokrouhlování float čísel může dojít k monstrózním chybám
Chyba s ořezáváním integeru se projeví i u modifikátoru %i. Tam
to lze obejít použitím %f. (Sic i (float) má omezenou délku.)
$db->test('SELECT %i', 123123123123);
// SELECT -1430928461
$db->test('SELECT %f', 123123123123);
// SELECT 123123123123
Podobná malá berlička při automatické detekci
může být:
$db->query('......')
->setType('sloupecBigint', dibi::FLOAT)
->fetchAll();
Používám následující fix (jen u PostgreSQL ve dvou aplikacích,
nevím jak ovlivní jiné DB)
// Původní dibi (libs/DibiDatabaseInfo.php -> detectType()
static $patterns = array(
'BYTEA|BLOB|BIN' => dibi::BINARY,
'TEXT|CHAR|BIGINT|LONGLONG' => dibi::TEXT,
'BYTE|COUNTER|SERIAL|INT|LONG' => dibi::INTEGER,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => dibi::FLOAT,
'^TIME$' => dibi::TIME,
'TIME' => dibi::DATETIME, // DATETIME, TIMESTAMP
'YEAR|DATE' => dibi::DATE,
'BOOL|BIT' => dibi::BOOL,
);
// Fix
static $patterns = array(
'BYTEA|BLOB|BIN' => dibi::BINARY,
'TEXT|CHAR|BIGINT|LONGLONG' => dibi::TEXT,
'INT8|CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => dibi::FLOAT,
'BYTE|COUNTER|SERIAL|INT|LONG' => dibi::INTEGER,
'^TIME$' => dibi::TIME,
'TIME' => dibi::DATETIME, // DATETIME, TIMESTAMP
'YEAR|DATE' => dibi::DATE,
'BOOL|BIT' => dibi::BOOL,
);
Jak to ale fixnout? Chápat %i jako integer v podání PHP, nebo jako celé
číslo s libovolným počtem cifer? Zavést %si modifikátor „string
integer“ a využívat nějakou Math knihovnu?
Každopádně jsem za to, aby se PostgreSQL (INT8) detekoval jako PHP
(float).
Editoval Milo (14. 4. 2011 14:39)
před 8 lety
- maarlin
- Člen | 207
A ještě jeden bug při rozpoznávání:
Pokud Postgre vrátí dat. typ interval – vizte např. http://developer.postgresql.org/…atetime.html → fce
age(), překonvertuje se to nějak záhadně na integer, obvykle jako počet
hodin… minuty a sekundy to zcela zanedbá… rozdíl ve dnech a více jsem
nezkoušel…
Navíc Postgre má datové typy víceúrovňových polí (obvykle název dat. typu začíná _), které dibi taky vůbec neřeší a konvertuje na standardní datové typy…
např. pokud mám pole integerů (_INT4), tak ho převede na standardní PHP int, tedy přesněji řečeno na nulu…
Editoval maarlin (10. 4. 2011 12:17)
před 8 lety
- maarlin
- Člen | 207
Jinak do PHP FLOATu se sice ta velká čísla vejdou, ale musí se počítat s tím, že to je float, tzn. při výpisu kamkoliv to nejde prostě pushnout jako proměnnou, ale třeba skrz
sprintf('%.0f', $bigInt);
nebo
number_format($bigInt, 0, '.', '');
Defaultně se to totiž vypisuje ve tvaru s exponentem, což asi
v případě, že to má jít třeba do link
makra v šabloně je
docela nešikovné…
Jestli opravdu nebude úplně nejlepší to defaultně konvertovat na string.
Editoval maarlin (14. 4. 2011 11:44)
před 8 lety
- Milo
- Nette Core | 1119
Aha, to je fakt, to jsem napsal pěknou blbost. Já se zatím vždy vešel do 1.0E+12.
A float by stejně nešel použít kvůli zaokrouhlování.
$num = 9999999999999999999999;
echo sprintf('%.0f', $num);
// 10000000000000000000000
Takže zřejmě jedinou možností jsou řetězce. Ale takový automatický
převod na řetězec také skrývá záludnosti. Dibi sice vrátí správně
velké číslo, ale stačí jediné $row->bigNumber + 1
a je to
v tahu.
Co umožnit zapnout podporu velkých čísel explicitně a až pak s nimi tak pracovat?
dibi::$allowBigNumbers = true; // nebo jakkoliv jinak, třeba přes config
// Při detekci typu INT
if( dibi::$allowBigNumbers && (strCmp((string)(int)$value, $value) !== 0)
{
// INT jako řetězec
}
// U %i modifikátoru
if( dibi::$allowBigNumbers && cType_digit((string) $value) )
{
// Hodnota je OK
}
před 8 lety
- Milo
- Nette Core | 1119
maarlin napsal(a):
Navíc Postgre má datové typy víceúrovňových polí (obvykle název dat. typu začíná _), které dibi taky vůbec neřeší a konvertuje na standardní datové typy…např. pokud mám pole integerů (_INT4), tak ho převede na standardní PHP int, tedy přesněji řečeno na nulu…
PostgreSQL má všobecně více vychytaných typů. MAC adresy, IP s netmaskou, atd… Ale myslím, že je zbytečné, aby tohle dibi celé pokrývalo.
Mám rozpracovaný koncept registrace vlastních modifikátorů. Výsledkem by měl být transparentní převod databázových typů na PHP typy. Nějak ale nezbývá čas to dotáhnout do konce.
class ipv4DibiModificator implements IDibiModificator
{
public function isValid( $phpValue )
{
...
}
public function matchDbNativeType( $type )
{
...
}
public function toPhp( $dbValue ) // Z databáze do PHP
{
...
}
public function toSql( $phpValue ) // Z PHP do databáze
{
...
}
}
dibi::registerModificator('ipv4', new ipv4DibiModificator);
// Userspace
dibi::query('INSERT INTO computers (ip_address, hostname) VALUES( %{ipv4}, %s )', $var, $var);
Tak by se daly pokrýt i pole a luxusně vytvářet i rozšíření pro Dibi.
Editoval Milo (14. 4. 2011 15:34)
před 8 lety
- HosipLan
- Moderator | 4693
Dalo by se s tím dělat mnohem víc věcí, ale tohle už trošku zavání ORMkem a přesně tenhle koncept typů využívá Doctrine. Ale je to pěkné :)
PS: registraci modifikátorů udělej objektově a na DibiConnection, nesnaž se to cpát staticky do třídy dibi, to je tfuj ;)
Editoval HosipLan (14. 4. 2011 18:42)
před 8 lety
- Milo
- Nette Core | 1119
Dal jsem na github implementaci, která mi ležela nějakou dobu v šuplíku. Více zde na fóru.
před 8 lety
- David Grudl
- Nette Core | 6806
Konverze už funguje jinak, přetrvává problém?
před 8 lety
- Milo
- Nette Core | 1119
Funguje to dobře. Zkusil jsem velká (nad PHP_INT_MAX) i hodně velká (300 cifer) čísla.
Narazil jsem ale na problém pole integerů v PostgreSQL, jsou převedeny na
nulu. Např {10}
nebo {{10},{20}}
. Fixnul jsem to
v pull requestu ale nevím, co
na to ostatní drivery. Pokud je sloupec v postgresu pole, nativní typ
začíná podtržítkem (_int8 _numeric ...
).