2011-04-16 08:17:40 +00:00
< ? php
/**
* ownCloud
*
* @ author Frank Karlitschek
2012-05-26 17:14:24 +00:00
* @ copyright 2012 Frank Karlitschek frank @ owncloud . org
2011-04-16 08:17:40 +00:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation ; either
* version 3 of the License , or any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details .
*
* You should have received a copy of the GNU Affero General Public
* License along with this library . If not , see < http :// www . gnu . org / licenses />.
*
*/
2012-12-07 15:09:29 +00:00
class DatabaseException extends Exception {
private $query ;
2013-06-10 10:56:45 +00:00
//FIXME getQuery seems to be unused, maybe use parent constructor with $message, $code and $previous
public function __construct ( $message , $query = null ){
2012-12-07 15:09:29 +00:00
parent :: __construct ( $message );
$this -> query = $query ;
}
public function getQuery (){
return $this -> query ;
}
}
2011-04-16 08:17:40 +00:00
/**
* This class manages the access to the database . It basically is a wrapper for
* MDB2 with some adaptions .
*/
class OC_DB {
2011-09-17 00:30:58 +00:00
const BACKEND_PDO = 0 ;
const BACKEND_MDB2 = 1 ;
2012-08-29 06:38:33 +00:00
2013-01-15 19:20:39 +00:00
static private $preparedQueries = array ();
2013-02-26 21:41:48 +00:00
static private $cachingEnabled = true ;
2013-01-15 19:20:39 +00:00
2012-09-22 23:52:34 +00:00
/**
* @ var MDB2_Driver_Common
*/
2011-10-16 21:03:03 +00:00
static private $connection ; //the prefered connection to use, either PDO or MDB2
2011-09-17 00:30:58 +00:00
static private $backend = null ;
2012-09-22 23:52:34 +00:00
/**
* @ var MDB2_Driver_Common
*/
static private $MDB2 = null ;
/**
* @ var PDO
*/
static private $PDO = null ;
/**
* @ var MDB2_Schema
*/
static private $schema = null ;
2012-02-09 18:51:24 +00:00
static private $inTransaction = false ;
2012-04-14 14:28:36 +00:00
static private $prefix = null ;
static private $type = null ;
2011-04-16 08:17:40 +00:00
2012-04-14 14:28:36 +00:00
/**
* check which backend we should use
2012-09-22 23:52:34 +00:00
* @ return int BACKEND_MDB2 or BACKEND_PDO
2012-04-14 14:28:36 +00:00
*/
2012-09-07 13:22:01 +00:00
private static function getDBBackend () {
2012-09-05 12:49:42 +00:00
//check if we can use PDO, else use MDB2 (installation always needs to be done my mdb2)
if ( class_exists ( 'PDO' ) && OC_Config :: getValue ( 'installed' , false )) {
2012-04-14 14:28:36 +00:00
$type = OC_Config :: getValue ( " dbtype " , " sqlite " );
2012-08-24 22:05:07 +00:00
if ( $type == 'oci' ) { //oracle also always needs mdb2
return self :: BACKEND_MDB2 ;
}
2012-04-14 14:28:36 +00:00
if ( $type == 'sqlite3' ) $type = 'sqlite' ;
$drivers = PDO :: getAvailableDrivers ();
2012-09-05 12:49:42 +00:00
if ( array_search ( $type , $drivers ) !== false ) {
2012-07-30 18:53:21 +00:00
return self :: BACKEND_PDO ;
2012-04-14 14:28:36 +00:00
}
}
2012-08-24 22:05:07 +00:00
return self :: BACKEND_MDB2 ;
2012-04-14 14:28:36 +00:00
}
2012-08-29 06:38:33 +00:00
2011-04-16 08:17:40 +00:00
/**
* @ brief connects to the database
2012-09-22 23:52:34 +00:00
* @ param int $backend
* @ return bool true if connection can be established or false on error
2011-04-16 08:17:40 +00:00
*
* Connects to the database as specified in config . php
*/
2012-09-07 13:22:01 +00:00
public static function connect ( $backend = null ) {
2012-09-05 12:49:42 +00:00
if ( self :: $connection ) {
2012-09-22 23:52:34 +00:00
return true ;
2011-10-16 18:47:25 +00:00
}
2012-09-05 12:49:42 +00:00
if ( is_null ( $backend )) {
2012-04-14 14:28:36 +00:00
$backend = self :: getDBBackend ();
2011-10-23 10:29:12 +00:00
}
2012-09-05 12:49:42 +00:00
if ( $backend == self :: BACKEND_PDO ) {
2012-09-22 23:52:34 +00:00
$success = self :: connectPDO ();
2011-09-17 00:30:58 +00:00
self :: $connection = self :: $PDO ;
self :: $backend = self :: BACKEND_PDO ;
} else {
2012-09-22 23:52:34 +00:00
$success = self :: connectMDB2 ();
2011-09-17 00:30:58 +00:00
self :: $connection = self :: $MDB2 ;
self :: $backend = self :: BACKEND_MDB2 ;
}
2012-09-22 23:52:34 +00:00
return $success ;
2011-09-17 00:30:58 +00:00
}
/**
* connect to the database using pdo
2012-09-22 23:52:34 +00:00
*
* @ return bool
2011-09-17 00:30:58 +00:00
*/
2012-09-07 13:22:01 +00:00
public static function connectPDO () {
2012-09-05 12:49:42 +00:00
if ( self :: $connection ) {
if ( self :: $backend == self :: BACKEND_MDB2 ) {
2012-01-08 01:57:52 +00:00
self :: disconnect ();
} else {
2012-09-22 23:52:34 +00:00
return true ;
2012-01-08 01:57:52 +00:00
}
}
2013-01-16 00:11:19 +00:00
self :: $preparedQueries = array ();
2011-04-16 08:17:40 +00:00
// The global data we need
2011-09-17 00:30:58 +00:00
$name = OC_Config :: getValue ( " dbname " , " owncloud " );
$host = OC_Config :: getValue ( " dbhost " , " " );
$user = OC_Config :: getValue ( " dbuser " , " " );
$pass = OC_Config :: getValue ( " dbpassword " , " " );
$type = OC_Config :: getValue ( " dbtype " , " sqlite " );
2012-09-05 12:49:42 +00:00
if ( strpos ( $host , ':' )) {
2012-11-02 18:53:02 +00:00
list ( $host , $port ) = explode ( ':' , $host , 2 );
2012-05-16 23:06:22 +00:00
} else {
$port = false ;
}
2012-04-08 01:30:06 +00:00
$opts = array ();
2011-10-16 18:47:25 +00:00
$datadir = OC_Config :: getValue ( " datadirectory " , OC :: $SERVERROOT . '/data' );
2012-08-29 06:38:33 +00:00
2011-09-17 00:30:58 +00:00
// do nothing if the connection already has been established
2012-09-05 12:49:42 +00:00
if ( ! self :: $PDO ) {
2011-09-17 00:30:58 +00:00
// Add the dsn according to the database type
2012-09-05 12:49:42 +00:00
switch ( $type ) {
2011-09-17 00:30:58 +00:00
case 'sqlite' :
$dsn = 'sqlite2:' . $datadir . '/' . $name . '.db' ;
break ;
case 'sqlite3' :
$dsn = 'sqlite:' . $datadir . '/' . $name . '.db' ;
break ;
case 'mysql' :
2012-09-05 12:49:42 +00:00
if ( $port ) {
2012-05-16 23:06:22 +00:00
$dsn = 'mysql:dbname=' . $name . ';host=' . $host . ';port=' . $port ;
} else {
$dsn = 'mysql:dbname=' . $name . ';host=' . $host ;
}
2012-04-08 01:30:06 +00:00
$opts [ PDO :: MYSQL_ATTR_INIT_COMMAND ] = " SET NAMES 'UTF8' " ;
2011-09-17 00:30:58 +00:00
break ;
case 'pgsql' :
2012-09-05 12:49:42 +00:00
if ( $port ) {
2012-05-16 23:06:22 +00:00
$dsn = 'pgsql:dbname=' . $name . ';host=' . $host . ';port=' . $port ;
} else {
$dsn = 'pgsql:dbname=' . $name . ';host=' . $host ;
}
2012-07-04 10:13:00 +00:00
/**
* Ugly fix for pg connections pbm when password use spaces
*/
$e_user = addslashes ( $user );
$e_password = addslashes ( $pass );
$pass = $user = null ;
$dsn .= " ;user=' $e_user ';password=' $e_password ' " ;
/** END OF FIX***/
2011-09-17 00:30:58 +00:00
break ;
2012-08-24 22:05:07 +00:00
case 'oci' : // Oracle with PDO is unsupported
2012-09-05 12:49:42 +00:00
if ( $port ) {
$dsn = 'oci:dbname=//' . $host . ':' . $port . '/' . $name ;
} else {
$dsn = 'oci:dbname=//' . $host . '/' . $name ;
}
break ;
2013-06-20 08:27:02 +00:00
case 'mssql' :
2013-02-07 23:00:51 +00:00
if ( $port ) {
$dsn = 'sqlsrv:Server=' . $host . ',' . $port . ';Database=' . $name ;
} else {
$dsn = 'sqlsrv:Server=' . $host . ';Database=' . $name ;
}
2013-06-20 08:27:02 +00:00
break ;
2012-09-22 23:52:34 +00:00
default :
return false ;
2011-09-17 00:30:58 +00:00
}
2013-06-20 08:27:02 +00:00
self :: $PDO = new PDO ( $dsn , $user , $pass , $opts );
2011-09-17 00:30:58 +00:00
// We always, really always want associative arrays
2012-09-05 12:49:42 +00:00
self :: $PDO -> setAttribute ( PDO :: ATTR_DEFAULT_FETCH_MODE , PDO :: FETCH_ASSOC );
self :: $PDO -> setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_EXCEPTION );
2011-09-17 00:30:58 +00:00
}
return true ;
}
2012-08-29 06:38:33 +00:00
2011-09-17 00:30:58 +00:00
/**
* connect to the database using mdb2
*/
2012-09-05 12:49:42 +00:00
public static function connectMDB2 () {
if ( self :: $connection ) {
if ( self :: $backend == self :: BACKEND_PDO ) {
2012-01-08 01:57:52 +00:00
self :: disconnect ();
} else {
2012-09-22 23:52:34 +00:00
return true ;
2012-01-08 01:57:52 +00:00
}
}
2013-01-16 00:11:19 +00:00
self :: $preparedQueries = array ();
2011-09-17 00:30:58 +00:00
// The global data we need
$name = OC_Config :: getValue ( " dbname " , " owncloud " );
$host = OC_Config :: getValue ( " dbhost " , " " );
$user = OC_Config :: getValue ( " dbuser " , " " );
$pass = OC_Config :: getValue ( " dbpassword " , " " );
$type = OC_Config :: getValue ( " dbtype " , " sqlite " );
$SERVERROOT = OC :: $SERVERROOT ;
2011-07-29 19:36:03 +00:00
$datadir = OC_Config :: getValue ( " datadirectory " , " $SERVERROOT /data " );
2011-04-16 08:17:40 +00:00
// do nothing if the connection already has been established
2012-09-05 12:49:42 +00:00
if ( ! self :: $MDB2 ) {
2011-04-16 08:17:40 +00:00
// Require MDB2.php (not required in the head of the file so we only load it when needed)
2012-09-05 12:49:42 +00:00
require_once 'MDB2.php' ;
2011-04-16 08:17:40 +00:00
// Prepare options array
$options = array (
2013-02-09 21:44:11 +00:00
'portability' => MDB2_PORTABILITY_ALL - MDB2_PORTABILITY_FIX_CASE ,
'log_line_break' => '<br>' ,
'idxname_format' => '%s' ,
'debug' => true ,
'quote_identifier' => true
);
2011-04-16 08:17:40 +00:00
// Add the dsn according to the database type
2012-09-07 13:22:01 +00:00
switch ( $type ) {
2011-09-17 00:30:58 +00:00
case 'sqlite' :
case 'sqlite3' :
$dsn = array (
'phptype' => $type ,
'database' => " $datadir / $name .db " ,
'mode' => '0644'
);
break ;
case 'mysql' :
$dsn = array (
'phptype' => 'mysql' ,
'username' => $user ,
'password' => $pass ,
'hostspec' => $host ,
'database' => $name
);
break ;
case 'pgsql' :
$dsn = array (
'phptype' => 'pgsql' ,
'username' => $user ,
'password' => $pass ,
'hostspec' => $host ,
'database' => $name
);
break ;
2012-08-24 22:05:07 +00:00
case 'oci' :
$dsn = array (
2013-05-08 14:18:24 +00:00
'phptype' => 'oci8' ,
'username' => $user ,
'password' => $pass ,
'service' => $name ,
'hostspec' => $host ,
'charset' => 'AL32UTF8' ,
2012-08-24 22:05:07 +00:00
);
2012-07-30 20:42:43 +00:00
break ;
2013-02-10 12:07:59 +00:00
case 'mssql' :
2013-02-07 23:00:51 +00:00
$dsn = array (
'phptype' => 'sqlsrv' ,
'username' => $user ,
'password' => $pass ,
'hostspec' => $host ,
2013-03-12 14:33:37 +00:00
'database' => $name ,
'charset' => 'UTF-8'
);
$options [ 'portability' ] = $options [ 'portability' ] - MDB2_PORTABILITY_EMPTY_TO_NULL ;
2013-02-10 12:07:59 +00:00
break ;
2012-09-22 23:52:34 +00:00
default :
return false ;
2011-04-16 08:17:40 +00:00
}
2012-08-29 06:38:33 +00:00
2011-04-16 08:17:40 +00:00
// Try to establish connection
2011-09-17 00:30:58 +00:00
self :: $MDB2 = MDB2 :: factory ( $dsn , $options );
2013-06-20 08:27:02 +00:00
self :: raiseExceptionOnError ( self :: $MDB2 );
2012-08-29 06:38:33 +00:00
2011-04-16 08:17:40 +00:00
// We always, really always want associative arrays
2011-09-17 00:30:58 +00:00
self :: $MDB2 -> setFetchMode ( MDB2_FETCHMODE_ASSOC );
2011-04-16 08:17:40 +00:00
}
2012-08-29 06:38:33 +00:00
2011-04-16 08:17:40 +00:00
// we are done. great!
return true ;
}
/**
* @ brief Prepare a SQL query
2012-09-22 23:52:34 +00:00
* @ param string $query Query string
* @ param int $limit
* @ param int $offset
2013-06-27 11:13:49 +00:00
* @ param bool $isManipulation
2012-09-22 23:52:34 +00:00
* @ return MDB2_Statement_Common prepared SQL query
2011-04-16 08:17:40 +00:00
*
* SQL query via MDB2 prepare (), needs to be execute () ' d !
*/
2013-06-20 12:46:22 +00:00
static public function prepare ( $query , $limit = null , $offset = null , $isManipulation = null ) {
2012-08-29 06:38:33 +00:00
2012-08-25 00:17:44 +00:00
if ( ! is_null ( $limit ) && $limit != - 1 ) {
2012-08-24 22:05:07 +00:00
if ( self :: $backend == self :: BACKEND_MDB2 ) {
//MDB2 uses or emulates limits & offset internally
self :: $MDB2 -> setLimit ( $limit , $offset );
} else {
//PDO does not handle limit and offset.
//FIXME: check limit notation for other dbs
//the following sql thus might needs to take into account db ways of representing it
//(oracle has no LIMIT / OFFSET)
2012-09-22 23:52:34 +00:00
$limit = ( int ) $limit ;
$limitsql = ' LIMIT ' . $limit ;
2012-08-24 22:05:07 +00:00
if ( ! is_null ( $offset )) {
2012-09-22 23:52:34 +00:00
$offset = ( int ) $offset ;
2012-08-24 22:05:07 +00:00
$limitsql .= ' OFFSET ' . $offset ;
}
//insert limitsql
if ( substr ( $query , - 1 ) == ';' ) { //if query ends with ;
$query = substr ( $query , 0 , - 1 ) . $limitsql . ';' ;
} else {
$query .= $limitsql ;
}
}
2013-01-15 19:20:39 +00:00
} else {
2013-02-26 21:41:48 +00:00
if ( isset ( self :: $preparedQueries [ $query ]) and self :: $cachingEnabled ) {
2013-01-15 19:20:39 +00:00
return self :: $preparedQueries [ $query ];
}
2012-08-24 22:05:07 +00:00
}
2013-01-15 19:20:39 +00:00
$rawQuery = $query ;
2012-08-24 22:05:07 +00:00
2011-04-16 08:17:40 +00:00
// Optimize the query
$query = self :: processQuery ( $query );
2013-03-30 21:36:55 +00:00
if ( OC_Config :: getValue ( " log_query " , false )) {
2013-03-30 16:40:46 +00:00
OC_Log :: write ( 'core' , 'DB prepare : ' . $query , OC_Log :: DEBUG );
}
2011-04-16 08:17:40 +00:00
self :: connect ();
2013-06-20 12:46:22 +00:00
if ( $isManipulation === null ) {
//try to guess, so we return the number of rows on manipulations
$isManipulation = self :: isManipulation ( $query );
}
2011-04-16 08:17:40 +00:00
// return the result
2012-09-05 12:49:42 +00:00
if ( self :: $backend == self :: BACKEND_MDB2 ) {
2013-06-20 12:46:22 +00:00
// 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 );
}
2011-09-17 00:30:58 +00:00
// Die if we have an error (error means: bad query, not 0 results!)
2013-06-20 12:46:22 +00:00
if ( self :: isError ( $result )) {
2012-12-07 15:09:29 +00:00
throw new DatabaseException ( $result -> getMessage (), $query );
2011-09-17 00:30:58 +00:00
}
} else {
try {
$result = self :: $connection -> prepare ( $query );
2012-09-07 13:22:01 +00:00
} catch ( PDOException $e ) {
2012-12-07 15:09:29 +00:00
throw new DatabaseException ( $e -> getMessage (), $query );
2011-09-17 00:30:58 +00:00
}
2013-06-20 12:46:22 +00:00
// differentiate between query and manipulation
2013-06-27 11:13:49 +00:00
$result = new PDOStatementWrapper ( $result , $isManipulation );
2011-04-16 08:17:40 +00:00
}
2013-02-26 21:41:48 +00:00
if (( is_null ( $limit ) || $limit == - 1 ) and self :: $cachingEnabled ) {
2013-02-26 21:41:05 +00:00
$type = OC_Config :: getValue ( " dbtype " , " sqlite " );
if ( $type != 'sqlite' && $type != 'sqlite3' ) {
self :: $preparedQueries [ $rawQuery ] = $result ;
}
2013-01-15 19:20:39 +00:00
}
2011-04-16 08:17:40 +00:00
return $result ;
}
2013-06-20 12:46:22 +00:00
/**
* 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 ;
}
2013-06-10 10:56:45 +00:00
/**
* @ 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' ;
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\'' ;
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 );
}
throw new DatabaseException ( $message );
}
return $result ;
}
2011-04-16 08:17:40 +00:00
/**
* @ brief gets last value of autoincrement
2012-09-22 23:52:34 +00:00
* @ param string $table The optional table name ( will replace * PREFIX * ) and add sequence suffix
* @ return int id
2013-06-10 10:56:45 +00:00
* @ throws DatabaseException
2011-04-16 08:17:40 +00:00
*
* MDB2 lastInsertID ()
*
* Call this method right after the insert command or other functions may
* cause trouble !
*/
2012-09-07 13:22:01 +00:00
public static function insertid ( $table = null ) {
2011-04-16 08:17:40 +00:00
self :: connect ();
2012-11-22 23:23:27 +00:00
$type = OC_Config :: getValue ( " dbtype " , " sqlite " );
2013-06-10 10:56:45 +00:00
if ( $type === 'pgsql' ) {
$result = self :: executeAudited ( 'SELECT lastval() AS id' );
$row = $result -> fetchRow ();
self :: raiseExceptionOnError ( $row , 'fetching row for insertid failed' );
2012-11-22 23:23:27 +00:00
return $row [ 'id' ];
2013-06-14 10:06:29 +00:00
} else if ( $type === 'mssql' || $type === 'oci' ) {
2013-02-14 20:59:24 +00:00
if ( $table !== null ) {
$prefix = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
$table = str_replace ( '*PREFIX*' , $prefix , $table );
}
2013-06-10 10:56:45 +00:00
$result = self :: $connection -> lastInsertId ( $table );
} else {
2012-11-22 23:23:27 +00:00
if ( $table !== null ) {
$prefix = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
$suffix = OC_Config :: getValue ( " dbsequencesuffix " , " _id_seq " );
$table = str_replace ( '*PREFIX*' , $prefix , $table ) . $suffix ;
}
2013-06-10 10:56:45 +00:00
$result = self :: $connection -> lastInsertId ( $table );
2011-10-29 09:40:48 +00:00
}
2013-06-10 10:56:45 +00:00
self :: raiseExceptionOnError ( $result , 'insertid failed' );
return $result ;
2011-04-16 08:17:40 +00:00
}
/**
* @ brief Disconnect
2012-09-22 23:52:34 +00:00
* @ return bool
2011-04-16 08:17:40 +00:00
*
* This is good bye , good bye , yeah !
*/
2012-09-07 13:22:01 +00:00
public static function disconnect () {
2011-04-16 08:17:40 +00:00
// Cut connection if required
2012-09-05 12:49:42 +00:00
if ( self :: $connection ) {
if ( self :: $backend == self :: BACKEND_MDB2 ) {
2011-09-17 00:30:58 +00:00
self :: $connection -> disconnect ();
}
self :: $connection = false ;
2012-01-08 01:57:52 +00:00
self :: $MDB2 = false ;
self :: $PDO = false ;
2011-04-16 08:17:40 +00:00
}
return true ;
}
/**
* @ brief saves database scheme to xml file
2012-09-22 23:52:34 +00:00
* @ param string $file name of file
* @ param int $mode
* @ return bool
2011-04-16 08:17:40 +00:00
*
* TODO : write more documentation
*/
2012-10-12 13:37:44 +00:00
public static function getDbStructure ( $file , $mode = MDB2_SCHEMA_DUMP_STRUCTURE ) {
2011-04-16 08:17:40 +00:00
self :: connectScheme ();
// write the scheme
$definition = self :: $schema -> getDefinitionFromDatabase ();
$dump_options = array (
'output_mode' => 'file' ,
'output' => $file ,
'end_of_line' => " \n "
);
2012-01-06 18:04:24 +00:00
self :: $schema -> dumpDatabase ( $definition , $dump_options , $mode );
2011-04-16 08:17:40 +00:00
return true ;
}
/**
* @ brief Creates tables from XML file
2012-09-22 23:52:34 +00:00
* @ param string $file file to read structure from
* @ return bool
2011-04-16 08:17:40 +00:00
*
* TODO : write more documentation
*/
2012-09-07 13:22:01 +00:00
public static function createDbFromStructure ( $file ) {
2011-07-29 19:36:03 +00:00
$CONFIG_DBNAME = OC_Config :: getValue ( " dbname " , " owncloud " );
$CONFIG_DBTABLEPREFIX = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
2011-08-07 19:06:53 +00:00
$CONFIG_DBTYPE = OC_Config :: getValue ( " dbtype " , " sqlite " );
2011-04-16 08:17:40 +00:00
2013-01-20 21:46:26 +00:00
// cleanup the cached queries
self :: $preparedQueries = array ();
2011-04-16 08:17:40 +00:00
self :: connectScheme ();
// read file
$content = file_get_contents ( $file );
2012-08-29 06:38:33 +00:00
2012-03-01 19:41:14 +00:00
// Make changes and save them to an in-memory file
$file2 = 'static://db_scheme' ;
2011-04-16 08:17:40 +00:00
$content = str_replace ( '*dbname*' , $CONFIG_DBNAME , $content );
$content = str_replace ( '*dbprefix*' , $CONFIG_DBTABLEPREFIX , $content );
2012-10-08 15:24:15 +00:00
/* FIXME : use CURRENT_TIMESTAMP for all databases . mysql supports it as a default for DATETIME since 5.6 . 5 [ 1 ]
* as a fallback we could use < default > 0000 - 01 - 01 00 : 00 : 00 </ default > everywhere
* [ 1 ] http :// bugs . mysql . com / bug . php ? id = 27645
2012-08-24 22:05:07 +00:00
* http :// dev . mysql . com / doc / refman / 5.0 / en / timestamp - initialization . html
* http :// www . postgresql . org / docs / 8.1 / static / functions - datetime . html
* http :// www . sqlite . org / lang_createtable . html
* http :// docs . oracle . com / cd / B19306_01 / server . 102 / b14200 / functions037 . htm
2012-09-01 14:49:29 +00:00
*/
2012-12-14 22:04:42 +00:00
if ( $CONFIG_DBTYPE == 'pgsql' ) { //mysql support it too but sqlite doesn't
2013-02-11 16:44:02 +00:00
$content = str_replace ( '<default>0000-00-00 00:00:00</default>' ,
'<default>CURRENT_TIMESTAMP</default>' , $content );
2012-12-14 22:04:42 +00:00
}
2012-09-07 12:05:51 +00:00
2011-04-16 08:17:40 +00:00
file_put_contents ( $file2 , $content );
// Try to create tables
2011-04-17 09:59:15 +00:00
$definition = self :: $schema -> parseDatabaseDefinitionFile ( $file2 );
2012-08-29 06:38:33 +00:00
2012-03-01 19:41:14 +00:00
//clean up memory
2011-04-16 08:17:40 +00:00
unlink ( $file2 );
2013-06-10 10:56:45 +00:00
self :: raiseExceptionOnError ( $definition , 'Failed to parse the database definition' );
2011-04-16 08:17:40 +00:00
2012-09-05 12:49:42 +00:00
if ( OC_Config :: getValue ( 'dbtype' , 'sqlite' ) === 'oci' ) {
2012-08-24 22:05:07 +00:00
unset ( $definition [ 'charset' ]); //or MDB2 tries SHUTDOWN IMMEDIATE
$oldname = $definition [ 'name' ];
$definition [ 'name' ] = OC_Config :: getValue ( " dbuser " , $oldname );
}
2012-08-29 06:38:33 +00:00
2013-02-15 07:22:31 +00:00
// we should never drop a database
$definition [ 'overwrite' ] = false ;
2011-04-17 09:59:15 +00:00
$ret = self :: $schema -> createDatabase ( $definition );
2011-04-16 08:17:40 +00:00
2013-06-10 10:56:45 +00:00
self :: raiseExceptionOnError ( $ret , 'Failed to create the database structure' );
2011-04-16 08:17:40 +00:00
return true ;
}
2012-08-29 06:38:33 +00:00
2011-10-23 13:25:38 +00:00
/**
* @ brief update the database scheme
2012-09-22 23:52:34 +00:00
* @ param string $file file to read structure from
* @ return bool
2011-10-23 13:25:38 +00:00
*/
2012-09-07 13:22:01 +00:00
public static function updateDbFromStructure ( $file ) {
2011-10-23 13:25:38 +00:00
$CONFIG_DBTABLEPREFIX = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
2012-10-09 09:17:10 +00:00
$CONFIG_DBTYPE = OC_Config :: getValue ( " dbtype " , " sqlite " );
2012-10-14 19:04:08 +00:00
2011-10-23 13:25:38 +00:00
self :: connectScheme ();
2013-06-14 10:06:29 +00:00
if ( OC_Config :: getValue ( 'dbtype' , 'sqlite' ) === 'oci' ) {
//set dbname, it is unset because oci uses 'service' to connect
self :: $schema -> db -> database_name = self :: $schema -> db -> dsn [ 'username' ];
}
2011-10-23 13:25:38 +00:00
// read file
$content = file_get_contents ( $file );
2012-08-29 06:38:33 +00:00
2011-11-13 15:16:21 +00:00
$previousSchema = self :: $schema -> getDefinitionFromDatabase ();
2013-06-10 10:56:45 +00:00
self :: raiseExceptionOnError ( $previousSchema , 'Failed to get existing database structure for updating' );
2011-11-13 15:16:21 +00:00
2012-03-01 19:41:14 +00:00
// Make changes and save them to an in-memory file
$file2 = 'static://db_scheme' ;
2011-11-13 15:16:21 +00:00
$content = str_replace ( '*dbname*' , $previousSchema [ 'name' ], $content );
2011-10-23 13:25:38 +00:00
$content = str_replace ( '*dbprefix*' , $CONFIG_DBTABLEPREFIX , $content );
2012-10-08 15:24:15 +00:00
/* FIXME : use CURRENT_TIMESTAMP for all databases . mysql supports it as a default for DATETIME since 5.6 . 5 [ 1 ]
* as a fallback we could use < default > 0000 - 01 - 01 00 : 00 : 00 </ default > everywhere
* [ 1 ] http :// bugs . mysql . com / bug . php ? id = 27645
2012-08-24 22:05:07 +00:00
* http :// dev . mysql . com / doc / refman / 5.0 / en / timestamp - initialization . html
* http :// www . postgresql . org / docs / 8.1 / static / functions - datetime . html
* http :// www . sqlite . org / lang_createtable . html
* http :// docs . oracle . com / cd / B19306_01 / server . 102 / b14200 / functions037 . htm
2012-10-08 15:24:15 +00:00
*/
2012-09-07 13:22:01 +00:00
if ( $CONFIG_DBTYPE == 'pgsql' ) { //mysql support it too but sqlite doesn't
2013-02-11 16:44:02 +00:00
$content = str_replace ( '<default>0000-00-00 00:00:00</default>' ,
'<default>CURRENT_TIMESTAMP</default>' , $content );
2011-10-23 13:25:38 +00:00
}
2013-06-14 10:06:29 +00:00
if ( OC_Config :: getValue ( 'dbtype' , 'sqlite' ) === 'oci' ) {
unset ( $previousSchema [ 'charset' ]); //or MDB2 tries SHUTDOWN IMMEDIATE
$oldname = $previousSchema [ 'name' ];
$previousSchema [ 'name' ] = OC_Config :: getValue ( " dbuser " , $oldname );
//TODO check identifiers are at most 30 chars long
}
2011-10-23 13:25:38 +00:00
file_put_contents ( $file2 , $content );
2011-11-13 15:16:21 +00:00
$op = self :: $schema -> updateDatabase ( $file2 , $previousSchema , array (), false );
2012-08-29 06:38:33 +00:00
2012-03-01 19:41:14 +00:00
//clean up memory
2012-01-13 19:05:44 +00:00
unlink ( $file2 );
2012-08-29 06:38:33 +00:00
2013-06-10 10:56:45 +00:00
self :: raiseExceptionOnError ( $op , 'Failed to update database structure' );
2011-10-23 13:25:38 +00:00
return true ;
}
2011-04-16 08:17:40 +00:00
/**
* @ brief connects to a MDB2 database scheme
2012-09-22 23:52:34 +00:00
* @ returns bool
2011-04-16 08:17:40 +00:00
*
* Connects to a MDB2 database scheme
*/
2012-09-07 13:22:01 +00:00
private static function connectScheme () {
2011-09-17 00:30:58 +00:00
// We need a mdb2 database connection
self :: connectMDB2 ();
2012-01-08 01:57:52 +00:00
self :: $MDB2 -> loadModule ( 'Manager' );
2012-01-08 12:16:11 +00:00
self :: $MDB2 -> loadModule ( 'Reverse' );
2011-04-16 08:17:40 +00:00
// Connect if this did not happen before
2012-09-05 12:49:42 +00:00
if ( ! self :: $schema ) {
require_once 'MDB2/Schema.php' ;
2011-09-17 00:30:58 +00:00
self :: $schema = MDB2_Schema :: factory ( self :: $MDB2 );
2011-04-16 08:17:40 +00:00
}
return true ;
}
2012-09-17 14:01:25 +00:00
/**
* @ brief Insert a row if a matching row doesn ' t exists .
2012-11-12 22:34:02 +00:00
* @ param string $table . The table to insert into in the form '*PREFIX*tableName'
* @ param array $input . An array of fieldname / value pairs
2013-07-05 12:05:42 +00:00
* @ returns int number of updated rows
2012-09-17 14:01:25 +00:00
*/
public static function insertIfNotExist ( $table , $input ) {
self :: connect ();
$prefix = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
$table = str_replace ( '*PREFIX*' , $prefix , $table );
if ( is_null ( self :: $type )) {
self :: $type = OC_Config :: getValue ( " dbtype " , " sqlite " );
}
$type = self :: $type ;
$query = '' ;
2013-03-25 22:59:34 +00:00
$inserts = array_values ( $input );
2012-09-17 14:01:25 +00:00
// differences in escaping of table names ('`' for mysql) and getting the current timestamp
if ( $type == 'sqlite' || $type == 'sqlite3' ) {
2012-11-12 22:34:02 +00:00
// NOTE: For SQLite we have to use this clumsy approach
// otherwise all fieldnames used must have a unique key.
2013-03-26 00:00:15 +00:00
$query = 'SELECT * FROM `' . $table . '` WHERE ' ;
2012-11-12 22:34:02 +00:00
foreach ( $input as $key => $value ) {
2013-03-26 00:00:15 +00:00
$query .= '`' . $key . '` = ? AND ' ;
2012-11-12 22:34:02 +00:00
}
$query = substr ( $query , 0 , strlen ( $query ) - 5 );
try {
2013-06-10 10:56:45 +00:00
$result = self :: executeAudited ( $query , $inserts );
} catch ( DatabaseException $e ) {
OC_Template :: printExceptionErrorPage ( $e );
2012-11-12 22:34:02 +00:00
}
2013-01-16 00:11:19 +00:00
2013-03-25 22:59:34 +00:00
if (( int ) $result -> numRows () === 0 ) {
2013-03-26 00:00:15 +00:00
$query = 'INSERT INTO `' . $table . '` (`'
. implode ( '`,`' , array_keys ( $input )) . '`) VALUES('
2013-03-25 22:59:34 +00:00
. str_repeat ( '?,' , count ( $input ) - 1 ) . '? ' . ')' ;
2012-11-12 22:34:02 +00:00
} else {
2013-07-05 12:05:42 +00:00
return 0 ; //no rows updated
2012-11-12 22:34:02 +00:00
}
2013-02-07 23:00:51 +00:00
} elseif ( $type == 'pgsql' || $type == 'oci' || $type == 'mysql' || $type == 'mssql' ) {
2013-03-25 22:59:34 +00:00
$query = 'INSERT INTO `' . $table . '` (`'
. implode ( '`,`' , array_keys ( $input )) . '`) SELECT '
. str_repeat ( '?,' , count ( $input ) - 1 ) . '? ' // Is there a prettier alternative?
2013-03-25 23:24:08 +00:00
. 'FROM `' . $table . '` WHERE ' ;
2012-11-12 22:34:02 +00:00
2012-09-17 14:01:25 +00:00
foreach ( $input as $key => $value ) {
2013-03-25 22:59:34 +00:00
$query .= '`' . $key . '` = ? AND ' ;
2012-09-17 14:01:25 +00:00
}
$query = substr ( $query , 0 , strlen ( $query ) - 5 );
$query .= ' HAVING COUNT(*) = 0' ;
2013-03-25 22:59:34 +00:00
$inserts = array_merge ( $inserts , $inserts );
2012-09-17 14:01:25 +00:00
}
2012-11-12 22:34:02 +00:00
2012-09-17 14:01:25 +00:00
try {
2013-06-10 10:56:45 +00:00
$result = self :: executeAudited ( $query , $inserts );
2012-09-17 14:01:25 +00:00
} catch ( PDOException $e ) {
2013-06-10 10:56:45 +00:00
OC_Template :: printExceptionErrorPage ( $e );
2012-09-17 14:01:25 +00:00
}
2013-06-10 10:56:45 +00:00
return $result ;
2012-09-17 14:01:25 +00:00
}
2013-01-16 00:11:19 +00:00
2011-04-16 08:17:40 +00:00
/**
2012-09-22 23:52:34 +00:00
* @ brief does minor changes to query
* @ param string $query Query string
* @ return string corrected query string
2011-04-16 08:17:40 +00:00
*
* This function replaces * PREFIX * with the value of $CONFIG_DBTABLEPREFIX
2012-09-22 23:52:34 +00:00
* and replaces the ` with ' or " according to the database driver.
2011-04-16 08:17:40 +00:00
*/
2012-09-07 13:22:01 +00:00
private static function processQuery ( $query ) {
2011-06-13 02:06:43 +00:00
self :: connect ();
2011-04-16 08:17:40 +00:00
// We need Database type and table prefix
2012-09-05 12:49:42 +00:00
if ( is_null ( self :: $type )) {
2012-04-14 16:30:13 +00:00
self :: $type = OC_Config :: getValue ( " dbtype " , " sqlite " );
2012-04-14 14:28:36 +00:00
}
$type = self :: $type ;
2012-09-05 12:49:42 +00:00
if ( is_null ( self :: $prefix )) {
2012-04-14 14:28:36 +00:00
self :: $prefix = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
}
$prefix = self :: $prefix ;
2012-08-29 06:38:33 +00:00
2011-09-17 00:30:58 +00:00
// differences in escaping of table names ('`' for mysql) and getting the current timestamp
2012-09-05 12:49:42 +00:00
if ( $type == 'sqlite' || $type == 'sqlite3' ) {
2012-05-11 19:45:24 +00:00
$query = str_replace ( '`' , '"' , $query );
2012-09-02 11:58:01 +00:00
$query = str_ireplace ( 'NOW()' , 'datetime(\'now\')' , $query );
$query = str_ireplace ( 'UNIX_TIMESTAMP()' , 'strftime(\'%s\',\'now\')' , $query );
2012-09-05 12:49:42 +00:00
} elseif ( $type == 'pgsql' ) {
2012-08-27 16:37:16 +00:00
$query = str_replace ( '`' , '"' , $query );
2013-02-11 16:44:02 +00:00
$query = str_ireplace ( 'UNIX_TIMESTAMP()' , 'cast(extract(epoch from current_timestamp) as integer)' ,
$query );
2012-09-05 12:49:42 +00:00
} elseif ( $type == 'oci' ) {
2011-04-16 08:17:40 +00:00
$query = str_replace ( '`' , '"' , $query );
2012-09-02 11:58:01 +00:00
$query = str_ireplace ( 'NOW()' , 'CURRENT_TIMESTAMP' , $query );
2013-06-25 07:52:04 +00:00
$query = str_ireplace ( 'UNIX_TIMESTAMP()' , '((CAST(SYS_EXTRACT_UTC(systimestamp) AS DATE))-TO_DATE(\'1970101000000\',\'YYYYMMDDHH24MiSS\'))*24*3600' , $query );
2013-02-07 23:00:51 +00:00
} 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 );
2013-06-20 08:27:02 +00:00
$query = self :: fixLimitClauseForMSSQL ( $query );
}
2011-04-16 08:17:40 +00:00
2011-04-28 15:40:51 +00:00
// replace table name prefix
2011-09-17 00:30:58 +00:00
$query = str_replace ( '*PREFIX*' , $prefix , $query );
2011-04-16 08:17:40 +00:00
return $query ;
}
2012-08-29 06:38:33 +00:00
2013-06-20 08:27:02 +00:00
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 ;
}
2011-06-12 15:51:31 +00:00
/**
* @ brief drop a table
2012-09-22 23:52:34 +00:00
* @ param string $tableName the table to drop
2011-06-12 15:51:31 +00:00
*/
2012-09-07 13:22:01 +00:00
public static function dropTable ( $tableName ) {
2011-09-17 00:30:58 +00:00
self :: connectMDB2 ();
self :: $MDB2 -> loadModule ( 'Manager' );
self :: $MDB2 -> dropTable ( $tableName );
2011-06-12 15:51:31 +00:00
}
2012-08-29 06:38:33 +00:00
2011-06-12 15:51:31 +00:00
/**
* remove all tables defined in a database structure xml file
* @ param string $file the xml file describing the tables
*/
2012-09-07 13:22:01 +00:00
public static function removeDBStructure ( $file ) {
2011-07-29 19:36:03 +00:00
$CONFIG_DBNAME = OC_Config :: getValue ( " dbname " , " owncloud " );
$CONFIG_DBTABLEPREFIX = OC_Config :: getValue ( " dbtableprefix " , " oc_ " );
2011-06-12 15:51:31 +00:00
self :: connectScheme ();
// read file
$content = file_get_contents ( $file );
// Make changes and save them to a temporary file
2011-10-19 21:38:35 +00:00
$file2 = tempnam ( get_temp_dir (), 'oc_db_scheme_' );
2011-06-12 15:51:31 +00:00
$content = str_replace ( '*dbname*' , $CONFIG_DBNAME , $content );
$content = str_replace ( '*dbprefix*' , $CONFIG_DBTABLEPREFIX , $content );
file_put_contents ( $file2 , $content );
// get the tables
$definition = self :: $schema -> parseDatabaseDefinitionFile ( $file2 );
2012-08-29 06:38:33 +00:00
2011-06-12 15:51:31 +00:00
// Delete our temporary file
unlink ( $file2 );
2012-07-20 15:51:50 +00:00
$tables = array_keys ( $definition [ 'tables' ]);
2012-09-07 13:22:01 +00:00
foreach ( $tables as $table ) {
2012-07-20 15:51:50 +00:00
self :: dropTable ( $table );
2011-06-12 15:51:31 +00:00
}
}
2012-08-29 06:38:33 +00:00
2012-03-01 19:41:14 +00:00
/**
2012-04-14 16:31:37 +00:00
* @ brief replaces the owncloud tables with a new set
2012-03-20 20:19:21 +00:00
* @ param $file string path to the MDB2 xml db export file
2012-03-01 19:41:14 +00:00
*/
2012-09-07 13:22:01 +00:00
public static function replaceDB ( $file ) {
2012-08-29 18:34:44 +00:00
$apps = OC_App :: getAllApps ();
self :: beginTransaction ();
// Delete the old tables
self :: removeDBStructure ( OC :: $SERVERROOT . '/db_structure.xml' );
2012-09-07 13:22:01 +00:00
foreach ( $apps as $app ) {
2012-08-29 18:34:44 +00:00
$path = OC_App :: getAppPath ( $app ) . '/appinfo/database.xml' ;
2012-09-05 12:49:42 +00:00
if ( file_exists ( $path )) {
2012-08-29 18:34:44 +00:00
self :: removeDBStructure ( $path );
}
}
// Create new tables
self :: createDBFromStructure ( $file );
self :: commit ();
2012-09-05 12:49:42 +00:00
}
2012-08-29 06:38:33 +00:00
2011-07-31 18:24:53 +00:00
/**
2011-09-17 00:30:58 +00:00
* Start a transaction
2012-09-22 23:52:34 +00:00
* @ return bool
2011-07-31 18:24:53 +00:00
*/
2012-09-07 13:22:01 +00:00
public static function beginTransaction () {
2011-07-31 22:07:46 +00:00
self :: connect ();
2012-02-05 00:23:41 +00:00
if ( self :: $backend == self :: BACKEND_MDB2 && ! self :: $connection -> supports ( 'transactions' )) {
2011-07-31 18:24:53 +00:00
return false ;
}
2011-09-17 00:30:58 +00:00
self :: $connection -> beginTransaction ();
2012-02-09 18:51:24 +00:00
self :: $inTransaction = true ;
2012-09-22 23:52:34 +00:00
return true ;
2011-07-31 18:24:53 +00:00
}
/**
2011-09-17 00:30:58 +00:00
* Commit the database changes done during a transaction that is in progress
2012-09-22 23:52:34 +00:00
* @ return bool
2011-07-31 18:24:53 +00:00
*/
2012-09-05 12:49:42 +00:00
public static function commit () {
2011-07-31 22:07:46 +00:00
self :: connect ();
2012-09-05 12:49:42 +00:00
if ( ! self :: $inTransaction ) {
2011-07-31 18:24:53 +00:00
return false ;
}
2011-09-17 00:30:58 +00:00
self :: $connection -> commit ();
2012-02-09 18:51:24 +00:00
self :: $inTransaction = false ;
2012-09-22 23:52:34 +00:00
return true ;
2011-09-17 00:30:58 +00:00
}
2012-03-01 19:41:14 +00:00
/**
* check if a result is an error , works with MDB2 and PDOException
* @ param mixed $result
* @ return bool
*/
2012-09-07 13:22:01 +00:00
public static function isError ( $result ) {
2013-06-27 14:48:09 +00:00
//MDB2 returns an MDB2_Error object
if ( class_exists ( 'PEAR' ) === true && PEAR :: isError ( $result )) {
2012-03-01 19:41:14 +00:00
return true ;
2013-06-27 14:48:09 +00:00
}
2013-06-20 09:31:23 +00:00
//PDO returns false on error (and throws an exception)
if ( self :: $backend === self :: BACKEND_PDO and $result === false ) {
2012-03-01 19:41:14 +00:00
return true ;
}
2013-06-27 14:48:09 +00:00
return false ;
2012-03-01 19:41:14 +00:00
}
2013-06-10 10:56:45 +00:00
/**
2013-06-12 13:48:22 +00:00
* check if a result is an error and throws an exception , works with MDB2 and PDOException
2013-06-10 10:56:45 +00:00
* @ param mixed $result
2013-06-27 14:48:09 +00:00
* @ param string $message
2013-06-10 10:56:45 +00:00
* @ 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 );
}
2013-06-12 13:48:22 +00:00
throw new DatabaseException ( $message , self :: getErrorCode ( $result ));
2013-06-10 10:56:45 +00:00
}
}
2012-10-14 19:04:08 +00:00
2013-06-10 10:56:45 +00:00
public static function getErrorCode ( $error ) {
2013-06-27 14:48:09 +00:00
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 ();
2013-06-10 10:56:45 +00:00
}
2013-06-27 14:48:09 +00:00
return - 1 ;
2013-06-10 10:56:45 +00:00
}
2012-09-12 10:45:20 +00:00
/**
* returns the error code and message as a string for logging
* works with MDB2 and PDOException
* @ param mixed $error
* @ return string
*/
public static function getErrorMessage ( $error ) {
2013-06-27 14:48:09 +00:00
if ( class_exists ( 'PEAR' ) === true && PEAR :: isError ( $error ) ) {
2012-09-12 10:45:20 +00:00
$msg = $error -> getCode () . ': ' . $error -> getMessage ();
2013-06-10 10:56:45 +00:00
$msg .= ' (' . $error -> getDebugInfo () . ')' ;
2013-06-27 14:48:09 +00:00
return $msg ;
}
if ( self :: $backend == self :: BACKEND_PDO and self :: $PDO ) {
2012-09-12 10:45:20 +00:00
$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 ];
}
2013-06-27 14:48:09 +00:00
return $msg ;
2012-09-12 10:45:20 +00:00
}
2013-06-27 14:48:09 +00:00
return '' ;
2012-09-12 10:45:20 +00:00
}
2013-02-26 21:41:48 +00:00
/**
* @ param bool $enabled
*/
static public function enableCaching ( $enabled ) {
if ( ! $enabled ) {
self :: $preparedQueries = array ();
}
self :: $cachingEnabled = $enabled ;
}
2011-09-17 00:30:58 +00:00
}
/**
* small wrapper around PDOStatement to make it behave , more like an MDB2 Statement
*/
class PDOStatementWrapper {
2012-09-22 23:52:34 +00:00
/**
* @ var PDOStatement
*/
2013-06-20 12:46:22 +00:00
private $statement = null ;
private $isManipulation = false ;
private $lastArguments = array ();
2011-09-17 00:30:58 +00:00
2013-06-20 12:46:22 +00:00
public function __construct ( $statement , $isManipulation = false ) {
$this -> statement = $statement ;
$this -> isManipulation = $isManipulation ;
2011-09-17 00:30:58 +00:00
}
2012-08-29 06:38:33 +00:00
2011-09-17 00:30:58 +00:00
/**
2013-07-05 13:39:57 +00:00
* make execute return the result or updated row count instead of a bool
2011-09-17 00:30:58 +00:00
*/
2012-09-07 13:22:01 +00:00
public function execute ( $input = array ()) {
2013-03-30 21:36:55 +00:00
if ( OC_Config :: getValue ( " log_query " , false )) {
2013-03-30 16:40:46 +00:00
$params_str = str_replace ( " \n " , " " , var_export ( $input , true ));
OC_Log :: write ( 'core' , 'DB execute with arguments : ' . $params_str , OC_Log :: DEBUG );
}
2013-02-10 12:07:59 +00:00
$this -> lastArguments = $input ;
if ( count ( $input ) > 0 ) {
if ( ! isset ( $type )) {
$type = OC_Config :: getValue ( " dbtype " , " sqlite " );
}
if ( $type == 'mssql' ) {
$input = $this -> tryFixSubstringLastArgumentDataForMSSQL ( $input );
}
2013-06-20 12:46:22 +00:00
$result = $this -> statement -> execute ( $input );
2013-02-10 12:07:59 +00:00
} else {
2013-06-20 12:46:22 +00:00
$result = $this -> statement -> execute ();
2012-03-01 19:41:14 +00:00
}
2013-02-10 12:07:59 +00:00
2013-06-20 12:46:22 +00:00
if ( $result === false ) {
2012-03-01 19:41:14 +00:00
return false ;
2011-09-17 00:30:58 +00:00
}
2013-06-20 12:46:22 +00:00
if ( $this -> isManipulation ) {
return $this -> statement -> rowCount ();
} else {
return $this ;
}
2011-09-17 00:30:58 +00:00
}
2012-08-29 06:38:33 +00:00
2013-02-10 13:08:00 +00:00
private function tryFixSubstringLastArgumentDataForMSSQL ( $input ) {
$query = $this -> statement -> queryString ;
$pos = stripos ( $query , 'SUBSTRING' );
2013-02-07 23:00:51 +00:00
2013-02-10 13:08:00 +00:00
if ( $pos === false ) {
return ;
}
2013-02-07 23:00:51 +00:00
2013-02-10 13:08:00 +00:00
try {
$newQuery = '' ;
2013-02-07 23:00:51 +00:00
2013-02-10 13:08:00 +00:00
$cArg = 0 ;
2013-02-07 23:00:51 +00:00
2013-02-10 13:08:00 +00:00
$inSubstring = false ;
2013-02-07 23:00:51 +00:00
2013-02-10 13:08:00 +00:00
// Create new query
for ( $i = 0 ; $i < strlen ( $query ); $i ++ ) {
if ( $inSubstring == false ) {
// Defines when we should start inserting values
if ( substr ( $query , $i , 9 ) == 'SUBSTRING' ) {
$inSubstring = true ;
}
} else {
// Defines when we should stop inserting values
if ( substr ( $query , $i , 1 ) == ')' ) {
$inSubstring = false ;
}
}
if ( substr ( $query , $i , 1 ) == '?' ) {
// We found a question mark
if ( $inSubstring ) {
$newQuery .= $input [ $cArg ];
//
// Remove from input array
//
array_splice ( $input , $cArg , 1 );
} else {
$newQuery .= substr ( $query , $i , 1 );
$cArg ++ ;
}
} else {
$newQuery .= substr ( $query , $i , 1 );
}
}
// The global data we need
$name = OC_Config :: getValue ( " dbname " , " owncloud " );
$host = OC_Config :: getValue ( " dbhost " , " " );
$user = OC_Config :: getValue ( " dbuser " , " " );
$pass = OC_Config :: getValue ( " dbpassword " , " " );
if ( strpos ( $host , ':' )) {
list ( $host , $port ) = explode ( ':' , $host , 2 );
} else {
$port = false ;
}
$opts = array ();
if ( $port ) {
$dsn = 'sqlsrv:Server=' . $host . ',' . $port . ';Database=' . $name ;
} else {
$dsn = 'sqlsrv:Server=' . $host . ';Database=' . $name ;
}
$PDO = new PDO ( $dsn , $user , $pass , $opts );
$PDO -> setAttribute ( PDO :: ATTR_DEFAULT_FETCH_MODE , PDO :: FETCH_ASSOC );
$PDO -> setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_EXCEPTION );
$this -> statement = $PDO -> prepare ( $newQuery );
$this -> lastArguments = $input ;
return $input ;
} catch ( PDOException $e ){
2013-02-07 23:00:51 +00:00
$entry = 'PDO DB Error: "' . $e -> getMessage () . '"<br />' ;
$entry .= 'Offending command was: ' . $this -> statement -> queryString . '<br />' ;
$entry .= 'Input parameters: ' . print_r ( $input , true ) . '<br />' ;
$entry .= 'Stack trace: ' . $e -> getTraceAsString () . '<br />' ;
OC_Log :: write ( 'core' , $entry , OC_Log :: FATAL );
2013-02-10 13:08:00 +00:00
OC_User :: setUserId ( null );
2013-02-07 23:00:51 +00:00
2013-02-10 13:08:00 +00:00
// 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 ( $entry );
2013-02-07 23:00:51 +00:00
}
2013-02-10 13:08:00 +00:00
}
2013-06-20 08:27:02 +00:00
2011-09-17 00:30:58 +00:00
/**
* provide numRows
*/
2012-09-07 13:22:01 +00:00
public function numRows () {
2011-09-17 00:30:58 +00:00
$regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i' ;
if ( preg_match ( $regex , $this -> statement -> queryString , $output ) > 0 ) {
2013-04-06 15:58:47 +00:00
$query = OC_DB :: prepare ( " SELECT COUNT(*) FROM { $output [ 1 ] } " );
2011-09-17 00:30:58 +00:00
return $query -> execute ( $this -> lastArguments ) -> fetchColumn ();
2011-07-31 18:24:53 +00:00
} else {
2011-09-17 00:30:58 +00:00
return $this -> statement -> rowCount ();
2011-07-31 18:24:53 +00:00
}
}
2012-08-29 06:38:33 +00:00
2011-09-17 00:30:58 +00:00
/**
* provide an alias for fetch
*/
2012-09-07 13:22:01 +00:00
public function fetchRow () {
2011-09-17 00:30:58 +00:00
return $this -> statement -> fetch ();
}
2012-08-29 06:38:33 +00:00
2011-09-17 00:30:58 +00:00
/**
* pass all other function directly to the PDOStatement
*/
2012-11-02 18:53:02 +00:00
public function __call ( $name , $arguments ) {
return call_user_func_array ( array ( $this -> statement , $name ), $arguments );
2011-09-17 00:30:58 +00:00
}
2012-08-29 06:38:33 +00:00
2011-10-16 21:03:03 +00:00
/**
* Provide a simple fetchOne .
* fetch single column from the next row
* @ param int $colnum the column number to fetch
*/
2012-09-07 13:22:01 +00:00
public function fetchOne ( $colnum = 0 ) {
2011-10-16 21:03:03 +00:00
return $this -> statement -> fetchColumn ( $colnum );
}
2011-04-16 08:17:40 +00:00
}