Oznámení
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 | 6806
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 | 6806
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 | 6806
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 | 6806
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 | 6806
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 | 6806
Tak je to v aktuální verzi.