tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

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

DibiTable a PostgreSQL

před 11 lety

johny
Člen | 12

Dobrý den, dnes jsem narazil na jednu nepříjemnost při použití DibiTable s Postgresem. Při update nastane chyba při získání ovlivněných řádků. Sám jsem její vznik bohužel i přes nemalou snahu neodhalil.

Při provedení DibiPostgreDriver::query() je resultset ještě v pořádku, když se ale po updatu volá affectedRows, tak je resultset neplatným zdrojem.

Viz výpis a ukázkový příklad:

Založím tabulku a vložím jeden záznam:

CREATE TABLE tabulka
(
   tabulka_id integer NOT NULL,
   hodnota character varying(256) NOT NULL,
   CONSTRAINT pk_tabulka PRIMARY KEY (tabulka_id)
);

insert into tabulka values(1, 'ahoj');

Update tohoto záznamu (při použití Mysql vše v pořádku)

<?php
error_reporting(E_ALL);

require_once('dibi.compact.php');

class Tabulka extends DibiTable{
    protected $primary = 'tabulka_id';
}

$opts['postgre'] = array(
  'driver'   => 'postgre',
  'host'     => 'localhost',
  'port'     => '5432',
  'user'     => 'postgres',
  'password' => 'password',
  'dbname'   => 'test',
  'charset'  => 'utf8',
  'lazy'     => TRUE
);

$opts['mysqli'] = array(
  'driver'   => 'mysqli',
  'host'     => 'localhost',
  'username' => 'root',
  'password' => 'password',
  'database' => 'test',
  'charset'  => 'utf8',
  'lazy'     => TRUE
);

dibi::connect($opts['postgre']);
//dibi::connect($opts['mysqli']);

$tabulka = new Tabulka();

$id = 1;
$data = array('hodnota' => 'Update proveden');

try {
  $tabulka->update($id, $data);
  echo 'OK';
} catch (DibiException $e){
  dibi::dump();
  echo $e;
}
/*
  Warning: pg_affected_rows(): 4 is not a valid PostgreSQL result resource in
  D:\dev\html\test\dibi\dibi.compact.php on line 995

  UPDATE "tabulka"
  SET "hodnota"='nazdar'
  WHERE "tabulka_id" IN (1 )

  exception 'DibiException' with message 'Cannot retrieve number of affected rows.'
  in D:\dev\html\test\dibi\dibi.compact.php:201
  Stack trace:
  #0 D:\dev\html\test\dibi\dibi.compact.php(416): DibiConnection->affectedRows()
  #1 D:\dev\html\test\dibi\dibi_err.php(31): DibiTable->update(1, Array)
  #2 {main}
*/
?>

Update je i přes chybu proveden

SELECT * FROM tabulka;
1;"Update proveden"

Je možné, že něco dělám špatně?

před 11 lety

David Grudl
Nette Core | 6822

Jelikož PostgreSQL nemám, tak to nemůžu odkrokovat. Zkus každopádně místo compact verze použít standardní dibi a v souboru postgre.php upravit metodu affectedRows() tak, aby vydumpovala $this->resultset a také návratovou hodnotu. Od toho bychom se mohli odrazit.

před 11 lety

deric
Člen | 93

Problém je ve volání funkce pg_affected_rows(). Pokud je volána ve funkci query(), tak se zdá, že funguje korektně.

Jinak to hází warning:
pg_affected_rows(): 265 is not a valid PostgreSQL result resource in /libs/dibi/drivers/postgre.php on line 166

private $affectedRows = 0 ;

    public function query($sql)
    {
        $this->resultset = pg_query($this->connection, $sql);
        is_resource($this->resultset);


        if ($this->resultset === FALSE) {
            throw new DibiDriverException(pg_last_error($this->connection), 0, $sql);
        }
        $this->affectedRows = pg_affected_rows($this->resultset);
        return is_resource($this->resultset);
    }

    public function affectedRows()
    {
        return $this->affectedRows;
    }

ovšem je zbytečné volat pg_affected_rows po každém selectu, takže to není úplně ideální řešení :-/

před 11 lety

johny
Člen | 12

Jak píše deric, jednou je resultset při volání pg_affected_rows v pořádku, po druhé ne.
Upravil jsem fce query() a affectedRows() v postgre.php takto:

public function query($sql)
{
    $this->resultset = @pg_query($this->connection, $sql);

    if ($this->resultset === FALSE) {
        throw new DibiDriverException(pg_last_error($this->connection), 0, $sql);
    }
    echo '1. print_r($this->resultset): ' . print_r($this->resultset, true) . "<br />\n";
    echo '2. is_resource($this->resultset): ' . is_resource($this->resultset) . "<br />\n";
    return is_resource($this->resultset);
}

public function affectedRows()
{
    echo '3. print_r($this->resultset): ' . print_r($this->resultset, true) . "<br />\n";
    echo '4. is_resource($this->resultset): ' . is_resource($this->resultset) . "<br />\n";
    echo '5. print_r(pg_affected_rows($this->resultset)): '. print_r(pg_affected_rows($this->resultset), true) . "<br />\n";
    return pg_affected_rows($this->resultset);
}

Výsledek:

1. print_r($this->resultset): Resource id #18
2. is_resource($this->resultset): 1
3. print_r($this->resultset): Resource id #18
4. is_resource($this->resultset):

Warning: pg_affected_rows(): 18 is not a valid PostgreSQL result resource in D:\dev\html\test\dibi\dibi\drivers\postgre.php on line 165
5. print_r(pg_affected_rows($this->resultset)):

Navíc jsem ukázkový příklad doplnil o toto:

dibi::query('UPDATE [tabulka] SET [hodnota] = %s', $data['hodnota'], 'WHERE [tabulka_id] IN ( %i', 2, ')');
dibi::dump();
echo dibi::affectedRows();

Což opět vyprodukuje stejnou chybu, takže se DibiTable je v tom nevinně

1. print_r($this->resultset): Resource id #19
2. is_resource($this->resultset): 1

UPDATE "tabulka"
SET "hodnota" = 'Update proveden'
WHERE "tabulka_id" IN ( 2 )

3. print_r($this->resultset): Resource id #19
4. is_resource($this->resultset):

Warning: pg_affected_rows(): 19 is not a valid PostgreSQL result resource in D:\dev\html\test\dibi\dibi\drivers\postgre.php on line 165
5. print_r(pg_affected_rows($this->resultset)):

Warning: pg_affected_rows(): 19 is not a valid PostgreSQL result resource in D:\dev\html\test\dibi\dibi\drivers\postgre.php on line 166

Fatal error: Uncaught exception 'DibiException' with message 'Cannot retrieve number of affected rows.'
in D:\dev\html\test\dibi\dibi\libs\DibiConnection.php:285
Stack trace:
#0 D:\dev\html\test\dibi\dibi\dibi.php(344): DibiConnection->affectedRows()
#1 D:\dev\html\test\dibi\dibi_err.php(64): dibi::affectedRows()
#2 {main} thrown in D:\dev\html\test\dibi\dibi\libs\DibiConnection.php on line 285

před 11 lety

David Grudl
Nette Core | 6822

Vypadá to jako chyba PHP. Pokud se vám ji podaří separovat na minimum kódu, bylo by vhodné ji nahlásit na http://bugs.php.net.

Jako wordaround implementuji dericovo řešení s voláním pg_affected_rows() ihned po query. Na výkon by to nemělo mít vliv. Mimochodem stejně to funguje u PDO driveru.

Než to commitnu, zkuste prosím, jestli to funguje odkaz smazán.

před 11 lety

johny
Člen | 12

Než to commitnu, zkuste prosím, jestli to funguje

Workaround funguje v pořádku.

Vypadá to jako chyba PHP. Pokud se vám ji podaří separovat na minimum kódu, bylo by vhodné ji nahlásit na http://bugs.php.net.

Zítra zkusím

před 11 lety

David Grudl
Nette Core | 6822

Je to fixnuto v poslední verzi.

před 11 lety

johny
Člen | 12

Tak jsem si s tím trochu pohrál, a vystopoval jsem příčinu. (Pracoval jsem s verzí ještě před opravou)

Jedná se odlišné chování mysqli a postgre driveru.

http://cz2.php.net/mysqli_query:
Returns TRUE on success or FALSE on failure. For SELECT, SHOW, DESCRIBE or EXPLAIN mysqli_query() will return a result object.

Při UPDATE DibiMysqliDriver::query vrací FALSE (is_object($this->resultset)), kdežto DibiPostgreDriver::query vrátí TRUE.

To má za následek, že DibiConnection::nativeQuery() v případě mysqli vrátí FALSE a v případě postgre vrátí objekt DibiResult (klon původního výsledku).

Pokud tento nijak neuložím, tak je ihned zavolán jeho destruktor a s ním i funkce pg_free_result(). Takže následně, když je volána metoda affectedRows (i když volána nad předkem klonu), tak už nemá s čím pracovat.

Editoval johny (15. 5. 2008 20:00)

před 11 lety

David Grudl
Nette Core | 6822

Díky za analýzu!

Myslím, že workaround zase odstraním, ale bude potřeba zjistit, jak odlišit resource, které nese resultset od jiného resource. Možná by se k tomu dala použít funkce pg_num_fields() – můžete to prosím vyzkoušet? Hlavně pro krajní případy, jako třeba SELECT který nevrátí žádné řádky apod.

před 11 lety

deric
Člen | 93

Při úspěšném SELECTu vrací pg_num_fields() číslo > 0 (i když pg_num_rows() == 0) při UPDATE a INSERT nulu

před 11 lety

David Grudl
Nette Core | 6822

Tak to prubnem? odkaz odstraněn

před 11 lety

johny
Člen | 12

Vyzkoušel jsem update, select, DibiTable->fetch a DibiTable->update a vypadá to, že to funguje. Kdyby něco, dám určitě ještě vědět. Dobrá práce!

před 11 lety

David Grudl
Nette Core | 6822

Tak je to v aktuální verzi.