|
|
|
@ -23,7 +23,8 @@ |
|
|
|
class DatabaseException extends Exception{ |
|
|
|
private $query; |
|
|
|
|
|
|
|
public function __construct($message, $query){ |
|
|
|
//FIXME getQuery seems to be unused, maybe use parent constructor with $message, $code and $previous
|
|
|
|
public function __construct($message, $query = null){ |
|
|
|
parent::__construct($message); |
|
|
|
$this->query = $query; |
|
|
|
} |
|
|
|
@ -391,10 +392,63 @@ class OC_DB { |
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @brief execute a prepared statement, on error write log and throw exception |
|
|
|
* @param mixed $stmt PDOStatementWrapper | MDB2_Statement_Common , |
|
|
|
* an array with 'sql' and optionally 'limit' and 'offset' keys |
|
|
|
* .. or a simple sql query string |
|
|
|
* @param array $parameters |
|
|
|
* @return result |
|
|
|
* @throws DatabaseException |
|
|
|
*/ |
|
|
|
static public function executeAudited( $stmt, array $parameters = null) { |
|
|
|
if (is_string($stmt)) { |
|
|
|
// convert to an array with 'sql'
|
|
|
|
if (stripos($stmt,'LIMIT') !== false) { //OFFSET requires LIMIT, se we only neet to check for LIMIT
|
|
|
|
// TODO try to convert LIMIT OFFSET notation to parameters, see fixLimitClauseForMSSQL
|
|
|
|
$message = 'LIMIT and OFFSET are forbidden for portability reasons,' |
|
|
|
. ' pass an array with \'limit\' and \'offset\' instead'; |
|
|
|
\OCP\Util::writeLog('db', $message, \OCP\Util::FATAL); |
|
|
|
throw new DatabaseException($message); |
|
|
|
} |
|
|
|
$stmt = array('sql' => $stmt, 'limit' => null, 'offset' => null); |
|
|
|
} |
|
|
|
if (is_array($stmt)){ |
|
|
|
// convert to prepared statement
|
|
|
|
if ( ! array_key_exists('sql', $stmt) ) { |
|
|
|
$message = 'statement array must at least contain key \'sql\''; |
|
|
|
\OCP\Util::writeLog('db', $message, \OCP\Util::FATAL); |
|
|
|
throw new DatabaseException($message); |
|
|
|
} |
|
|
|
if ( ! array_key_exists('limit', $stmt) ) { |
|
|
|
$stmt['limit'] = null; |
|
|
|
} |
|
|
|
if ( ! array_key_exists('limit', $stmt) ) { |
|
|
|
$stmt['offset'] = null; |
|
|
|
} |
|
|
|
$stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']); |
|
|
|
} |
|
|
|
self::raiseExceptionOnError($stmt, 'Could not prepare statement'); |
|
|
|
if ($stmt instanceof PDOStatementWrapper || $stmt instanceof MDB2_Statement_Common) { |
|
|
|
$result = $stmt->execute($parameters); |
|
|
|
self::raiseExceptionOnError($result, 'Could not execute statement'); |
|
|
|
} else { |
|
|
|
if (is_object($stmt)) { |
|
|
|
$message = 'Expected a prepared statement or array got ' . get_class($stmt); |
|
|
|
} else { |
|
|
|
$message = 'Expected a prepared statement or array got ' . gettype($stmt); |
|
|
|
} |
|
|
|
\OCP\Util::writeLog('db', $message, \OCP\Util::FATAL); |
|
|
|
throw new DatabaseException($message); |
|
|
|
} |
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @brief gets last value of autoincrement |
|
|
|
* @param string $table The optional table name (will replace *PREFIX*) and add sequence suffix |
|
|
|
* @return int id |
|
|
|
* @throws DatabaseException |
|
|
|
* |
|
|
|
* MDB2 lastInsertID() |
|
|
|
* |
|
|
|
@ -404,25 +458,27 @@ class OC_DB { |
|
|
|
public static function insertid($table=null) { |
|
|
|
self::connect(); |
|
|
|
$type = OC_Config::getValue( "dbtype", "sqlite" ); |
|
|
|
if( $type == 'pgsql' ) { |
|
|
|
$query = self::prepare('SELECT lastval() AS id'); |
|
|
|
$row = $query->execute()->fetchRow(); |
|
|
|
if( $type === 'pgsql' ) { |
|
|
|
$result = self::executeAudited('SELECT lastval() AS id'); |
|
|
|
$row = $result->fetchRow(); |
|
|
|
self::raiseExceptionOnError($row, 'fetching row for insertid failed'); |
|
|
|
return $row['id']; |
|
|
|
} |
|
|
|
if( $type == 'mssql' ) { |
|
|
|
} else if( $type === 'mssql') { |
|
|
|
if($table !== null) { |
|
|
|
$prefix = OC_Config::getValue( "dbtableprefix", "oc_" ); |
|
|
|
$table = str_replace( '*PREFIX*', $prefix, $table ); |
|
|
|
} |
|
|
|
return self::$connection->lastInsertId($table); |
|
|
|
}else{ |
|
|
|
$result = self::$connection->lastInsertId($table); |
|
|
|
} else { |
|
|
|
if($table !== null) { |
|
|
|
$prefix = OC_Config::getValue( "dbtableprefix", "oc_" ); |
|
|
|
$suffix = OC_Config::getValue( "dbsequencesuffix", "_id_seq" ); |
|
|
|
$table = str_replace( '*PREFIX*', $prefix, $table ).$suffix; |
|
|
|
} |
|
|
|
return self::$connection->lastInsertId($table); |
|
|
|
$result = self::$connection->lastInsertId($table); |
|
|
|
} |
|
|
|
self::raiseExceptionOnError($result, 'insertid failed'); |
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -512,6 +568,8 @@ class OC_DB { |
|
|
|
|
|
|
|
//clean up memory
|
|
|
|
unlink( $file2 ); |
|
|
|
|
|
|
|
self::raiseExceptionOnError($definition,'Failed to parse the database definition'); |
|
|
|
|
|
|
|
// Die in case something went wrong
|
|
|
|
if( $definition instanceof MDB2_Schema_Error ) { |
|
|
|
@ -528,11 +586,7 @@ class OC_DB { |
|
|
|
|
|
|
|
$ret=self::$schema->createDatabase( $definition ); |
|
|
|
|
|
|
|
// Die in case something went wrong
|
|
|
|
if( $ret instanceof MDB2_Error ) { |
|
|
|
OC_Template::printErrorPage( self::$MDB2->getDebugOutput().' '.$ret->getMessage() . ': ' |
|
|
|
. $ret->getUserInfo() ); |
|
|
|
} |
|
|
|
self::raiseExceptionOnError($ret,'Failed to create the database structure'); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
@ -552,13 +606,7 @@ class OC_DB { |
|
|
|
$content = file_get_contents( $file ); |
|
|
|
|
|
|
|
$previousSchema = self::$schema->getDefinitionFromDatabase(); |
|
|
|
if (PEAR::isError($previousSchema)) { |
|
|
|
$error = $previousSchema->getMessage(); |
|
|
|
$detail = $previousSchema->getDebugInfo(); |
|
|
|
$message = 'Failed to get existing database structure for updating ('.$error.', '.$detail.')'; |
|
|
|
OC_Log::write('core', $message, OC_Log::FATAL); |
|
|
|
throw new Exception($message); |
|
|
|
} |
|
|
|
self::raiseExceptionOnError($previousSchema,'Failed to get existing database structure for updating'); |
|
|
|
|
|
|
|
// Make changes and save them to an in-memory file
|
|
|
|
$file2 = 'static://db_scheme'; |
|
|
|
@ -582,13 +630,7 @@ class OC_DB { |
|
|
|
//clean up memory
|
|
|
|
unlink( $file2 ); |
|
|
|
|
|
|
|
if (PEAR::isError($op)) { |
|
|
|
$error = $op->getMessage(); |
|
|
|
$detail = $op->getDebugInfo(); |
|
|
|
$message = 'Failed to update database structure ('.$error.', '.$detail.')'; |
|
|
|
OC_Log::write('core', $message, OC_Log::FATAL); |
|
|
|
throw new Exception($message); |
|
|
|
} |
|
|
|
self::raiseExceptionOnError($op,'Failed to update database structure'); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
@ -641,15 +683,9 @@ class OC_DB { |
|
|
|
} |
|
|
|
$query = substr($query, 0, strlen($query) - 5); |
|
|
|
try { |
|
|
|
$stmt = self::prepare($query); |
|
|
|
$result = $stmt->execute($inserts); |
|
|
|
|
|
|
|
} catch(PDOException $e) { |
|
|
|
$entry = 'DB Error: "'.$e->getMessage() . '"<br />'; |
|
|
|
$entry .= 'Offending command was: ' . $query . '<br />'; |
|
|
|
OC_Log::write('core', $entry, OC_Log::FATAL); |
|
|
|
error_log('DB error: '.$entry); |
|
|
|
OC_Template::printErrorPage( $entry ); |
|
|
|
$result = self::executeAudited($query, $inserts); |
|
|
|
} catch(DatabaseException $e) { |
|
|
|
OC_Template::printExceptionErrorPage( $e ); |
|
|
|
} |
|
|
|
|
|
|
|
if((int)$result->numRows() === 0) { |
|
|
|
@ -674,16 +710,12 @@ class OC_DB { |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
$result = self::prepare($query); |
|
|
|
$result = self::executeAudited($query, $inserts); |
|
|
|
} catch(PDOException $e) { |
|
|
|
$entry = 'DB Error: "'.$e->getMessage() . '"<br />'; |
|
|
|
$entry .= 'Offending command was: ' . $query.'<br />'; |
|
|
|
OC_Log::write('core', $entry, OC_Log::FATAL); |
|
|
|
error_log('DB error: ' . $entry); |
|
|
|
OC_Template::printErrorPage( $entry ); |
|
|
|
OC_Template::printExceptionErrorPage( $e ); |
|
|
|
} |
|
|
|
|
|
|
|
return $result->execute($inserts); |
|
|
|
return $result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -891,7 +923,33 @@ class OC_DB { |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
* check if a result is an error, writes a log entry and throws an exception, works with MDB2 and PDOException |
|
|
|
* @param mixed $result |
|
|
|
* @param string message |
|
|
|
* @return void |
|
|
|
* @throws DatabaseException |
|
|
|
*/ |
|
|
|
public static function raiseExceptionOnError($result, $message = null) { |
|
|
|
if(self::isError($result)) { |
|
|
|
if ($message === null) { |
|
|
|
$message = self::getErrorMessage($result); |
|
|
|
} else { |
|
|
|
$message .= ', Root cause:' . self::getErrorMessage($result); |
|
|
|
} |
|
|
|
OC_Log::write('db', $message, OC_Log::FATAL); |
|
|
|
throw new DatabaseException($message, getErrorCode($result)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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(); |
|
|
|
} |
|
|
|
return $code; |
|
|
|
} |
|
|
|
/** |
|
|
|
* returns the error code and message as a string for logging |
|
|
|
* works with MDB2 and PDOException |
|
|
|
@ -901,9 +959,7 @@ class OC_DB { |
|
|
|
public static function getErrorMessage($error) { |
|
|
|
if ( self::$backend==self::BACKEND_MDB2 and PEAR::isError($error) ) { |
|
|
|
$msg = $error->getCode() . ': ' . $error->getMessage(); |
|
|
|
if (defined('DEBUG') && DEBUG) { |
|
|
|
$msg .= '(' . $error->getDebugInfo() . ')'; |
|
|
|
} |
|
|
|
$msg .= ' (' . $error->getDebugInfo() . ')'; |
|
|
|
} elseif (self::$backend==self::BACKEND_PDO and self::$PDO) { |
|
|
|
$msg = self::$PDO->errorCode() . ': '; |
|
|
|
$errorInfo = self::$PDO->errorInfo(); |
|
|
|
|