diff options
Diffstat (limited to 'lib/db.php')
-rw-r--r-- | lib/db.php | 266 |
1 files changed, 150 insertions, 116 deletions
diff --git a/lib/db.php b/lib/db.php index a6b81aaba69..a93b9eccbdf 100644 --- a/lib/db.php +++ b/lib/db.php @@ -180,28 +180,18 @@ class OC_DB { $dsn = 'oci:dbname=//' . $host . '/' . $name; } break; - case 'mssql': + case 'mssql': if ($port) { $dsn='sqlsrv:Server='.$host.','.$port.';Database='.$name; } else { $dsn='sqlsrv:Server='.$host.';Database='.$name; } - break; + break; default: return false; } - try{ - self::$PDO=new PDO($dsn, $user, $pass, $opts); - }catch(PDOException $e) { - OC_Log::write('core', $e->getMessage(), OC_Log::FATAL); - OC_User::setUserId(null); - - // send http status 503 - header('HTTP/1.1 503 Service Temporarily Unavailable'); - header('Status: 503 Service Temporarily Unavailable'); - OC_Template::printErrorPage('Failed to connect to database'); - die(); - } + self::$PDO=new PDO($dsn, $user, $pass, $opts); + // We always, really always want associative arrays self::$PDO->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); self::$PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); @@ -299,19 +289,8 @@ class OC_DB { // Try to establish connection self::$MDB2 = MDB2::factory( $dsn, $options ); - - // Die if we could not connect - if( PEAR::isError( self::$MDB2 )) { - OC_Log::write('core', self::$MDB2->getUserInfo(), OC_Log::FATAL); - OC_Log::write('core', self::$MDB2->getMessage(), OC_Log::FATAL); - OC_User::setUserId(null); - - // send http status 503 - header('HTTP/1.1 503 Service Temporarily Unavailable'); - header('Status: 503 Service Temporarily Unavailable'); - OC_Template::printErrorPage('Failed to connect to database'); - die(); - } + + self::raiseExceptionOnError( self::$MDB2 ); // We always, really always want associative arrays self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); @@ -326,11 +305,12 @@ class OC_DB { * @param string $query Query string * @param int $limit * @param int $offset + * @param bool $isManipulation * @return MDB2_Statement_Common prepared SQL query * * SQL query via MDB2 prepare(), needs to be execute()'d! */ - static public function prepare( $query , $limit=null, $offset=null ) { + static public function prepare( $query , $limit = null, $offset = null, $isManipulation = null) { if (!is_null($limit) && $limit != -1) { if (self::$backend == self::BACKEND_MDB2) { @@ -367,12 +347,23 @@ class OC_DB { OC_Log::write('core', 'DB prepare : '.$query, OC_Log::DEBUG); } self::connect(); + + if ($isManipulation === null) { + //try to guess, so we return the number of rows on manipulations + $isManipulation = self::isManipulation($query); + } + // return the result if(self::$backend==self::BACKEND_MDB2) { - $result = self::$connection->prepare( $query ); + // differentiate between query and manipulation + if ($isManipulation) { + $result = self::$connection->prepare( $query, null, MDB2_PREPARE_MANIP ); + } else { + $result = self::$connection->prepare( $query, null, MDB2_PREPARE_RESULT ); + } // Die if we have an error (error means: bad query, not 0 results!) - if( PEAR::isError($result)) { + if( self::isError($result)) { throw new DatabaseException($result->getMessage(), $query); } }else{ @@ -381,7 +372,8 @@ class OC_DB { }catch(PDOException $e) { throw new DatabaseException($e->getMessage(), $query); } - $result=new PDOStatementWrapper($result); + // differentiate between query and manipulation + $result = new PDOStatementWrapper($result, $isManipulation); } if ((is_null($limit) || $limit == -1) and self::$cachingEnabled ) { $type = OC_Config::getValue( "dbtype", "sqlite" ); @@ -391,7 +383,33 @@ class OC_DB { } return $result; } - + + /** + * tries to guess the type of statement based on the first 10 characters + * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements + * + * @param string $sql + */ + static public function isManipulation( $sql ) { + $selectOccurence = stripos ($sql, "SELECT"); + if ($selectOccurence !== false && $selectOccurence < 10) { + return false; + } + $insertOccurence = stripos ($sql, "INSERT"); + if ($insertOccurence !== false && $insertOccurence < 10) { + return true; + } + $updateOccurence = stripos ($sql, "UPDATE"); + if ($updateOccurence !== false && $updateOccurence < 10) { + return true; + } + $deleteOccurance = stripos ($sql, "DELETE"); + if ($deleteOccurance !== false && $deleteOccurance < 10) { + return true; + } + return false; + } + /** * @brief execute a prepared statement, on error write log and throw exception * @param mixed $stmt PDOStatementWrapper | MDB2_Statement_Common , @@ -718,6 +736,9 @@ class OC_DB { } catch(PDOException $e) { OC_Template::printExceptionErrorPage( $e ); } + if ($result === 0) { + return true; + } return $result; } @@ -754,15 +775,16 @@ class OC_DB { }elseif( $type == 'oci' ) { $query = str_replace( '`', '"', $query ); $query = str_ireplace( 'NOW()', 'CURRENT_TIMESTAMP', $query ); + $query = str_ireplace( 'UNIX_TIMESTAMP()', '((CAST(SYS_EXTRACT_UTC(systimestamp) AS DATE))-TO_DATE(\'1970101000000\',\'YYYYMMDDHH24MiSS\'))*24*3600', $query ); }elseif( $type == 'mssql' ) { $query = preg_replace( "/\`(.*?)`/", "[$1]", $query ); $query = str_replace( 'NOW()', 'CURRENT_TIMESTAMP', $query ); $query = str_replace( 'now()', 'CURRENT_TIMESTAMP', $query ); $query = str_replace( 'LENGTH(', 'LEN(', $query ); $query = str_replace( 'SUBSTR(', 'SUBSTRING(', $query ); - - $query = self::fixLimitClauseForMSSQL($query); - } + + $query = self::fixLimitClauseForMSSQL($query); + } // replace table name prefix $query = str_replace( '*PREFIX*', $prefix, $query ); @@ -770,60 +792,60 @@ class OC_DB { return $query; } - private static function fixLimitClauseForMSSQL($query) { - $limitLocation = stripos ($query, "LIMIT"); - - if ( $limitLocation === false ) { - return $query; - } - - // total == 0 means all results - not zero results - // - // First number is either total or offset, locate it by first space - // - $offset = substr ($query, $limitLocation + 5); - $offset = substr ($offset, 0, stripos ($offset, ' ')); - $offset = trim ($offset); - - // check for another parameter - if (stripos ($offset, ',') === false) { - // no more parameters - $offset = 0; - $total = intval ($offset); - } else { - // found another parameter - $offset = intval ($offset); - - $total = substr ($query, $limitLocation + 5); - $total = substr ($total, stripos ($total, ',')); - - $total = substr ($total, 0, stripos ($total, ' ')); - $total = intval ($total); - } - - $query = trim (substr ($query, 0, $limitLocation)); - - if ($offset == 0 && $total !== 0) { - if (strpos($query, "SELECT") === false) { - $query = "TOP {$total} " . $query; - } else { - $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); - } - } else if ($offset > 0) { - $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); - $query = 'SELECT * - FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 - FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; - - if ($total > 0) { - $query .= ' WHERE line3 BETWEEN ' . ($offset + 1) . ' AND ' . ($offset + $total); - } else { - $query .= ' WHERE line3 > ' . $offset; - } - } - return $query; - } - + private static function fixLimitClauseForMSSQL($query) { + $limitLocation = stripos ($query, "LIMIT"); + + if ( $limitLocation === false ) { + return $query; + } + + // total == 0 means all results - not zero results + // + // First number is either total or offset, locate it by first space + // + $offset = substr ($query, $limitLocation + 5); + $offset = substr ($offset, 0, stripos ($offset, ' ')); + $offset = trim ($offset); + + // check for another parameter + if (stripos ($offset, ',') === false) { + // no more parameters + $offset = 0; + $total = intval ($offset); + } else { + // found another parameter + $offset = intval ($offset); + + $total = substr ($query, $limitLocation + 5); + $total = substr ($total, stripos ($total, ',')); + + $total = substr ($total, 0, stripos ($total, ' ')); + $total = intval ($total); + } + + $query = trim (substr ($query, 0, $limitLocation)); + + if ($offset == 0 && $total !== 0) { + if (strpos($query, "SELECT") === false) { + $query = "TOP {$total} " . $query; + } else { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); + } + } else if ($offset > 0) { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); + $query = 'SELECT * + FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 + FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; + + if ($total > 0) { + $query .= ' WHERE line3 BETWEEN ' . ($offset + 1) . ' AND ' . ($offset + $total); + } else { + $query .= ' WHERE line3 > ' . $offset; + } + } + return $query; + } + /** * @brief drop a table * @param string $tableName the table to drop @@ -919,18 +941,21 @@ class OC_DB { * @return bool */ public static function isError($result) { - if(!$result) { + //MDB2 returns an MDB2_Error object + if (class_exists('PEAR') === true && PEAR::isError($result)) { return true; - }elseif(self::$backend==self::BACKEND_MDB2 and PEAR::isError($result)) { + } + //PDO returns false on error (and throws an exception) + if (self::$backend===self::BACKEND_PDO and $result === false) { return true; - }else{ - return false; } + + return false; } /** * check if a result is an error and throws an exception, works with MDB2 and PDOException * @param mixed $result - * @param string message + * @param string $message * @return void * @throws DatabaseException */ @@ -946,12 +971,15 @@ class OC_DB { } public static function getErrorCode($error) { - if ( self::$backend==self::BACKEND_MDB2 and PEAR::isError($error) ) { - $code = $error->getCode(); - } elseif ( self::$backend==self::BACKEND_PDO and self::$PDO ) { - $code = self::$PDO->errorCode(); + if ( class_exists('PEAR') === true && PEAR::isError($error) ) { + /** @var $error PEAR_Error */ + return $error->getCode(); + } + if ( self::$backend==self::BACKEND_PDO and self::$PDO ) { + return self::$PDO->errorCode(); } - return $code; + + return -1; } /** * returns the error code and message as a string for logging @@ -960,23 +988,24 @@ class OC_DB { * @return string */ public static function getErrorMessage($error) { - if ( self::$backend==self::BACKEND_MDB2 and PEAR::isError($error) ) { + if ( class_exists('PEAR') === true && PEAR::isError($error) ) { $msg = $error->getCode() . ': ' . $error->getMessage(); $msg .= ' (' . $error->getDebugInfo() . ')'; - } elseif (self::$backend==self::BACKEND_PDO and self::$PDO) { + + return $msg; + } + if (self::$backend==self::BACKEND_PDO and self::$PDO) { $msg = self::$PDO->errorCode() . ': '; $errorInfo = self::$PDO->errorInfo(); if (is_array($errorInfo)) { $msg .= 'SQLSTATE = '.$errorInfo[0] . ', '; $msg .= 'Driver Code = '.$errorInfo[1] . ', '; $msg .= 'Driver Message = '.$errorInfo[2]; - }else{ - $msg = ''; } - }else{ - $msg = ''; + return $msg; } - return $msg; + + return ''; } /** @@ -997,11 +1026,13 @@ class PDOStatementWrapper{ /** * @var PDOStatement */ - private $statement=null; - private $lastArguments=array(); + private $statement = null; + private $isManipulation = false; + private $lastArguments = array(); - public function __construct($statement) { - $this->statement=$statement; + public function __construct($statement, $isManipulation = false) { + $this->statement = $statement; + $this->isManipulation = $isManipulation; } /** @@ -1023,16 +1054,19 @@ class PDOStatementWrapper{ $input = $this->tryFixSubstringLastArgumentDataForMSSQL($input); } - $result=$this->statement->execute($input); + $result = $this->statement->execute($input); } else { - $result=$this->statement->execute(); + $result = $this->statement->execute(); } - if ($result) { - return $this; - } else { + if ($result === false) { return false; } + if ($this->isManipulation) { + return $this->statement->rowCount(); + } else { + return $this; + } } private function tryFixSubstringLastArgumentDataForMSSQL($input) { @@ -1124,7 +1158,7 @@ class PDOStatementWrapper{ die ($entry); } } - + /** * provide numRows */ |