From: Robin Appelman Date: Tue, 6 Aug 2013 13:43:58 +0000 (+0200) Subject: merge master into doctrine-object X-Git-Tag: v6.0.0alpha2~337^2~4 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=95a959b10b7c9dc7b0529fbb7b05c11685f19e8b;p=nextcloud-server.git merge master into doctrine-object --- 95a959b10b7c9dc7b0529fbb7b05c11685f19e8b diff --cc lib/db.php index fec4eaaf93f,a96f4697235..2b6cd59366a --- a/lib/db.php +++ b/lib/db.php @@@ -41,15 -41,35 +41,16 @@@ class DatabaseException extends Excepti * Doctrine with some adaptions. */ class OC_DB { - const BACKEND_DOCTRINE=2; - - static private $preparedQueries = array(); - static private $cachingEnabled = true; - /** - * @var \Doctrine\DBAL\Connection + * @var \OC\DB\Connection $connection */ - static private $connection; //the preferred connection to use, only Doctrine - static private $backend=null; - /** - * @var \Doctrine\DBAL\Connection - */ - static private $DOCTRINE=null; + static private $connection; //the prefered connection to use, only Doctrine + static private $prefix=null; static private $type=null; - /** - * check which backend we should use - * @return int BACKEND_DOCTRINE - */ - private static function getDBBackend() { - return self::BACKEND_DOCTRINE; - } - /** * @brief connects to the database - * @param int $backend * @return bool true if connection can be established or false on error * * Connects to the database as specified in config.php @@@ -204,22 -276,22 +215,23 @@@ * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements * * @param string $sql ++ * @return bool */ static public function isManipulation( $sql ) { -- $selectOccurence = stripos ($sql, "SELECT"); -- if ($selectOccurence !== false && $selectOccurence < 10) { ++ $selectOccurrence = stripos ($sql, "SELECT"); ++ if ($selectOccurrence !== false && $selectOccurrence < 10) { return false; } -- $insertOccurence = stripos ($sql, "INSERT"); -- if ($insertOccurence !== false && $insertOccurence < 10) { ++ $insertOccurrence = stripos ($sql, "INSERT"); ++ if ($insertOccurrence !== false && $insertOccurrence < 10) { return true; } -- $updateOccurence = stripos ($sql, "UPDATE"); -- if ($updateOccurence !== false && $updateOccurence < 10) { ++ $updateOccurrence = stripos ($sql, "UPDATE"); ++ if ($updateOccurrence !== false && $updateOccurrence < 10) { return true; } -- $deleteOccurance = stripos ($sql, "DELETE"); -- if ($deleteOccurance !== false && $deleteOccurance < 10) { ++ $deleteOccurrence = stripos ($sql, "DELETE"); ++ if ($deleteOccurrence !== false && $deleteOccurrence < 10) { return true; } return false; @@@ -287,34 -359,36 +299,34 @@@ */ public static function insertid($table=null) { self::connect(); - $type = OC_Config::getValue( "dbtype", "sqlite" ); - if( $type === 'pgsql' ) { - $result = self::executeAudited('SELECT lastval() AS id'); - $row = $result->fetchRow(); - self::raiseExceptionOnError($row, 'fetching row for insertid failed'); - return (int)$row['id']; - } else if( $type === 'mssql') { - if($table !== null) { - $prefix = OC_Config::getValue( "dbtableprefix", "oc_" ); - $table = str_replace( '*PREFIX*', $prefix, $table ); - } - return self::$connection->lastInsertId($table); - } - if( $type === 'oci' ) { - if($table !== null) { - $prefix = OC_Config::getValue( "dbtableprefix", "oc_" ); - $suffix = '_SEQ'; - $table = '"'.str_replace( '*PREFIX*', $prefix, $table ).$suffix.'"'; - } - return 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; - } - $result = self::$connection->lastInsertId($table); - } - self::raiseExceptionOnError($result, 'insertid failed'); - return (int)$result; + return self::$connection->lastInsertId($table); + } + + /** + * @brief Insert a row if a matching row doesn't exists. + * @param string $table. The table to insert into in the form '*PREFIX*tableName' + * @param array $input. An array of fieldname/value pairs - * @returns int number of updated rows ++ * @return int number of updated rows + */ + public static function insertIfNotExist($table, $input) { + self::connect(); + return self::$connection->insertIfNotExist($table, $input); + } + + /** + * Start a transaction + */ + public static function beginTransaction() { + self::connect(); + self::$connection->beginTransaction(); + } + + /** + * Commit the database changes done during a transaction that is in progress + */ + public static function commit() { + self::connect(); + self::$connection->commit(); } /** @@@ -325,21 -400,24 +337,21 @@@ public static function disconnect() { // Cut connection if required if(self::$connection) { - self::$connection=false; - self::$DOCTRINE=false; + self::$connection->close(); } - - return true; } -- /** else { - * @brief saves database scheme to xml file ++ /** + * @brief saves database schema to xml file * @param string $file name of file * @param int $mode * @return bool * * TODO: write more documentation */ -- public static function getDbStructure( $file, $mode=MDB2_SCHEMA_DUMP_STRUCTURE) { - self::connect(); - return OC_DB_Schema::getDbStructure(self::$connection, $file); ++ public static function getDbStructure( $file, $mode = 0) { + $schemaManager = self::getMDB2SchemaManager(); + return $schemaManager->getDbStructure($file); } /** @@@ -395,10 -639,26 +407,10 @@@ * @param $file string path to the MDB2 xml db export file */ public static function replaceDB( $file ) { - self::connect(); - OC_DB_Schema::replaceDB(self::$connection, $file); + $schemaManager = self::getMDB2SchemaManager(); + $schemaManager->replaceDB($file); } - /** - * Start a transaction - */ - public static function beginTransaction() { - self::connect(); - self::$connection->beginTransaction(); - } - - /** - * Commit the database changes done during a transaction that is in progress - */ - public static function commit() { - self::connect(); - self::$connection->commit(); - } - /** * check if a result is an error, works with Doctrine * @param mixed $result diff --cc lib/db/connection.php index 920fe144a8a,00000000000..7f207ff76ec mode 100644,000000..100644 --- a/lib/db/connection.php +++ b/lib/db/connection.php @@@ -1,197 -1,0 +1,196 @@@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\DB; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\Common\EventManager; + +class Connection extends \Doctrine\DBAL\Connection { + /** + * @var string $table_prefix + */ + protected $table_prefix; + + /** + * @var \OC\DB\Adapter $adapter + */ + protected $adapter; + + /** + * @var \Doctrine\DBAL\Driver\Statement[] $preparedQueries + */ + protected $preparedQueries = array(); + + protected $cachingQueryStatementEnabled = true; + + /** + * Initializes a new instance of the Connection class. + * + * @param array $params The connection parameters. + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration $config + * @param \Doctrine\Common\EventManager $eventManager + * @throws \Exception + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, + EventManager $eventManager = null) + { + if (!isset($params['adapter'])) { + throw new \Exception('adapter not set'); + } + if (!isset($params['table_prefix'])) { + throw new \Exception('table_prefix not set'); + } + parent::__construct($params, $driver, $config, $eventManager); + $this->adapter = new $params['adapter']($this); + $this->table_prefix = $params['table_prefix']; + } + + /** + * Prepares an SQL statement. + * + * @param string $statement The SQL statement to prepare. + * @param int $limit + * @param int $offset + * @return \Doctrine\DBAL\Driver\Statement The prepared statement. + */ + public function prepare( $statement, $limit=null, $offset=null ) { + $statement = $this->replaceTablePrefix($statement); + $statement = $this->adapter->fixupStatement($statement); + + if ($limit === -1) { + $limit = null; + } + if (!is_null($limit)) { + $platform = $this->getDatabasePlatform(); + $statement = $platform->modifyLimitQuery($statement, $limit, $offset); + } else { + if (isset($this->preparedQueries[$statement]) && $this->cachingQueryStatementEnabled) { + return $this->preparedQueries[$statement]; + } + } - $rawQuery = $statement; + if(\OC_Config::getValue( "log_query", false)) { + \OC_Log::write('core', 'DB prepare : '.$statement, \OC_Log::DEBUG); + } + $result = parent::prepare($statement); + if (is_null($limit) && $this->cachingQueryStatementEnabled) { - $this->preparedQueries[$rawQuery] = $result; ++ $this->preparedQueries[$statement] = $result; + } + return $result; + } + + /** + * Executes an, optionally parameterized, SQL query. + * + * If the query is parameterized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param QueryCacheProfile $qcp + * @return \Doctrine\DBAL\Driver\Statement The executed statement. + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null) + { + $query = $this->replaceTablePrefix($query); + $query = $this->adapter->fixupStatement($query); + return parent::executeQuery($query, $params, $types, $qcp); + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters + * and returns the number of affected rows. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $query The SQL query. + * @param array $params The query parameters. + * @param array $types The parameter types. + * @return integer The number of affected rows. + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeUpdate($query, array $params = array(), array $types = array()) + { + $query = $this->replaceTablePrefix($query); + $query = $this->adapter->fixupStatement($query); + return parent::executeUpdate($query, $params, $types); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * depending on the underlying driver. + * + * Note: This method may not return a meaningful or consistent result across different drivers, + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. + * + * @param string $seqName Name of the sequence object from which the ID should be returned. + * @return string A string representation of the last inserted ID. + */ + public function lastInsertId($seqName = null) + { + if ($seqName) { + $seqName = $this->replaceTablePrefix($seqName); + } + return $this->adapter->lastInsertId($seqName); + } + + // internal use + public function realLastInsertId($seqName = null) + { + return parent::lastInsertId($seqName); + } + + /** + * @brief Insert a row if a matching row doesn't exists. + * @param string $table. The table to insert into in the form '*PREFIX*tableName' + * @param array $input. An array of fieldname/value pairs + * @return bool The return value from execute() + */ + public function insertIfNotExist($table, $input) { + return $this->adapter->insertIfNotExist($table, $input); + } + + /** + * returns the error code and message as a string for logging + * works with DoctrineException + * @return string + */ + public function getError() { + $msg = $this->errorCode() . ': '; + $errorInfo = $this->errorInfo(); + if (is_array($errorInfo)) { + $msg .= 'SQLSTATE = '.$errorInfo[0] . ', '; + $msg .= 'Driver Code = '.$errorInfo[1] . ', '; + $msg .= 'Driver Message = '.$errorInfo[2]; + } + return $msg; + } + + // internal use + /** + * @param string $statement + * @return string + */ + protected function replaceTablePrefix($statement) { + return str_replace( '*PREFIX*', $this->table_prefix, $statement ); + } + + public function enableQueryStatementCaching() { + $this->cachingQueryStatementEnabled = true; + } + + public function disableQueryStatementCaching() { + $this->cachingQueryStatementEnabled = false; + $this->preparedQueries = array(); + } +} diff --cc lib/db/mdb2schemamanager.php index 00000000000,a34bc9dae75..8e76f46c78f mode 000000,100644..100644 --- a/lib/db/mdb2schemamanager.php +++ b/lib/db/mdb2schemamanager.php @@@ -1,0 -1,150 +1,150 @@@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + namespace OC\DB; + + class MDB2SchemaManager { + /** - * @var \Doctrine\DBAL\Connection $conn ++ * @var \OC\DB\Connection $conn + */ + protected $conn; + + /** - * @param \Doctrine\DBAL\Connection $conn ++ * @param \OC\DB\Connection $conn + */ + public function __construct($conn) { + $this->conn = $conn; + } + + /** + * @brief saves database scheme to xml file + * @param string $file name of file + * @param int|string $mode + * @return bool + * + * TODO: write more documentation + */ + public function getDbStructure( $file, $mode = MDB2_SCHEMA_DUMP_STRUCTURE) { + $sm = $this->conn->getSchemaManager(); + + return \OC_DB_MDB2SchemaWriter::saveSchemaToFile($file, $sm); + } + + /** + * @brief Creates tables from XML file + * @param string $file file to read structure from + * @return bool + * + * TODO: write more documentation + */ + public function createDbFromStructure( $file ) { + $schemaReader = new MDB2SchemaReader(\OC_Config::getObject(), $this->conn->getDatabasePlatform()); + $toSchema = $schemaReader->loadSchemaFromFile($file); + return $this->executeSchemaChange($toSchema); + } + + /** + * @brief update the database scheme + * @param string $file file to read structure from + * @return bool + */ + public function updateDbFromStructure($file) { + $sm = $this->conn->getSchemaManager(); + $fromSchema = $sm->createSchema(); + + $schemaReader = new MDB2SchemaReader(\OC_Config::getObject(), $this->conn->getDatabasePlatform()); + $toSchema = $schemaReader->loadSchemaFromFile($file); + + // remove tables we don't know about + foreach($fromSchema->getTables() as $table) { + if (!$toSchema->hasTable($table->getName())) { + $fromSchema->dropTable($table->getName()); + } + } + // remove sequences we don't know about + foreach($fromSchema->getSequences() as $table) { + if (!$toSchema->hasSequence($table->getName())) { + $fromSchema->dropSequence($table->getName()); + } + } + + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + + $platform = $this->conn->getDatabasePlatform(); + $tables = $schemaDiff->newTables + $schemaDiff->changedTables + $schemaDiff->removedTables; + foreach($tables as $tableDiff) { + $tableDiff->name = $platform->quoteIdentifier($tableDiff->name); + } + + return $this->executeSchemaChange($schemaDiff); + } + + /** + * @brief drop a table + * @param string $tableName the table to drop + */ + public function dropTable($tableName) { + $sm = $this->conn->getSchemaManager(); + $fromSchema = $sm->createSchema(); + $toSchema = clone $fromSchema; + $toSchema->dropTable($tableName); + $sql = $fromSchema->getMigrateToSql($toSchema, $this->conn->getDatabasePlatform()); - $this->conn->execute($sql); ++ $this->conn->executeQuery($sql); + } + + /** + * remove all tables defined in a database structure xml file + * @param string $file the xml file describing the tables + */ + public function removeDBStructure($file) { + $schemaReader = new MDB2SchemaReader(\OC_Config::getObject(), $this->conn->getDatabasePlatform()); + $fromSchema = $schemaReader->loadSchemaFromFile($file); + $toSchema = clone $fromSchema; + foreach($toSchema->getTables() as $table) { + $toSchema->dropTable($table->getName()); + } + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + $this->executeSchemaChange($schemaDiff); + } + + /** + * @brief replaces the ownCloud tables with a new set + * @param $file string path to the MDB2 xml db export file + */ + public function replaceDB( $file ) { + $apps = \OC_App::getAllApps(); + $this->conn->beginTransaction(); + // Delete the old tables - $this->removeDBStructure( OC::$SERVERROOT . '/db_structure.xml' ); ++ $this->removeDBStructure( \OC::$SERVERROOT . '/db_structure.xml' ); + + foreach($apps as $app) { + $path = \OC_App::getAppPath($app).'/appinfo/database.xml'; + if(file_exists($path)) { + $this->removeDBStructure( $path ); + } + } + + // Create new tables + $this->conn->commit(); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @return bool + */ + private function executeSchemaChange($schema) { + $this->conn->beginTransaction(); + foreach($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { + $this->conn->query($sql); + } + $this->conn->commit(); + return true; + } + }