tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

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 ...).