You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

536 lines
15 KiB

  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Frank Karlitschek
  6. * @copyright 2010 Frank Karlitschek karlitschek@kde.org
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. /**
  23. * This class manages the access to the database. It basically is a wrapper for
  24. * MDB2 with some adaptions.
  25. */
  26. class OC_DB {
  27. const BACKEND_PDO=0;
  28. const BACKEND_MDB2=1;
  29. static private $connection; //the prefered connection to use, either PDO or MDB2
  30. static private $backend=null;
  31. static private $MDB2=false;
  32. static private $PDO=false;
  33. static private $schema=false;
  34. static private $affected=0;
  35. static private $result=false;
  36. /**
  37. * @brief connects to the database
  38. * @returns true if connection can be established or nothing (die())
  39. *
  40. * Connects to the database as specified in config.php
  41. */
  42. public static function connect($backend=null){
  43. if(self::$connection){
  44. return;
  45. }
  46. if(is_null($backend)){
  47. $backend=self::BACKEND_MDB2;
  48. if(class_exists('PDO') && OC_Config::getValue('installed', false)){//check if we can use PDO, else use MDB2 (instalation always needs to be done my mdb2)
  49. $type = OC_Config::getValue( "dbtype", "sqlite" );
  50. if($type=='sqlite3') $type='sqlite';
  51. $drivers=PDO::getAvailableDrivers();
  52. if(array_search($type,$drivers)!==false){
  53. $backend=self::BACKEND_PDO;
  54. }
  55. }
  56. }
  57. if($backend==self::BACKEND_PDO){
  58. self::connectPDO();
  59. self::$connection=self::$PDO;
  60. self::$backend=self::BACKEND_PDO;
  61. }else{
  62. self::connectMDB2();
  63. self::$connection=self::$MDB2;
  64. self::$backend=self::BACKEND_MDB2;
  65. }
  66. }
  67. /**
  68. * connect to the database using pdo
  69. */
  70. private static function connectPDO(){
  71. // The global data we need
  72. $name = OC_Config::getValue( "dbname", "owncloud" );
  73. $host = OC_Config::getValue( "dbhost", "" );
  74. $user = OC_Config::getValue( "dbuser", "" );
  75. $pass = OC_Config::getValue( "dbpassword", "" );
  76. $type = OC_Config::getValue( "dbtype", "sqlite" );
  77. $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' );
  78. // do nothing if the connection already has been established
  79. if(!self::$PDO){
  80. // Add the dsn according to the database type
  81. switch($type){
  82. case 'sqlite':
  83. $dsn='sqlite2:'.$datadir.'/'.$name.'.db';
  84. break;
  85. case 'sqlite3':
  86. $dsn='sqlite:'.$datadir.'/'.$name.'.db';
  87. break;
  88. case 'mysql':
  89. $dsn='mysql:dbname='.$name.';host='.$host;
  90. break;
  91. case 'pgsql':
  92. $dsn='pgsql:dbname='.$name.';host='.$host;
  93. break;
  94. }
  95. try{
  96. self::$PDO=new PDO($dsn,$user,$pass);
  97. }catch(PDOException $e){
  98. echo( '<b>can not connect to database, using '.$type.'. ('.$e->getMessage().')</center>');
  99. die();
  100. }
  101. // We always, really always want associative arrays
  102. self::$PDO->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
  103. self::$PDO->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
  104. }
  105. return true;
  106. }
  107. /**
  108. * connect to the database using mdb2
  109. */
  110. static private function connectMDB2(){
  111. // The global data we need
  112. $name = OC_Config::getValue( "dbname", "owncloud" );
  113. $host = OC_Config::getValue( "dbhost", "" );
  114. $user = OC_Config::getValue( "dbuser", "" );
  115. $pass = OC_Config::getValue( "dbpassword", "" );
  116. $type = OC_Config::getValue( "dbtype", "sqlite" );
  117. $SERVERROOT=OC::$SERVERROOT;
  118. $datadir=OC_Config::getValue( "datadirectory", "$SERVERROOT/data" );
  119. // do nothing if the connection already has been established
  120. if(!self::$MDB2){
  121. // Require MDB2.php (not required in the head of the file so we only load it when needed)
  122. require_once('MDB2.php');
  123. // Prepare options array
  124. $options = array(
  125. 'portability' => MDB2_PORTABILITY_ALL,
  126. 'log_line_break' => '<br>',
  127. 'idxname_format' => '%s',
  128. 'debug' => true,
  129. 'quote_identifier' => true );
  130. // Add the dsn according to the database type
  131. switch($type){
  132. case 'sqlite':
  133. case 'sqlite3':
  134. $dsn = array(
  135. 'phptype' => $type,
  136. 'database' => "$datadir/$name.db",
  137. 'mode' => '0644'
  138. );
  139. break;
  140. case 'mysql':
  141. $dsn = array(
  142. 'phptype' => 'mysql',
  143. 'username' => $user,
  144. 'password' => $pass,
  145. 'hostspec' => $host,
  146. 'database' => $name
  147. );
  148. break;
  149. case 'pgsql':
  150. $dsn = array(
  151. 'phptype' => 'pgsql',
  152. 'username' => $user,
  153. 'password' => $pass,
  154. 'hostspec' => $host,
  155. 'database' => $name
  156. );
  157. break;
  158. }
  159. // Try to establish connection
  160. self::$MDB2 = MDB2::factory( $dsn, $options );
  161. // Die if we could not connect
  162. if( PEAR::isError( self::$MDB2 )){
  163. echo( '<b>can not connect to database, using '.$type.'. ('.self::$MDB2->getUserInfo().')</center>');
  164. OC_Log::write('core',self::$MDB2->getUserInfo(),OC_Log::FATAL);
  165. OC_Log::write('core',self::$MDB2->getMessage(),OC_Log::FATAL);
  166. die( $error );
  167. }
  168. // We always, really always want associative arrays
  169. self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC);
  170. }
  171. // we are done. great!
  172. return true;
  173. }
  174. /**
  175. * @brief Prepare a SQL query
  176. * @param $query Query string
  177. * @returns prepared SQL query
  178. *
  179. * SQL query via MDB2 prepare(), needs to be execute()'d!
  180. */
  181. static public function prepare( $query ){
  182. // Optimize the query
  183. $query = self::processQuery( $query );
  184. self::connect();
  185. // return the result
  186. if(self::$backend==self::BACKEND_MDB2){
  187. $result = self::$connection->prepare( $query );
  188. // Die if we have an error (error means: bad query, not 0 results!)
  189. if( PEAR::isError($result)) {
  190. $entry = 'DB Error: "'.$result->getMessage().'"<br />';
  191. $entry .= 'Offending command was: '.$query.'<br />';
  192. OC_Log::write('core',$entry,OC_Log::FATAL);
  193. die( $entry );
  194. }
  195. }else{
  196. try{
  197. $result=self::$connection->prepare($query);
  198. }catch(PDOException $e){
  199. $entry = 'DB Error: "'.$e->getMessage().'"<br />';
  200. $entry .= 'Offending command was: '.$query.'<br />';
  201. OC_Log::write('core',$entry,OC_Log::FATAL);
  202. die( $entry );
  203. }
  204. $result=new PDOStatementWrapper($result);
  205. }
  206. return $result;
  207. }
  208. /**
  209. * @brief gets last value of autoincrement
  210. * @returns id
  211. *
  212. * MDB2 lastInsertID()
  213. *
  214. * Call this method right after the insert command or other functions may
  215. * cause trouble!
  216. */
  217. public static function insertid(){
  218. self::connect();
  219. return self::$connection->lastInsertId();
  220. }
  221. /**
  222. * @brief Disconnect
  223. * @returns true/false
  224. *
  225. * This is good bye, good bye, yeah!
  226. */
  227. public static function disconnect(){
  228. // Cut connection if required
  229. if(self::$connection){
  230. if(self::$backend==self::BACKEND_MDB2){
  231. self::$connection->disconnect();
  232. }
  233. self::$connection=false;
  234. self::$mdb2=false;
  235. self::$pdo=false;
  236. }
  237. return true;
  238. }
  239. /**
  240. * @brief saves database scheme to xml file
  241. * @param $file name of file
  242. * @returns true/false
  243. *
  244. * TODO: write more documentation
  245. */
  246. public static function getDbStructure( $file ,$mode=MDB2_SCHEMA_DUMP_STRUCTURE){
  247. self::connectScheme();
  248. // write the scheme
  249. $definition = self::$schema->getDefinitionFromDatabase();
  250. $dump_options = array(
  251. 'output_mode' => 'file',
  252. 'output' => $file,
  253. 'end_of_line' => "\n"
  254. );
  255. self::$schema->dumpDatabase( $definition, $dump_options, MDB2_SCHEMA_DUMP_STRUCTURE );
  256. return true;
  257. }
  258. /**
  259. * @brief Creates tables from XML file
  260. * @param $file file to read structure from
  261. * @returns true/false
  262. *
  263. * TODO: write more documentation
  264. */
  265. public static function createDbFromStructure( $file ){
  266. $CONFIG_DBNAME = OC_Config::getValue( "dbname", "owncloud" );
  267. $CONFIG_DBTABLEPREFIX = OC_Config::getValue( "dbtableprefix", "oc_" );
  268. $CONFIG_DBTYPE = OC_Config::getValue( "dbtype", "sqlite" );
  269. self::connectScheme();
  270. // read file
  271. $content = file_get_contents( $file );
  272. // Make changes and save them to a temporary file
  273. $file2 = tempnam( get_temp_dir(), 'oc_db_scheme_' );
  274. $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content );
  275. $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content );
  276. if( $CONFIG_DBTYPE == 'pgsql' ){ //mysql support it too but sqlite doesn't
  277. $content = str_replace( '<default>0000-00-00 00:00:00</default>', '<default>CURRENT_TIMESTAMP</default>', $content );
  278. }
  279. file_put_contents( $file2, $content );
  280. // Try to create tables
  281. $definition = self::$schema->parseDatabaseDefinitionFile( $file2 );
  282. // Delete our temporary file
  283. unlink( $file2 );
  284. // Die in case something went wrong
  285. if( $definition instanceof MDB2_Schema_Error ){
  286. die( $definition->getMessage().': '.$definition->getUserInfo());
  287. }
  288. // if(OC_Config::getValue('dbtype','sqlite')=='sqlite'){
  289. // $definition['overwrite']=true;//always overwrite for sqlite
  290. // }
  291. $ret=self::$schema->createDatabase( $definition );
  292. // Die in case something went wrong
  293. if( $ret instanceof MDB2_Error ){
  294. die ($ret->getMessage() . ': ' . $ret->getUserInfo());
  295. }
  296. return true;
  297. }
  298. /**
  299. * @brief update the database scheme
  300. * @param $file file to read structure from
  301. */
  302. public static function updateDbFromStructure($file){
  303. $CONFIG_DBNAME = OC_Config::getValue( "dbname", "owncloud" );
  304. $CONFIG_DBTABLEPREFIX = OC_Config::getValue( "dbtableprefix", "oc_" );
  305. $CONFIG_DBTYPE = OC_Config::getValue( "dbtype", "sqlite" );
  306. self::connectScheme();
  307. // read file
  308. $content = file_get_contents( $file );
  309. // Make changes and save them to a temporary file
  310. $file2 = tempnam( get_temp_dir(), 'oc_db_scheme_' );
  311. $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content );
  312. $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content );
  313. if( $CONFIG_DBTYPE == 'pgsql' ){ //mysql support it too but sqlite doesn't
  314. $content = str_replace( '<default>0000-00-00 00:00:00</default>', '<default>CURRENT_TIMESTAMP</default>', $content );
  315. }
  316. file_put_contents( $file2, $content );
  317. $previousSchema = self::$schema->getDefinitionFromDatabase();
  318. $op = $schema->updateDatabase($file2, $previousSchema, array(), false);
  319. if (PEAR::isError($op)) {
  320. $error = $op->getMessage();
  321. OC_Log::write('core','Failed to update database structure ('.$error.')',OC_Log::FATAL);
  322. return false;
  323. }
  324. return true;
  325. }
  326. /**
  327. * @brief connects to a MDB2 database scheme
  328. * @returns true/false
  329. *
  330. * Connects to a MDB2 database scheme
  331. */
  332. private static function connectScheme(){
  333. // We need a mdb2 database connection
  334. self::connectMDB2();
  335. // Connect if this did not happen before
  336. if(!self::$schema){
  337. require_once('MDB2/Schema.php');
  338. self::$schema=MDB2_Schema::factory(self::$MDB2);
  339. }
  340. return true;
  341. }
  342. /**
  343. * @brief does minor chages to query
  344. * @param $query Query string
  345. * @returns corrected query string
  346. *
  347. * This function replaces *PREFIX* with the value of $CONFIG_DBTABLEPREFIX
  348. * and replaces the ` woth ' or " according to the database driver.
  349. */
  350. private static function processQuery( $query ){
  351. self::connect();
  352. // We need Database type and table prefix
  353. $type = OC_Config::getValue( "dbtype", "sqlite" );
  354. $prefix = OC_Config::getValue( "dbtableprefix", "oc_" );
  355. // differences in escaping of table names ('`' for mysql) and getting the current timestamp
  356. if( $type == 'sqlite' || $type == 'sqlite3' ){
  357. $query = str_replace( '`', '\'', $query );
  358. $query = str_replace( 'NOW()', 'datetime(\'now\')', $query );
  359. $query = str_replace( 'now()', 'datetime(\'now\')', $query );
  360. }elseif( $type == 'mysql' ){
  361. $query = str_replace( 'NOW()', 'CURRENT_TIMESTAMP', $query );
  362. $query = str_replace( 'now()', 'CURRENT_TIMESTAMP', $query );
  363. }elseif( $type == 'pgsql' ){
  364. $query = str_replace( '`', '"', $query );
  365. $query = str_replace( 'NOW()', 'CURRENT_TIMESTAMP', $query );
  366. $query = str_replace( 'now()', 'CURRENT_TIMESTAMP', $query );
  367. }
  368. // replace table name prefix
  369. $query = str_replace( '*PREFIX*', $prefix, $query );
  370. return $query;
  371. }
  372. /**
  373. * @brief drop a table
  374. * @param string $tableNamme the table to drop
  375. */
  376. public static function dropTable($tableName){
  377. self::connectMDB2();
  378. self::$MDB2->loadModule('Manager');
  379. self::$MDB2->dropTable($tableName);
  380. }
  381. /**
  382. * remove all tables defined in a database structure xml file
  383. * @param string $file the xml file describing the tables
  384. */
  385. public static function removeDBStructure($file){
  386. $CONFIG_DBNAME = OC_Config::getValue( "dbname", "owncloud" );
  387. $CONFIG_DBTABLEPREFIX = OC_Config::getValue( "dbtableprefix", "oc_" );
  388. self::connectScheme();
  389. // read file
  390. $content = file_get_contents( $file );
  391. // Make changes and save them to a temporary file
  392. $file2 = tempnam( get_temp_dir(), 'oc_db_scheme_' );
  393. $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content );
  394. $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content );
  395. file_put_contents( $file2, $content );
  396. // get the tables
  397. $definition = self::$schema->parseDatabaseDefinitionFile( $file2 );
  398. // Delete our temporary file
  399. unlink( $file2 );
  400. foreach($definition['tables'] as $name=>$table){
  401. self::dropTable($name);
  402. }
  403. }
  404. /**
  405. * Start a transaction
  406. */
  407. public static function beginTransaction(){
  408. self::connect();
  409. if (self::$backend=self::BACKEND_MDB2 && !self::$connection->supports('transactions')) {
  410. return false;
  411. }
  412. self::$connection->beginTransaction();
  413. }
  414. /**
  415. * Commit the database changes done during a transaction that is in progress
  416. */
  417. public static function commit($savePoint=''){
  418. self::connect();
  419. if(!self::$connection->inTransaction()){
  420. return false;
  421. }
  422. self::$connection->commit();
  423. }
  424. }
  425. /**
  426. * small wrapper around PDOStatement to make it behave ,more like an MDB2 Statement
  427. */
  428. class PDOStatementWrapper{
  429. private $statement=null;
  430. private $lastArguments=array();
  431. public function __construct($statement){
  432. $this->statement=$statement;
  433. }
  434. /**
  435. * make exucute return the result instead of a bool
  436. */
  437. public function execute($input=array()){
  438. $this->lastArguments=$input;
  439. if(count($input)>0){
  440. $this->statement->execute($input);
  441. }else{
  442. $this->statement->execute();
  443. }
  444. return $this;
  445. }
  446. /**
  447. * provide numRows
  448. */
  449. public function numRows(){
  450. $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i';
  451. if (preg_match($regex, $this->statement->queryString, $output) > 0) {
  452. $query = OC_DB::prepare("SELECT COUNT(*) FROM {$output[1]}", PDO::FETCH_NUM);
  453. return $query->execute($this->lastArguments)->fetchColumn();
  454. }else{
  455. return $this->statement->rowCount();
  456. }
  457. }
  458. /**
  459. * provide an alias for fetch
  460. */
  461. public function fetchRow(){
  462. return $this->statement->fetch();
  463. }
  464. /**
  465. * pass all other function directly to the PDOStatement
  466. */
  467. public function __call($name,$arguments){
  468. return call_user_func_array(array($this->statement,$name),$arguments);
  469. }
  470. /**
  471. * Provide a simple fetchOne.
  472. * fetch single column from the next row
  473. * @param int $colnum the column number to fetch
  474. */
  475. public function fetchOne($colnum = 0){
  476. return $this->statement->fetchColumn($colnum);
  477. }
  478. }