From ea00d5d37dff184c93bd0b907932138bdd0fedc4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 12 Nov 2007 01:39:26 +0000 Subject: [PATCH] * seek() or rowCount() in unbuffered mode throws exceptions * out of range seek() throws exception * deprecated DibiDriver::errorInfo * fixed seek(0) on first iteration * added DibiDatabaseException::catchError() & restore() for converting errors to exceptions --- dibi/drivers/mssql.php | 30 ++++--------- dibi/drivers/mysql.php | 55 +++++++---------------- dibi/drivers/mysqli.php | 34 +++++--------- dibi/drivers/odbc.php | 27 +++--------- dibi/drivers/oracle.php | 28 ++++-------- dibi/drivers/pdo.php | 28 +++--------- dibi/drivers/postgre.php | 45 +++++-------------- dibi/drivers/sqlite.php | 38 ++++++---------- dibi/libs/DibiDriver.php | 8 ++-- dibi/libs/DibiException.php | 32 +++++++++++++- dibi/libs/DibiResult.php | 76 +++++++++++++++++++++++++++----- dibi/libs/DibiResultIterator.php | 2 +- examples/fetch.php | 2 - examples/metatypes.php | 2 - 14 files changed, 184 insertions(+), 223 deletions(-) diff --git a/dibi/drivers/mssql.php b/dibi/drivers/mssql.php index 6ed5a625..2a95f28a 100644 --- a/dibi/drivers/mssql.php +++ b/dibi/drivers/mssql.php @@ -123,7 +123,7 @@ class DibiMsSqlDriver extends DibiDriver throw new DibiDatabaseException('Query error', 0, $sql); } - return is_resource($res) ? new DibiMSSqlResult($res) : NULL; + return is_resource($res) ? new DibiMSSqlResult($res, TRUE) : NULL; } @@ -189,21 +189,6 @@ class DibiMsSqlDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - return array( - 'message' => NULL, - 'code' => NULL, - ); - } - - - /** * Escapes the string * @@ -293,7 +278,7 @@ class DibiMSSqlResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return mssql_num_rows($this->resource); } @@ -317,11 +302,14 @@ class DibiMSSqlResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { - return mssql_data_seek($this->resource, $row); + if (!mssql_data_seek($this->resource, $row)) { + throw new DibiDriverException('Unable to seek to row ' . $row); + } } @@ -331,7 +319,7 @@ class DibiMSSqlResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { mssql_free_result($this->resource); } diff --git a/dibi/drivers/mysql.php b/dibi/drivers/mysql.php index 79e54019..2741ef67 100644 --- a/dibi/drivers/mysql.php +++ b/dibi/drivers/mysql.php @@ -103,27 +103,16 @@ class DibiMySqlDriver extends DibiDriver $host = ':' . $config['socket']; } - // some errors aren't handled. Must use $php_errormsg - if (function_exists('ini_set')) { - $save = ini_set('track_errors', TRUE); - } - - $php_errormsg = ''; - + DibiDatabaseException::catchError(); if (empty($config['persistent'])) { $connection = @mysql_connect($host, $config['username'], $config['password'], TRUE, $config['options']); } else { $connection = @mysql_pconnect($host, $config['username'], $config['password'], $config['options']); } - - if (function_exists('ini_set')) { - ini_set('track_errors', $save); - } + DibiDatabaseException::restore(); if (!is_resource($connection)) { - $msg = mysql_error(); - if (!$msg) $msg = $php_errormsg; - throw new DibiDatabaseException($msg, mysql_errno()); + throw new DibiDatabaseException(mysql_error(), mysql_errno()); } if (isset($config['charset'])) { @@ -163,17 +152,18 @@ class DibiMySqlDriver extends DibiDriver { $connection = $this->getConnection(); - if ($this->getConfig('unbuffered')) { - $res = @mysql_unbuffered_query($sql, $connection); - } else { + $buffered = !$this->getConfig('unbuffered'); + if ($buffered) { $res = @mysql_query($sql, $connection); + } else { + $res = @mysql_unbuffered_query($sql, $connection); } if ($errno = mysql_errno($connection)) { throw new DibiDatabaseException(mysql_error($connection), $errno, $sql); } - return is_resource($res) ? new DibiMySqlResult($res) : NULL; + return is_resource($res) ? new DibiMySqlResult($res, $buffered) : NULL; } @@ -240,22 +230,6 @@ class DibiMySqlDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - $connection = $this->getConnection(); - return array( - 'message' => mysql_error($connection), - 'code' => mysql_errno($connection), - ); - } - - - /** * Escapes the string * @@ -342,7 +316,7 @@ class DibiMySqlResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return mysql_num_rows($this->resource); } @@ -366,11 +340,14 @@ class DibiMySqlResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { - return mysql_data_seek($this->resource, $row); + if (!mysql_data_seek($this->resource, $row)) { + throw new DibiDriverException('Unable to seek to row ' . $row); + } } @@ -380,7 +357,7 @@ class DibiMySqlResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { mysql_free_result($this->resource); } diff --git a/dibi/drivers/mysqli.php b/dibi/drivers/mysqli.php index 27ed7c86..dafccab8 100644 --- a/dibi/drivers/mysqli.php +++ b/dibi/drivers/mysqli.php @@ -132,13 +132,14 @@ class DibiMySqliDriver extends DibiDriver protected function doQuery($sql) { $connection = $this->getConnection(); - $res = @mysqli_query($connection, $sql, $this->getConfig('unbuffered') ? MYSQLI_USE_RESULT : MYSQLI_STORE_RESULT); + $buffered = !$this->getConfig('unbuffered'); + $res = @mysqli_query($connection, $sql, $buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); if ($errno = mysqli_errno($connection)) { throw new DibiDatabaseException(mysqli_error($connection), $errno, $sql); } - return is_object($res) ? new DibiMySqliResult($res) : NULL; + return is_object($res) ? new DibiMySqliResult($res, $buffered) : NULL; } @@ -216,22 +217,6 @@ class DibiMySqliDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - $connection = $this->getConnection(); - return array( - 'message' => mysqli_error($connection), - 'code' => mysqli_errno($connection), - ); - } - - - /** * Escapes the string * @@ -318,7 +303,7 @@ class DibiMySqliResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return mysqli_num_rows($this->resource); } @@ -342,11 +327,14 @@ class DibiMySqliResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { - return mysqli_data_seek($this->resource, $row); + if (!mysqli_data_seek($this->resource, $row)) { + throw new DibiDriverException('Unable to seek to row ' . $row); + } } @@ -356,7 +344,7 @@ class DibiMySqliResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { mysqli_free_result($this->resource); } diff --git a/dibi/drivers/odbc.php b/dibi/drivers/odbc.php index ffb62aa3..22a064d0 100644 --- a/dibi/drivers/odbc.php +++ b/dibi/drivers/odbc.php @@ -135,7 +135,7 @@ class DibiOdbcDriver extends DibiDriver if (is_resource($res)) { $this->affectedRows = odbc_num_rows($res); if ($this->affectedRows < 0) $this->affectedRows = FALSE; - return new DibiOdbcResult($res); + return new DibiOdbcResult($res, TRUE); } } @@ -212,22 +212,6 @@ class DibiOdbcDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - $connection = $this->getConnection(); - return array( - 'message' => odbc_errormsg($connection), - 'code' => odbc_error($connection), - ); - } - - - /** * Escapes the string * @@ -316,7 +300,7 @@ class DibiOdbcResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { // will return -1 with many drivers :-( return odbc_num_rows($this->resource); @@ -341,9 +325,10 @@ class DibiOdbcResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { $this->row = $row; } @@ -355,7 +340,7 @@ class DibiOdbcResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { odbc_free_result($this->resource); } diff --git a/dibi/drivers/oracle.php b/dibi/drivers/oracle.php index e820967d..0b9fb64f 100644 --- a/dibi/drivers/oracle.php +++ b/dibi/drivers/oracle.php @@ -126,8 +126,7 @@ class DibiOracleDriver extends DibiDriver throw new DibiDatabaseException($err['message'], $err['code'], $sql); } - // TODO! - return is_resource($res) ? new DibiOracleResult($statement) : TRUE; + return is_resource($res) ? new DibiOracleResult($statement, TRUE) : TRUE; } @@ -201,18 +200,6 @@ class DibiOracleDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - return oci_error($this->getConnection()); - } - - - /** * Escapes the string * @@ -294,7 +281,7 @@ class DibiOracleResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return oci_num_rows($this->resource); } @@ -309,6 +296,7 @@ class DibiOracleResult extends DibiResult */ protected function doFetch() { + $this->fetched = TRUE; return oci_fetch_assoc($this->resource); } @@ -318,11 +306,13 @@ class DibiOracleResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { - //throw new BadMethodCallException(__METHOD__ . ' is not implemented'); + if ($row === 0 && !$this->fetched) return TRUE; + throw new BadMethodCallException(__METHOD__ . ' is not implemented'); } @@ -332,7 +322,7 @@ class DibiOracleResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { oci_free_statement($this->resource); } diff --git a/dibi/drivers/pdo.php b/dibi/drivers/pdo.php index 71bb0f65..31d9bf22 100644 --- a/dibi/drivers/pdo.php +++ b/dibi/drivers/pdo.php @@ -104,7 +104,7 @@ class DibiPdoDriver extends DibiDriver protected function doQuery($sql) { $res = $this->getConnection()->query($sql); - return $res instanceof PDOStatement ? new DibiPdoResult($res) : NULL; + return $res instanceof PDOStatement ? new DibiPdoResult($res, TRUE) : NULL; } @@ -169,23 +169,6 @@ class DibiPdoDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - $error = $this->getConnection()->errorInfo(); - return array( - 'message' => $error[2], - 'code' => $error[1], - 'SQLSTATE '=> $error[0], - ); - } - - - /** * Escapes the string * @@ -270,7 +253,7 @@ class DibiPdoResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return $this->resource->rowCount(); } @@ -294,9 +277,10 @@ class DibiPdoResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { $this->row = $row; } @@ -308,7 +292,7 @@ class DibiPdoResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { } diff --git a/dibi/drivers/postgre.php b/dibi/drivers/postgre.php index 846f2f83..9e034b31 100644 --- a/dibi/drivers/postgre.php +++ b/dibi/drivers/postgre.php @@ -81,25 +81,16 @@ class DibiPostgreDriver extends DibiDriver $config = $this->getConfig(); - // some errors aren't handled. Must use $php_errormsg - if (function_exists('ini_set')) { - $save = ini_set('track_errors', TRUE); - } - - $php_errormsg = ''; - + DibiDatabaseException::catchError(); if (isset($config['persistent'])) { $connection = @pg_connect($config['database'], PGSQL_CONNECT_FORCE_NEW); } else { $connection = @pg_pconnect($config['database'], PGSQL_CONNECT_FORCE_NEW); } - - if (function_exists('ini_set')) { - ini_set('track_errors', $save); - } + DibiDatabaseException::restore(); if (!is_resource($connection)) { - throw new DibiDatabaseException($php_errormsg); + throw new DibiDatabaseException('unknown error'); } if (isset($config['charset'])) { @@ -146,7 +137,7 @@ class DibiPostgreDriver extends DibiDriver $this->affectedRows = pg_affected_rows($res); if ($this->affectedRows < 0) $this->affectedRows = FALSE; } - return new DibiPostgreResult($res); + return new DibiPostgreResult($res, TRUE); } } @@ -225,21 +216,6 @@ class DibiPostgreDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - return array( - 'message' => pg_last_error($this->getConnection()), - 'code' => NULL, - ); - } - - - /** * Escapes the string * @@ -326,7 +302,7 @@ class DibiPostgreResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return pg_num_rows($this->resource); } @@ -350,11 +326,14 @@ class DibiPostgreResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { - return pg_result_seek($this->resource, $row); + if (!pg_result_seek($this->resource, $row)) { + throw new DibiDriverException('Unable to seek to row ' . $row); + } } @@ -364,7 +343,7 @@ class DibiPostgreResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { pg_free_result($this->resource); } diff --git a/dibi/drivers/sqlite.php b/dibi/drivers/sqlite.php index 4c6061c1..ffef507e 100644 --- a/dibi/drivers/sqlite.php +++ b/dibi/drivers/sqlite.php @@ -113,10 +113,11 @@ class DibiSqliteDriver extends DibiDriver $connection = $this->getConnection(); $errorMsg = NULL; - if ($this->getConfig('unbuffered')) { - $res = @sqlite_unbuffered_query($connection, $sql, SQLITE_ASSOC, $errorMsg); - } else { + $buffered = !$this->getConfig('unbuffered'); + if ($buffered) { $res = @sqlite_query($connection, $sql, SQLITE_ASSOC, $errorMsg); + } else { + $res = @sqlite_unbuffered_query($connection, $sql, SQLITE_ASSOC, $errorMsg); } @@ -124,7 +125,7 @@ class DibiSqliteDriver extends DibiDriver throw new DibiDatabaseException($errorMsg, sqlite_last_error($connection), $sql); } - return is_resource($res) ? new DibiSqliteResult($res) : NULL; + return is_resource($res) ? new DibiSqliteResult($res, $buffered) : NULL; } @@ -191,22 +192,6 @@ class DibiSqliteDriver extends DibiDriver - /** - * Returns last error - * - * @return array with items 'message' and 'code' - */ - public function errorInfo() - { - $code = sqlite_last_error($this->getConnection()); - return array( - 'message' => sqlite_error_string($code), - 'code' => $code, - ); - } - - - /** * Escapes the string * @@ -288,7 +273,7 @@ class DibiSqliteResult extends DibiResult * * @return int */ - public function rowCount() + protected function doRowCount() { return sqlite_num_rows($this->resource); } @@ -312,11 +297,14 @@ class DibiSqliteResult extends DibiResult * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - public function seek($row) + protected function doSeek($row) { - return sqlite_seek($this->resource, $row); + DibiDatabaseException::catchError(); + sqlite_seek($this->resource, $row); + DibiDatabaseException::restore(); } @@ -326,7 +314,7 @@ class DibiSqliteResult extends DibiResult * * @return void */ - protected function free() + protected function doFree() { } diff --git a/dibi/libs/DibiDriver.php b/dibi/libs/DibiDriver.php index d4527758..bbd725a9 100644 --- a/dibi/libs/DibiDriver.php +++ b/dibi/libs/DibiDriver.php @@ -303,10 +303,12 @@ abstract class DibiDriver extends NObject /** * Returns last error - * - * @return array with items 'message' and 'code' + * @deprecated */ - abstract public function errorInfo(); + public function errorInfo() + { + throw new BadMethodCallException(__METHOD__ . ' has been deprecated'); + } diff --git a/dibi/libs/DibiException.php b/dibi/libs/DibiException.php index e63c47ab..7241fbf0 100644 --- a/dibi/libs/DibiException.php +++ b/dibi/libs/DibiException.php @@ -45,6 +45,9 @@ class DibiDatabaseException extends DibiException /** @var string */ private $sql; + /** @var callback */ + private static $oldHandler; + public function __construct($message = NULL, $code = 0, $sql = NULL) { @@ -71,4 +74,31 @@ class DibiDatabaseException extends DibiException return $s; } -} + + + public static function _catchErrorHandler($errno, $errstr) + { + self::restore(); + throw new self($errstr, $errno); + } + + + + public static function catchError() + { + self::$oldHandler = set_error_handler(array(__CLASS__, '_catchErrorHandler'), E_ALL); + } + + + + public static function restore() + { + if (self::$oldHandler) { + set_error_handler(self::$oldHandler); + self::$oldHandler = NULL; + } else { + restore_error_handler(); + } + } + +} \ No newline at end of file diff --git a/dibi/libs/DibiResult.php b/dibi/libs/DibiResult.php index 3737984d..6ccb0666 100644 --- a/dibi/libs/DibiResult.php +++ b/dibi/libs/DibiResult.php @@ -54,13 +54,25 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl */ protected $meta; - /** * Resultset resource * @var resource */ protected $resource; + /** + * Is buffered (seekable and countable)? + * @var bool + */ + protected $buffered; + + /** + * Already fetched? Used for allowance for first seek(0) + * @var bool + */ + protected $fetched = FALSE; + + private static $types = array( dibi::FIELD_TEXT => 'string', @@ -74,9 +86,10 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl - public function __construct($resource) + public function __construct($resource, $buffered) { $this->resource = $resource; + $this->buffered = $buffered; } @@ -96,9 +109,21 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl * Moves cursor position without fetching row * * @param int the 0-based cursor pos to seek to - * @return boolean TRUE on success, FALSE if unable to seek to specified record + * @return void + * @throws DibiException */ - abstract public function seek($row); + final public function seek($row) + { + if ($row === 0 && !$this->fetched) { + return TRUE; + } + + if (!$this->buffered) { + throw new BadMethodCallException(__METHOD__ . ' is not allowed for unbuffered queries'); + } + + return $this->doSeek($row); + } @@ -107,16 +132,43 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl * * @return int */ - abstract public function rowCount(); + final public function rowCount() + { + if (!$this->buffered) { + throw new BadMethodCallException(__METHOD__ . ' is not allowed for unbuffered queries'); + } + + return $this->doRowCount(); + } /** - * Frees the resources allocated for this result set + * Internal: Moves cursor position without fetching row + * + * @param int the 0-based cursor pos to seek to + * @return void + * @throws DibiException + */ + abstract protected function doSeek($row); + + + + /** + * Internal: Returns the number of rows in a result set + * + * @return int + */ + abstract protected function doRowCount(); + + + + /** + * Internal: Frees the resources allocated for this result set * * @return void */ - abstract protected function free(); + abstract protected function doFree(); @@ -140,6 +192,7 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl { $row = $this->doFetch(); if (!is_array($row)) return FALSE; + $this->fetched = TRUE; // types-converting? if ($t = $this->convert) { // little speed-up @@ -164,6 +217,7 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl { $row = $this->doFetch(); if (!is_array($row)) return FALSE; + $this->fetched = TRUE; // types-converting? if ($t = $this->convert) { // little speed-up @@ -186,7 +240,7 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl */ final function fetchAll() { - @$this->seek(0); + $this->seek(0); $row = $this->fetch(); if (!$row) return array(); // empty resultset @@ -220,7 +274,7 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl */ final function fetchAssoc($assoc) { - @$this->seek(0); + $this->seek(0); $row = $this->fetch(); if (!$row) return array(); // empty resultset @@ -288,7 +342,7 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl */ final function fetchPairs($key = NULL, $value = NULL) { - @$this->seek(0); + $this->seek(0); $row = $this->fetch(); if (!$row) return array(); // empty resultset @@ -341,7 +395,7 @@ abstract class DibiResult extends NObject implements IteratorAggregate, Countabl */ public function __destruct() { - @$this->free(); + @$this->doFree(); } diff --git a/dibi/libs/DibiResultIterator.php b/dibi/libs/DibiResultIterator.php index a9657b56..7589954d 100644 --- a/dibi/libs/DibiResultIterator.php +++ b/dibi/libs/DibiResultIterator.php @@ -74,7 +74,7 @@ final class DibiResultIterator implements Iterator public function rewind() { $this->pointer = 0; - @$this->result->seek($this->offset); + $this->result->seek($this->offset); $this->row = $this->result->fetch(); } diff --git a/examples/fetch.php b/examples/fetch.php index 23c99a7f..8a91d8a1 100644 --- a/examples/fetch.php +++ b/examples/fetch.php @@ -25,8 +25,6 @@ product_id | title // fetch a single value $res = dibi::query('SELECT [title] FROM [products]'); -if (!$res) die('SQL error'); - $value = $res->fetchSingle(); print_r($value); // Chair echo '
'; diff --git a/examples/metatypes.php b/examples/metatypes.php index bae9d375..453ae903 100644 --- a/examples/metatypes.php +++ b/examples/metatypes.php @@ -12,8 +12,6 @@ dibi::connect(array( $res = dibi::query('SELECT * FROM [customers]'); -if (!$res) die('SQL error'); - // auto-convert this field to integer $res->setType('customer_id', Dibi::FIELD_INTEGER);