PageRenderTime 68ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/php/MDB/Manager.php

https://bitbucket.org/adarshj/convenient_website
PHP | 2148 lines | 1657 code | 85 blank | 406 comment | 439 complexity | bd1ebbc794ec7f9e153393dfd80db32c MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-2-Clause, GPL-2.0, LGPL-3.0
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP Version 4 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox, |
  6. // | Stig. S. Bakken, Lukas Smith |
  7. // | All rights reserved. |
  8. // +----------------------------------------------------------------------+
  9. // | MDB is a merge of PEAR DB and Metabases that provides a unified DB |
  10. // | API as well as database abstraction for PHP applications. |
  11. // | This LICENSE is in the BSD license style. |
  12. // | |
  13. // | Redistribution and use in source and binary forms, with or without |
  14. // | modification, are permitted provided that the following conditions |
  15. // | are met: |
  16. // | |
  17. // | Redistributions of source code must retain the above copyright |
  18. // | notice, this list of conditions and the following disclaimer. |
  19. // | |
  20. // | Redistributions in binary form must reproduce the above copyright |
  21. // | notice, this list of conditions and the following disclaimer in the |
  22. // | documentation and/or other materials provided with the distribution. |
  23. // | |
  24. // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, |
  25. // | Lukas Smith nor the names of his contributors may be used to endorse |
  26. // | or promote products derived from this software without specific prior|
  27. // | written permission. |
  28. // | |
  29. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  30. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  31. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
  32. // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
  33. // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
  34. // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
  35. // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
  36. // | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
  37. // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  38. // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
  39. // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
  40. // | POSSIBILITY OF SUCH DAMAGE. |
  41. // +----------------------------------------------------------------------+
  42. // | Author: Lukas Smith <smith@backendmedia.com> |
  43. // +----------------------------------------------------------------------+
  44. //
  45. // $Id: Manager.php,v 1.75.4.4 2004/03/10 14:42:59 lsmith Exp $
  46. //
  47. require_once('MDB/Parser.php');
  48. define('MDB_MANAGER_DUMP_ALL', 0);
  49. define('MDB_MANAGER_DUMP_STRUCTURE', 1);
  50. define('MDB_MANAGER_DUMP_CONTENT', 2);
  51. /**
  52. * The database manager is a class that provides a set of database
  53. * management services like installing, altering and dumping the data
  54. * structures of databases.
  55. *
  56. * @package MDB
  57. * @category Database
  58. * @author Lukas Smith <smith@backendmedia.com>
  59. */
  60. class MDB_Manager extends PEAR
  61. {
  62. // {{{ properties
  63. var $database;
  64. var $options = array(
  65. 'fail_on_invalid_names' => 1,
  66. 'debug' => 0
  67. );
  68. var $invalid_names = array(
  69. 'user' => array(),
  70. 'is' => array(),
  71. 'file' => array(
  72. 'oci' => array(),
  73. 'oracle' => array()
  74. ),
  75. 'notify' => array(
  76. 'pgsql' => array()
  77. ),
  78. 'restrict' => array(
  79. 'mysql' => array()
  80. ),
  81. 'password' => array(
  82. 'ibase' => array()
  83. )
  84. );
  85. var $default_values = array(
  86. 'integer' => 0,
  87. 'float' => 0,
  88. 'decimal' => 0,
  89. 'text' => '',
  90. 'timestamp' => '0001-01-01 00:00:00',
  91. 'date' => '0001-01-01',
  92. 'time' => '00:00:00'
  93. );
  94. var $warnings = array();
  95. var $database_definition = array(
  96. 'name' => '',
  97. 'create' => 0,
  98. 'TABLES' => array()
  99. );
  100. // }}}
  101. // {{{ raiseError()
  102. /**
  103. * This method is used to communicate an error and invoke error
  104. * callbacks etc. Basically a wrapper for PEAR::raiseError
  105. * without the message string.
  106. *
  107. * @param mixed $code integer error code, or a PEAR error object (all
  108. * other parameters are ignored if this parameter is an object
  109. * @param int $mode error mode, see PEAR_Error docs
  110. * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the
  111. * error level (E_USER_NOTICE etc). If error mode is
  112. * PEAR_ERROR_CALLBACK, this is the callback function, either as a
  113. * function name, or as an array of an object and method name. For
  114. * other error modes this parameter is ignored.
  115. * @param string $userinfo Extra debug information. Defaults to the last
  116. * query and native error code.
  117. * @param mixed $nativecode Native error code, integer or string depending
  118. * the backend.
  119. * @return object a PEAR error object
  120. * @access public
  121. * @see PEAR_Error
  122. */
  123. function &raiseError($code = MDB_MANAGER_ERROR, $mode = NULL, $options = NULL,
  124. $userinfo = NULL, $nativecode = NULL)
  125. {
  126. // The error is yet a MDB error object
  127. if(is_object($code)) {
  128. $err = PEAR::raiseError($code, NULL, NULL, NULL, NULL, NULL, TRUE);
  129. return($err);
  130. }
  131. $err = PEAR::raiseError(NULL, $code, $mode, $options, $userinfo,
  132. 'MDB_Error', TRUE);
  133. return($err);
  134. }
  135. // }}}
  136. // {{{ captureDebugOutput()
  137. /**
  138. * set a debug handler
  139. *
  140. * @param string $capture name of the function that should be used in
  141. * debug()
  142. * @access public
  143. * @see debug()
  144. */
  145. function captureDebugOutput($capture)
  146. {
  147. $this->options['debug'] = $capture;
  148. $this->database->captureDebugOutput(1);
  149. }
  150. // }}}
  151. // {{{ debugOutput()
  152. /**
  153. * output debug info
  154. *
  155. * @return string content of the debug_output class variable
  156. * @access public
  157. */
  158. function debugOutput()
  159. {
  160. return($this->database->debugOutput());
  161. }
  162. // }}}
  163. // {{{ resetWarnings()
  164. /**
  165. * reset the warning array
  166. *
  167. * @access public
  168. */
  169. function resetWarnings()
  170. {
  171. $this->warnings = array();
  172. }
  173. // }}}
  174. // {{{ getWarnings()
  175. /**
  176. * get all warnings in reverse order.
  177. * This means that the last warning is the first element in the array
  178. *
  179. * @return array with warnings
  180. * @access public
  181. * @see resetWarnings()
  182. */
  183. function getWarnings()
  184. {
  185. return array_reverse($this->warnings);
  186. }
  187. // }}}
  188. // {{{ setOption()
  189. /**
  190. * set the option for the db class
  191. *
  192. * @param string $option option name
  193. * @param mixed $value value for the option
  194. * @return mixed MDB_OK or MDB_Error
  195. * @access public
  196. */
  197. function setOption($option, $value)
  198. {
  199. if(isset($this->options[$option])) {
  200. $this->options[$option] = $value;
  201. return(MDB_OK);
  202. }
  203. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, "unknown option $option"));
  204. }
  205. // }}}
  206. // {{{ getOption()
  207. /**
  208. * returns the value of an option
  209. *
  210. * @param string $option option name
  211. * @return mixed the option value or error object
  212. * @access public
  213. */
  214. function getOption($option)
  215. {
  216. if(isset($this->options[$option])) {
  217. return($this->options[$option]);
  218. }
  219. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, "unknown option $option"));
  220. }
  221. // }}}
  222. // {{{ connect()
  223. /**
  224. * Create a new MDB connection object and connect to the specified
  225. * database
  226. *
  227. * @param mixed $dbinfo 'data source name', see the MDB::parseDSN
  228. * method for a description of the dsn format.
  229. * Can also be specified as an array of the
  230. * format returned by MDB::parseDSN.
  231. * Finally you can also pass an existing db
  232. * object to be used.
  233. * @param mixed $options An associative array of option names and
  234. * their values.
  235. * @return mixed MDB_OK on success, or a MDB error object
  236. * @access public
  237. * @see MDB::parseDSN
  238. */
  239. function &connect(&$dbinfo, $options = FALSE)
  240. {
  241. if(is_object($this->database) && !MDB::isError($this->database)) {
  242. $this->disconnect();
  243. }
  244. if(is_object($dbinfo)) {
  245. $this->database =& $dbinfo;
  246. } else {
  247. $this->database =& MDB::connect($dbinfo, $options);
  248. if(MDB::isError($this->database)) {
  249. return($this->database);
  250. }
  251. }
  252. if(is_array($options)) {
  253. $this->options = array_merge($options, $this->options);
  254. }
  255. return(MDB_OK);
  256. }
  257. // }}}
  258. // {{{ disconnect()
  259. /**
  260. * Log out and disconnect from the database.
  261. *
  262. * @access public
  263. */
  264. function disconnect()
  265. {
  266. if(is_object($this->database) && !MDB::isError($this->database)) {
  267. $this->database->disconnect();
  268. unset($this->database);
  269. }
  270. }
  271. // }}}
  272. // {{{ setDatabase()
  273. /**
  274. * Select a different database
  275. *
  276. * @param string $name name of the database that should be selected
  277. * @return string name of the database previously connected to
  278. * @access public
  279. */
  280. function setDatabase($name)
  281. {
  282. return($this->database->setDatabase($name));
  283. }
  284. // }}}
  285. // {{{ _createTable()
  286. /**
  287. * create a table and inititialize the table if data is available
  288. *
  289. * @param string $table_name name of the table to be created
  290. * @param array $table multi dimensional array that containts the
  291. * structure and optional data of the table
  292. * @param boolean $overwrite determine if the table/index should be
  293. overwritten if it already exists
  294. * @return mixed MDB_OK on success, or a MDB error object
  295. * @access private
  296. */
  297. function _createTable($table_name, $table, $overwrite = FALSE)
  298. {
  299. $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  300. $result = $this->database->createTable($table_name, $table['FIELDS']);
  301. $this->popExpect();
  302. if(MDB::isError($result)) {
  303. if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  304. $this->warnings[] = 'Table already exists: '.$table_name;
  305. if($overwrite) {
  306. $this->database->debug('Overwritting Table');
  307. $result = $this->database->dropTable($table_name);
  308. if(MDB::isError($result)) {
  309. return($result);
  310. }
  311. $result = $this->database->createTable($table_name, $table['FIELDS']);
  312. if(MDB::isError($result)) {
  313. return($result);
  314. }
  315. } else {
  316. $result = MDB_OK;
  317. }
  318. } else {
  319. $this->database->debug('Create table error: '.$table_name);
  320. return($result);
  321. }
  322. }
  323. if(isset($table['initialization']) && is_array($table['initialization'])) {
  324. foreach($table['initialization'] as $instruction) {
  325. switch($instruction['type']) {
  326. case 'insert':
  327. $query_fields = $query_values = array();
  328. if(isset($instruction['FIELDS']) && is_array($instruction['FIELDS'])) {
  329. foreach($instruction['FIELDS'] as $field_name => $field) {
  330. $query_fields[] = $field_name;
  331. $query_values[] = '?';
  332. }
  333. $query_fields = implode(',',$query_fields);
  334. $query_values = implode(',',$query_values);
  335. $result = $prepared_query = $this->database->prepareQuery(
  336. "INSERT INTO $table_name ($query_fields) VALUES ($query_values)");
  337. }
  338. if(!MDB::isError($prepared_query)) {
  339. if(isset($instruction['FIELDS']) && is_array($instruction['FIELDS'])) {
  340. $lobs = array();
  341. $field_number = 0;
  342. foreach($instruction['FIELDS'] as $field_name => $field) {
  343. $field_number++;
  344. $query = $field_name;
  345. switch($table['FIELDS'][$field_name]['type']) {
  346. case 'integer':
  347. $result = $this->database->setParamInteger($prepared_query,
  348. $field_number, intval($field));
  349. break;
  350. case 'text':
  351. $result = $this->database->setParamText($prepared_query,
  352. $field_number, $field);
  353. break;
  354. case 'clob':
  355. $lob_definition = array(
  356. 'Database' => $this->database,
  357. 'Error' => '',
  358. 'Data' => $field
  359. );
  360. if(MDB::isError($result = $this->database->createLob($lob_definition)))
  361. {
  362. break;
  363. }
  364. $lob = count($lobs);
  365. $lobs[$lob] = $result;
  366. $result = $this->database->setParamClob($prepared_query,
  367. $field_number, $lobs[$lob], $field_name);
  368. break;
  369. case 'blob':
  370. $lob_definition = array(
  371. 'Database' => $this->database,
  372. 'Error' => '',
  373. 'Data' => $field
  374. );
  375. if(MDB::isError($result = $this->database->createLob($lob_definition))) {
  376. break;
  377. }
  378. $lob = count($lobs);
  379. $lobs[$lob] = $result;
  380. $result = $this->database->setParamBlob($prepared_query,
  381. $field_number, $lobs[$lob], $field_name);
  382. break;
  383. case 'boolean':
  384. $result = $this->database->setParamBoolean($prepared_query,
  385. $field_number, intval($field));
  386. break;
  387. case 'date':
  388. $result = $this->database->setParamDate($prepared_query,
  389. $field_number, $field);
  390. break;
  391. case 'timestamp':
  392. $result = $this->database->setParamTimestamp($prepared_query,
  393. $field_number, $field);
  394. break;
  395. case 'time':
  396. $result = $this->database->setParamTime($prepared_query,
  397. $field_number, $field);
  398. break;
  399. case 'float':
  400. $result = $this->database->setParamFloat($prepared_query,
  401. $field_number, doubleval($field));
  402. break;
  403. case 'decimal':
  404. $result = $this->database->setParamDecimal($prepared_query,
  405. $field_number, $field);
  406. break;
  407. default:
  408. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  409. 'type "'.$field['type'].'" is not yet supported');
  410. break;
  411. }
  412. if(MDB::isError($result)) {
  413. break;
  414. }
  415. }
  416. }
  417. if(!MDB::isError($result)) {
  418. $result = $this->database->executeQuery($prepared_query);
  419. }
  420. for($lob = 0; $lob < count($lobs); $lob++) {
  421. $this->database->destroyLOB($lobs[$lob]);
  422. }
  423. $this->database->freePreparedQuery($prepared_query);
  424. }
  425. break;
  426. }
  427. }
  428. };
  429. if(!MDB::isError($result) && isset($table['INDEXES']) && is_array($table['INDEXES'])) {
  430. if(!$this->database->support('Indexes')) {
  431. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  432. 'indexes are not supported'));
  433. }
  434. foreach($table['INDEXES'] as $index_name => $index) {
  435. $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  436. $result = $this->database->createIndex($table_name, $index_name, $index);
  437. $this->popExpect();
  438. if(MDB::isError($result)) {
  439. if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  440. $this->warnings[] = 'Index already exists: '.$index_name;
  441. if($overwrite) {
  442. $this->database->debug('Overwritting Index');
  443. $result = $this->database->dropIndex($table_name, $index_name);
  444. if(MDB::isError($result)) {
  445. break;
  446. }
  447. $result = $this->database->createIndex($table_name, $index_name, $index);
  448. if(MDB::isError($result)) {
  449. break;
  450. }
  451. } else {
  452. $result = MDB_OK;
  453. }
  454. } else {
  455. $this->database->debug('Create index error: '.$table_name);
  456. break;
  457. }
  458. }
  459. }
  460. }
  461. if(MDB::isError($result)) {
  462. $result = $this->database->dropTable($table_name);
  463. if(MDB::isError($result)) {
  464. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  465. 'could not drop the table ('
  466. .$result->getMessage().' ('.$result->getUserinfo(),'))',
  467. 'MDB_Error', TRUE);
  468. }
  469. return($result);
  470. }
  471. return(MDB_OK);
  472. }
  473. // }}}
  474. // {{{ _dropTable()
  475. /**
  476. * drop a table
  477. *
  478. * @param string $table_name name of the table to be dropped
  479. * @return mixed MDB_OK on success, or a MDB error object
  480. * @access private
  481. */
  482. function _dropTable($table_name)
  483. {
  484. return($this->database->dropTable($table_name));
  485. }
  486. // }}}
  487. // {{{ _createSequence()
  488. /**
  489. * create a sequence
  490. *
  491. * @param string $sequence_name name of the sequence to be created
  492. * @param array $sequence multi dimensional array that containts the
  493. * structure and optional data of the table
  494. * @param string $created_on_table
  495. * @param boolean $overwrite determine if the sequence should be overwritten
  496. if it already exists
  497. * @return mixed MDB_OK on success, or a MDB error object
  498. * @access private
  499. */
  500. function _createSequence($sequence_name, $sequence, $created_on_table, $overwrite = FALSE)
  501. {
  502. if(!$this->database->support('Sequences')) {
  503. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  504. 'sequences are not supported'));
  505. }
  506. if(!isset($sequence_name) || !strcmp($sequence_name, '')) {
  507. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  508. 'no valid sequence name specified'));
  509. }
  510. $this->database->debug('Create sequence: '.$sequence_name);
  511. if(isset($sequence['start']) && $sequence['start'] != '') {
  512. $start = $sequence['start'];
  513. } else if(isset($sequence['on']) && !$created_on_table) {
  514. $table = $sequence['on']['table'];
  515. $field = $sequence['on']['field'];
  516. if($this->database->support('Summaryfunctions')) {
  517. $field = "MAX($field)";
  518. }
  519. $start = $this->database->queryOne("SELECT $field FROM $table");
  520. if(MDB::isError($start)) {
  521. return($start);
  522. }
  523. } else {
  524. $start = 1;
  525. }
  526. $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  527. $result = $this->database->createSequence($sequence_name, $start);
  528. $this->popExpect();
  529. if(MDB::isError($result)) {
  530. if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  531. $this->warnings[] = 'Sequence already exists: '.$sequence_name;
  532. if($overwrite) {
  533. $this->database->debug('Overwritting Sequence');
  534. $result = $this->database->dropSequence($sequence_name);
  535. if(MDB::isError($result)) {
  536. return($result);
  537. }
  538. $result = $this->database->createSequence($sequence_name, $start);
  539. if(MDB::isError($result)) {
  540. return($result);
  541. }
  542. } else {
  543. return(MDB_OK);
  544. }
  545. } else {
  546. $this->database->debug('Create sequence error: '.$sequence_name);
  547. return($result);
  548. }
  549. }
  550. }
  551. // }}}
  552. // {{{ _dropSequence()
  553. /**
  554. * drop a table
  555. *
  556. * @param string $sequence_name name of the sequence to be dropped
  557. * @return mixed MDB_OK on success, or a MDB error object
  558. * @access private
  559. */
  560. function _dropSequence($sequence_name)
  561. {
  562. if(!$this->database->support('Sequences')) {
  563. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  564. 'sequences are not supported'));
  565. }
  566. $this->database->debug('Dropping sequence: '.$sequence_name);
  567. if(!isset($sequence_name) || !strcmp($sequence_name, '')) {
  568. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  569. 'no valid sequence name specified'));
  570. }
  571. return($this->database->dropSequence($sequence_name));
  572. }
  573. // }}}
  574. // {{{ _createDatabase()
  575. /**
  576. * Create a database space within which may be created database objects
  577. * like tables, indexes and sequences. The implementation of this function
  578. * is highly DBMS specific and may require special permissions to run
  579. * successfully. Consult the documentation or the DBMS drivers that you
  580. * use to be aware of eventual configuration requirements.
  581. *
  582. * @return mixed MDB_OK on success, or a MDB error object
  583. * @access private
  584. */
  585. function _createDatabase()
  586. {
  587. if(!isset($this->database_definition['name'])
  588. || !strcmp($this->database_definition['name'], '')
  589. ) {
  590. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  591. 'no valid database name specified'));
  592. }
  593. $create = (isset($this->database_definition['create']) && $this->database_definition['create']);
  594. $overwrite = (isset($this->database_definition['overwrite']) && $this->database_definition['overwrite']);
  595. if($create) {
  596. $this->database->debug('Create database: '.$this->database_definition['name']);
  597. $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  598. $result = $this->database->createDatabase($this->database_definition['name']);
  599. $this->popExpect();
  600. if(MDB::isError($result)) {
  601. if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  602. $this->warnings[] = 'Database already exists: '.$this->database_definition['name'];
  603. if($overwrite) {
  604. $this->database->debug('Overwritting Database');
  605. $result = $this->database->dropDatabase($this->database_definition['name']);
  606. if(MDB::isError($result)) {
  607. return($result);
  608. }
  609. $result = $this->database->createDatabase($this->database_definition['name']);
  610. if(MDB::isError($result)) {
  611. return($result);
  612. }
  613. } else {
  614. $result = MDB_OK;
  615. }
  616. } else {
  617. $this->database->debug('Create database error.');
  618. return($result);
  619. }
  620. }
  621. }
  622. $previous_database_name = $this->database->setDatabase($this->database_definition['name']);
  623. if(($support_transactions = $this->database->support('Transactions'))
  624. && MDB::isError($result = $this->database->autoCommit(FALSE))
  625. ) {
  626. return($result);
  627. }
  628. $created_objects = 0;
  629. if(isset($this->database_definition['TABLES'])
  630. && is_array($this->database_definition['TABLES'])
  631. ) {
  632. foreach($this->database_definition['TABLES'] as $table_name => $table) {
  633. $result = $this->_createTable($table_name, $table, $overwrite);
  634. if(MDB::isError($result)) {
  635. break;
  636. }
  637. $created_objects++;
  638. }
  639. }
  640. if(!MDB::isError($result)
  641. && isset($this->database_definition['SEQUENCES'])
  642. && is_array($this->database_definition['SEQUENCES'])
  643. ) {
  644. foreach($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) {
  645. $result = $this->_createSequence($sequence_name, $sequence, 0, $overwrite);
  646. if(MDB::isError($result)) {
  647. break;
  648. }
  649. $created_objects++;
  650. }
  651. }
  652. if(MDB::isError($result)) {
  653. if($created_objects) {
  654. if($support_transactions) {
  655. $res = $this->database->rollback();
  656. if(MDB::isError($res))
  657. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  658. 'Could not rollback the partially created database alterations ('
  659. .$result->getMessage().' ('.$result->getUserinfo(),'))',
  660. 'MDB_Error', TRUE);
  661. } else {
  662. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  663. 'the database was only partially created ('
  664. .$result->getMessage().' ('.$result->getUserinfo(),'))',
  665. 'MDB_Error', TRUE);
  666. }
  667. }
  668. } else {
  669. if($support_transactions) {
  670. $res = $this->database->autoCommit(TRUE);
  671. if(MDB::isError($res))
  672. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  673. 'Could not end transaction after successfully created the database ('
  674. .$res->getMessage().' ('.$res->getUserinfo(),'))',
  675. 'MDB_Error', TRUE);
  676. }
  677. }
  678. $this->database->setDatabase($previous_database_name);
  679. if(MDB::isError($result)
  680. && $create
  681. && MDB::isError($res = $this->database->dropDatabase($this->database_definition['name']))
  682. ) {
  683. return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  684. 'Could not drop the created database after unsuccessful creation attempt ('
  685. .$res->getMessage().' ('.$res->getUserinfo(),'))',
  686. 'MDB_Error', TRUE));
  687. }
  688. if(MDB::isError($result)) {
  689. return($result);
  690. }
  691. return(MDB_OK);
  692. }
  693. // }}}
  694. // {{{ _addDefinitionChange()
  695. /**
  696. * add change to an array of multiple changes
  697. *
  698. * @param array &$changes
  699. * @param string $definition
  700. * @param string $item
  701. * @param array $change
  702. * @return mixed MDB_OK on success, or a MDB error object
  703. * @access private
  704. */
  705. function _addDefinitionChange(&$changes, $definition, $item, $change)
  706. {
  707. if(!isset($changes[$definition][$item])) {
  708. $changes[$definition][$item] = array();
  709. }
  710. foreach($change as $change_data_name => $change_data) {
  711. if(isset($change_data) && is_array($change_data)) {
  712. if(!isset($changes[$definition][$item][$change_data_name])) {
  713. $changes[$definition][$item][$change_data_name] = array();
  714. }
  715. foreach($change_data as $change_part_name => $change_part) {
  716. $changes[$definition][$item][$change_data_name][$change_part_name] = $change_part;
  717. }
  718. } else {
  719. $changes[$definition][$item][$change_data_name] = $change_data;
  720. }
  721. }
  722. return(MDB_OK);
  723. }
  724. // }}}
  725. // {{{ _compareDefinitions()
  726. /**
  727. * compare a previous definition with the currenlty parsed definition
  728. *
  729. * @param array multi dimensional array that contains the previous definition
  730. * @return mixed array of changes on success, or a MDB error object
  731. * @access private
  732. */
  733. function _compareDefinitions($previous_definition)
  734. {
  735. $defined_tables = $changes = array();
  736. if(isset($this->database_definition['TABLES']) && is_array($this->database_definition['TABLES'])) {
  737. foreach($this->database_definition['TABLES'] as $table_name => $table) {
  738. $was_table_name = $table['was'];
  739. if(isset($previous_definition['TABLES'][$table_name])
  740. && isset($previous_definition['TABLES'][$table_name]['was'])
  741. && !strcmp($previous_definition['TABLES'][$table_name]['was'], $was_table_name)
  742. ) {
  743. $was_table_name = $table_name;
  744. }
  745. if(isset($previous_definition['TABLES'][$was_table_name])) {
  746. if(strcmp($was_table_name, $table_name)) {
  747. $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, array('name' => $table_name));
  748. $this->database->debug("Renamed table '$was_table_name' to '$table_name'");
  749. }
  750. if(isset($defined_tables[$was_table_name])) {
  751. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  752. 'the table "'.$was_table_name.'" was specified as base of more than of table of the database',
  753. 'MDB_Error', TRUE));
  754. }
  755. $defined_tables[$was_table_name] = 1;
  756. $previous_fields = $previous_definition['TABLES'][$was_table_name]['FIELDS'];
  757. $defined_fields = array();
  758. if(isset($table['FIELDS']) && is_array($table['FIELDS'])) {
  759. foreach($table['FIELDS'] as $field_name => $field) {
  760. $was_field_name = $field['was'];
  761. if(isset($previous_fields[$field_name])
  762. && isset($previous_fields[$field_name]['was'])
  763. && !strcmp($previous_fields[$field_name]['was'], $was_field_name)
  764. ) {
  765. $was_field_name = $field_name;
  766. }
  767. if(isset($previous_fields[$was_field_name])) {
  768. if(strcmp($was_field_name, $field_name)) {
  769. $query = $this->database->getFieldDeclaration($field_name, $field);
  770. if(MDB::isError($query)) {
  771. return($query);
  772. }
  773. $this->_addDefinitionChange($changes, 'TABLES', $was_table_name,
  774. array(
  775. 'RenamedFields' => array(
  776. $was_field_name => array(
  777. 'name' => $field_name,
  778. 'Declaration' => $query
  779. )
  780. )
  781. )
  782. );
  783. $this->database->debug("Renamed field '$was_field_name' to '$field_name' in table '$table_name'");
  784. }
  785. if(isset($defined_fields[$was_field_name])) {
  786. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  787. 'the field "'.$was_table_name.'" was specified as base of more than one field of table',
  788. 'MDB_Error', TRUE));
  789. }
  790. $defined_fields[$was_field_name] = 1;
  791. $change = array();
  792. if($field['type'] == $previous_fields[$was_field_name]['type']) {
  793. switch($field['type']) {
  794. case 'integer':
  795. $previous_unsigned = isset($previous_fields[$was_field_name]['unsigned']);
  796. $unsigned = isset($fields[$field_name]['unsigned']);
  797. if(strcmp($previous_unsigned, $unsigned)) {
  798. $change['unsigned'] = $unsigned;
  799. $this->database->debug("Changed field '$field_name' type from '".($previous_unsigned ? 'unsigned ' : '').$previous_fields[$was_field_name]['type']."' to '".($unsigned ? 'unsigned ' : '').$field['type']."' in table '$table_name'");
  800. }
  801. break;
  802. case 'text':
  803. case 'clob':
  804. case 'blob':
  805. $previous_length = (isset($previous_fields[$was_field_name]['length']) ? $previous_fields[$was_field_name]['length'] : 0);
  806. $length = (isset($field['length']) ? $field['length'] : 0);
  807. if(strcmp($previous_length, $length)) {
  808. $change['length'] = $length;
  809. $this->database->debug("Changed field '$field_name' length from '".$previous_fields[$was_field_name]['type'].($previous_length == 0 ? ' no length' : "($previous_length)")."' to '".$field['type'].($length == 0 ? ' no length' : "($length)")."' in table '$table_name'");
  810. }
  811. break;
  812. case 'date':
  813. case 'timestamp':
  814. case 'time':
  815. case 'boolean':
  816. case 'float':
  817. case 'decimal':
  818. break;
  819. default:
  820. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  821. 'type "'.$field['type'].'" is not yet supported',
  822. 'MDB_Error', TRUE));
  823. }
  824. $previous_notnull = isset($previous_fields[$was_field_name]['notnull']);
  825. $notnull = isset($field['notnull']);
  826. if($previous_notnull != $notnull) {
  827. $change['ChangedNotNull'] = 1;
  828. if($notnull) {
  829. $change['notnull'] = isset($field['notnull']);
  830. }
  831. $this->database->debug("Changed field '$field_name' notnull from $previous_notnull to $notnull in table '$table_name'");
  832. }
  833. $previous_default = isset($previous_fields[$was_field_name]['default']);
  834. $default = isset($field['default']);
  835. if(strcmp($previous_default, $default)) {
  836. $change['ChangedDefault'] = 1;
  837. if($default) {
  838. $change['default'] = $field['default'];
  839. }
  840. $this->database->debug("Changed field '$field_name' default from ".($previous_default ? "'".$previous_fields[$was_field_name]['default']."'" : 'NULL').' TO '.($default ? "'".$fields[$field_name]['default']."'" : 'NULL')." IN TABLE '$table_name'");
  841. } else {
  842. if($default
  843. && strcmp($previous_fields[$was_field_name]['default'], $field['default'])
  844. ) {
  845. $change['ChangedDefault'] = 1;
  846. $change['default'] = $field['default'];
  847. $this->database->debug("Changed field '$field_name' default from '".$previous_fields[$was_field_name]['default']."' to '".$fields[$field_name]['default']."' in table '$table_name'");
  848. }
  849. }
  850. } else {
  851. $change['type'] = $field['type'];
  852. $this->database->debug("Changed field '$field_name' type from '".$previous_fields[$was_field_name]['type']."' to '".$fields[$field_name]['type']."' in table '$table_name'");
  853. }
  854. if(count($change)) {
  855. $query = $this->database->getFieldDeclaration($field_name, $field);
  856. if(MDB::isError($query)) {
  857. return($query);
  858. }
  859. $change['Declaration'] = $query;
  860. $change['Definition'] = $field;
  861. $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, array('ChangedFields' => array($field_name => $change)));
  862. }
  863. } else {
  864. if(strcmp($field_name, $was_field_name)) {
  865. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  866. 'it was specified a previous field name ("'
  867. .$was_field_name.'") for field "'.$field_name.'" of table "'
  868. .$table_name.'" that does not exist',
  869. 'MDB_Error', TRUE));
  870. }
  871. $query = $this->database->getFieldDeclaration($field_name, $field);
  872. if(MDB::isError($query)) {
  873. return($query);
  874. }
  875. $change['Declaration'] = $query;
  876. $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('AddedFields' => array($field_name => $change)));
  877. $this->database->debug("Added field '$field_name' to table '$table_name'");
  878. }
  879. }
  880. }
  881. if(isset($previous_fields) && is_array($previous_fields)) {
  882. foreach ($previous_fields as $field_previous_name => $field_previous) {
  883. if(!isset($defined_fields[$field_previous_name])) {
  884. $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('RemovedFields' => array($field_previous_name => array())));
  885. $this->database->debug("Removed field '$field_name' from table '$table_name'");
  886. }
  887. }
  888. }
  889. $indexes = array();
  890. if(isset($this->database_definition['TABLES'][$table_name]['INDEXES'])
  891. && is_array($this->database_definition['TABLES'][$table_name]['INDEXES'])
  892. ) {
  893. $indexes = $this->database_definition['TABLES'][$table_name]['INDEXES'];
  894. }
  895. $previous_indexes = array();
  896. if(isset($previous_definition['TABLES'][$was_table_name]['INDEXES'])
  897. && is_array($previous_definition['TABLES'][$was_table_name]['INDEXES'])
  898. ) {
  899. $previous_indexes = $previous_definition['TABLES'][$was_table_name]['INDEXES'];
  900. }
  901. $defined_indexes = array();
  902. foreach($indexes as $index_name => $index) {
  903. $was_index_name = $index['was'];
  904. if(isset($previous_indexes[$index_name])
  905. && isset($previous_indexes[$index_name]['was'])
  906. && !strcmp($previous_indexes[$index_name]['was'], $was_index_name)
  907. ) {
  908. $was_index_name = $index_name;
  909. }
  910. if(isset($previous_indexes[$was_index_name])) {
  911. $change = array();
  912. if(strcmp($was_index_name, $index_name)) {
  913. $change['name'] = $was_index_name;
  914. $this->database->debug("Changed index '$was_index_name' name to '$index_name' in table '$table_name'");
  915. }
  916. if(isset($defined_indexes[$was_index_name])) {
  917. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  918. 'the index "'.$was_index_name.'" was specified as base of'
  919. .' more than one index of table "'.$table_name.'"',
  920. 'MDB_Error', TRUE));
  921. }
  922. $defined_indexes[$was_index_name] = 1;
  923. $previous_unique = isset($previous_indexes[$was_index_name]['unique']);
  924. $unique = isset($index['unique']);
  925. if($previous_unique != $unique) {
  926. $change['ChangedUnique'] = 1;
  927. if($unique) {
  928. $change['unique'] = $unique;
  929. }
  930. $this->database->debug("Changed index '$index_name' unique from $previous_unique to $unique in table '$table_name'");
  931. }
  932. $defined_fields = array();
  933. $previous_fields = $previous_indexes[$was_index_name]['FIELDS'];
  934. if(isset($index['FIELDS']) && is_array($index['FIELDS'])) {
  935. foreach($index['FIELDS'] as $field_name => $field) {
  936. if(isset($previous_fields[$field_name])) {
  937. $defined_fields[$field_name] = 1;
  938. $sorting = (isset($field['sorting']) ? $field['sorting'] : '');
  939. $previous_sorting = (isset($previous_fields[$field_name]['sorting']) ? $previous_fields[$field_name]['sorting'] : '');
  940. if(strcmp($sorting, $previous_sorting)) {
  941. $this->database->debug("Changed index field '$field_name' sorting default from '$previous_sorting' to '$sorting' in table '$table_name'");
  942. $change['ChangedFields'] = 1;
  943. }
  944. } else {
  945. $change['ChangedFields'] = 1;
  946. $this->database->debug("Added field '$field_name' to index '$index_name' of table '$table_name'");
  947. }
  948. }
  949. }
  950. if(isset($previous_fields) && is_array($previous_fields)) {
  951. foreach($previous_fields as $field_name => $field) {
  952. if(!isset($defined_fields[$field_name])) {
  953. $change['ChangedFields'] = 1;
  954. $this->database->debug("Removed field '$field_name' from index '$index_name' of table '$table_name'");
  955. }
  956. }
  957. }
  958. if(count($change)) {
  959. $this->_addDefinitionChange($changes, 'INDEXES', $table_name,array('ChangedIndexes' => array($index_name => $change)));
  960. }
  961. } else {
  962. if(strcmp($index_name, $was_index_name)) {
  963. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  964. 'it was specified a previous index name ("'.$was_index_name
  965. .') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist',
  966. 'MDB_Error', TRUE));
  967. }
  968. $this->_addDefinitionChange($changes, 'INDEXES', $table_name,array('AddedIndexes' => array($index_name => $indexes[$index_name])));
  969. $this->database->debug("Added index '$index_name' to table '$table_name'");
  970. }
  971. }
  972. foreach($previous_indexes as $index_previous_name => $index_previous) {
  973. if(!isset($defined_indexes[$index_previous_name])) {
  974. $this->_addDefinitionChange($changes, 'INDEXES', $table_name, array('RemovedIndexes' => array($index_previous_name => $was_table_name)));
  975. $this->database->debug("Removed index '$index_name' from table '$table_name'");
  976. }
  977. }
  978. } else {
  979. if(strcmp($table_name, $was_table_name)) {
  980. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  981. 'it was specified a previous table name ("'
  982. .$was_table_name.'") for table "'.$table_name.'" that does not exist',
  983. 'MDB_Error', TRUE));
  984. }
  985. $this->_addDefinitionChange($changes, 'TABLES', $table_name,array('Add' => 1));
  986. $this->database->debug("Added table '$table_name'");
  987. }
  988. }
  989. if(isset($previous_definition['TABLES']) && is_array($previous_definition['TABLES'])) {
  990. foreach ($previous_definition['TABLES'] as $table_name => $table) {
  991. if(!isset($defined_tables[$table_name])) {
  992. $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('Remove' => 1));
  993. $this->database->debug("Removed table '$table_name'");
  994. }
  995. }
  996. }
  997. if(isset($this->database_definition['SEQUENCES']) && is_array($this->database_definition['SEQUENCES'])) {
  998. foreach ($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) {
  999. $was_sequence_name = $sequence['was'];
  1000. if(isset($previous_definition['SEQUENCES'][$sequence_name])
  1001. && isset($previous_definition['SEQUENCES'][$sequence_name]['was'])
  1002. && !strcmp($previous_definition['SEQUENCES'][$sequence_name]['was'], $was_sequence_name)
  1003. ) {
  1004. $was_sequence_name = $sequence_name;
  1005. }
  1006. if(isset($previous_definition['SEQUENCES'][$was_sequence_name])) {
  1007. if(strcmp($was_sequence_name, $sequence_name)) {
  1008. $this->_addDefinitionChange($changes, 'SEQUENCES', $was_sequence_name,array('name' => $sequence_name));
  1009. $this->database->debug("Renamed sequence '$was_sequence_name' to '$sequence_name'");
  1010. }
  1011. if(isset($defined_sequences[$was_sequence_name])) {
  1012. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  1013. 'the sequence "'.$was_sequence_name.'" was specified as base'
  1014. .' of more than of sequence of the database',
  1015. 'MDB_Error', TRUE));
  1016. }
  1017. $defined_sequences[$was_sequence_name] = 1;
  1018. $change = array();
  1019. if(strcmp($sequence['start'], $previous_definition['SEQUENCES'][$was_sequence_name]['start'])) {
  1020. $change['start'] = $this->database_definition['SEQUENCES'][$sequence_name]['start'];
  1021. $this->database->debug("Changed sequence '$sequence_name' start from '".$previous_definition['SEQUENCES'][$was_sequence_name]['start']."' to '".$this->database_definition['SEQUENCES'][$sequence_name]['start']."'");
  1022. }
  1023. if(strcmp($sequence['on']['table'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['table'])
  1024. || strcmp($sequence['on']['field'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['field'])
  1025. ) {
  1026. $change['on'] = $sequence['on'];
  1027. $this->database->debug("Changed sequence '$sequence_name' on table field from '".$previous_definition['SEQUENCES'][$was_sequence_name]['on']['table'].'.'.$previous_definition['SEQUENCES'][$was_sequence_name]['on']['field']."' to '".$this->database_definition['SEQUENCES'][$sequence_name]['on']['table'].'.'.$this->database_definition['SEQUENCES'][$sequence_name]['on']['field']."'");
  1028. }
  1029. if(count($change)) {
  1030. $this->_addDefinitionChange($changes, 'SEQUENCES', $was_sequence_name,array('Change' => array($sequence_name => array($change))));
  1031. }
  1032. } else {
  1033. if(strcmp($sequence_name, $was_sequence_name)) {
  1034. return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  1035. 'it was specified a previous sequence name ("'.$was_sequence_name
  1036. .'") for sequence "'.$sequence_name.'" that does not exist',
  1037. 'MDB_Error', TRUE));
  1038. }
  1039. $this->_addDefinitionChange($changes, 'SEQUENCES', $sequence_name, array('Add' => 1));
  1040. $this->database->debug("Added sequence '$sequence_name'");
  1041. }
  1042. }
  1043. }
  1044. if(isset($previous_definition['SEQUENCES']) && is_array($previous_definition['SEQUENCES'])) {
  1045. foreach ($previous_definition['SEQUENCES'] as $sequence_name => $sequence) {
  1046. if(!isset($defined_sequences[$sequence_name])) {
  1047. $this->_addDefinitionChange($changes, 'SEQUENCES', $sequence_name, array('Remove' => 1));
  1048. $this->database->debug("Removed sequence '$sequence_name'");
  1049. }
  1050. }
  1051. }
  1052. }
  1053. return($changes);
  1054. }
  1055. // }}}
  1056. // {{{ _alterDatabase()
  1057. /**
  1058. * Execute the necessary actions to implement the requested changes
  1059. * in a database structure.
  1060. *
  1061. * @param array $previous_definition an associative array that contains
  1062. * the definition of the database structure before applying the requested
  1063. * changes. The definition of this array may be built separately, but
  1064. * usually it is built by the Parse method the Metabase parser class.
  1065. * @param array $changes an associative array that contains the definition of
  1066. * the changes that are meant to be applied to the database structure.
  1067. * @return mixed MDB_OK on success, or a MDB error object
  1068. * @access private
  1069. */
  1070. function _alterDatabase($previous_definition, $changes)
  1071. {
  1072. $result = '';
  1073. if(isset($changes['TABLES']) && is_array($changes['TABLES'])) {
  1074. foreach($changes['TABLES'] as $table_name => $table) {
  1075. if(isset($table['Add']) || isset($table['Remove'])) {
  1076. continue;
  1077. }
  1078. $result = $this->database->alterTable($table_name, $table, 1);
  1079. if(MDB::isError($result)) {
  1080. return($result);
  1081. }
  1082. }
  1083. }
  1084. if(isset($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) {
  1085. if(!$this->database->support('Sequences')) {
  1086. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1087. 'sequences are not supported'));
  1088. }
  1089. foreach($changes['SEQUENCES'] as $sequence) {
  1090. if(isset($sequence['Add'])
  1091. || isset($sequence['Remove'])
  1092. || isset($sequence['Change'])
  1093. ) {
  1094. continue;
  1095. }
  1096. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1097. 'some sequences changes are not yet supported'));
  1098. }
  1099. }
  1100. if(isset($changes['INDEXES']) && is_array($changes['INDEXES'])) {
  1101. if(!$this->database->support('Indexes')) {
  1102. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1103. 'indexes are not supported'));
  1104. }
  1105. foreach($changes['INDEXES'] as $index) {
  1106. $table_changes = count($index);
  1107. if(isset($index['AddedIndexes'])) {
  1108. $table_changes--;
  1109. }
  1110. if(isset($index['RemovedIndexes'])) {
  1111. $table_changes--;
  1112. }
  1113. if(isset($index['ChangedIndexes'])) {
  1114. $table_changes--;
  1115. }
  1116. if($table_changes) {
  1117. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1118. 'index alteration not yet supported'));
  1119. }
  1120. }
  1121. }
  1122. $previous_database_name = $this->database->setDatabase($this->database_definition['name']);
  1123. if(($support_transactions = $this->database->support('Transactions'))
  1124. && MDB::isError($result = $this->database->autoCommit(FALSE))
  1125. ) {
  1126. return($result);
  1127. }
  1128. $error = '';
  1129. $alterations = 0;
  1130. if(isset($changes['INDEXES']) && is_array($changes['INDEXES'])) {
  1131. foreach($changes['INDEXES'] as $index_name => $index) {
  1132. if(isset($index['RemovedIndexes']) && is_array($index['RemovedIndexes'])) {
  1133. foreach($index['RemovedIndexes'] as $index_remove_name => $index_remove) {
  1134. $result = $this->database->dropIndex($index_name,$index_remove_name);
  1135. if(MDB::isError($result)) {
  1136. break;
  1137. }
  1138. $alterations++;
  1139. }
  1140. }
  1141. if(!MDB::isError($result)
  1142. && is_array($index['ChangedIndexes'])
  1143. ) {
  1144. foreach($index['ChangedIndexes'] as $index_changed_name => $index_changed) {
  1145. $was_name = (isset($indexes[$name]['name']) ? $indexes[$index_changed_name]['name'] : $index_changed_name);
  1146. $result = $this->database->dropIndex($index_name, $was_name);
  1147. if(MDB::isError($result)) {
  1148. break;
  1149. }
  1150. $alterations++;
  1151. }
  1152. }
  1153. if(MDB::isError($result)) {
  1154. break;
  1155. }
  1156. }
  1157. }
  1158. if(!MDB::isError($result) && isset($changes['TABLES'])
  1159. && is_array($changes['TABLES'])
  1160. ) {
  1161. foreach($changes['TABLES'] as $table_name => $table) {
  1162. if(isset($table['Remove'])) {
  1163. $result = $this->_dropTable($table_name);
  1164. if(!MDB::isError($result)) {
  1165. $alterations++;
  1166. }
  1167. } else {
  1168. if(!isset($table['Add'])) {
  1169. $result = $this->database->alterTable($table_name, $changes['TABLES'][$table_name], 0);
  1170. if(!MDB::isError($result)) {
  1171. $alterations++;
  1172. }
  1173. }
  1174. }
  1175. if(MDB::isError($result)) {
  1176. break;
  1177. }
  1178. }
  1179. foreach($changes['TABLES'] as $table_name => $table) {
  1180. if(isset($table['Add'])) {
  1181. $result = $this->_createTable($table_name, $this->database_definition['TABLES'][$table_name]);
  1182. if(!MDB::isError($result)) {
  1183. $alterations++;
  1184. }
  1185. }
  1186. if(MDB::isError($result)) {
  1187. break;
  1188. }
  1189. }
  1190. }
  1191. if(!MDB::isError($result) && isset($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) {
  1192. foreach($changes['SEQUENCES'] as $sequence_name => $sequence) {
  1193. if(isset($sequence['Add'])) {
  1194. $created_on_table = 0;
  1195. if(isset($this->database_definition['SEQUENCES'][$sequence_name]['on'])) {
  1196. $table = $this->database_definition['SEQUENCES'][$sequence_name]['on']['table'];
  1197. if(isset($changes['TABLES'])
  1198. && isset($changes['TABLES'][$table_name])
  1199. && isset($changes['TABLES'][$table_name]['Add'])
  1200. ) {
  1201. $created_on_table = 1;
  1202. }
  1203. }
  1204. $result = $this->_createSequence($sequence_name,
  1205. $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table);
  1206. if(!MDB::isError($result)) {
  1207. $alterations++;
  1208. }
  1209. } else {
  1210. if(isset($sequence['Remove'])) {
  1211. if(!strcmp($error = $this->_dropSequence($sequence_name), '')) {
  1212. $alterations++;
  1213. }
  1214. } else {
  1215. if(isset($sequence['Change'])) {
  1216. $created_on_table = 0;
  1217. if(isset($this->database_definition['SEQUENCES'][$sequence_name]['on'])) {
  1218. $table = $this->database_definition['SEQUENCES'][$sequence_name]['on']['table'];
  1219. if(isset($changes['TABLES'])
  1220. && isset($changes['TABLES'][$table_name])
  1221. && isset($changes['TABLES'][$table_name]['Add'])
  1222. ) {
  1223. $created_on_table = 1;
  1224. }
  1225. }
  1226. if(!MDB::isError($result = $this->_dropSequence(
  1227. $this->database_definition['SEQUENCES'][$sequence_name]['was']), '')
  1228. && !MDB::isError($result = $this->_createSequence(
  1229. $sequence_name, $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table), '')
  1230. ) {
  1231. $alterations++;
  1232. }
  1233. } else {
  1234. return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1235. 'changing sequences is not yet supported'));
  1236. }
  1237. }
  1238. }
  1239. if(MDB::isError($result)) {
  1240. break;
  1241. }
  1242. }
  1243. }
  1244. if(!MDB::isError($result) && isset($changes['INDEXES']) && is_array($changes['INDEXES'])) {
  1245. foreach($changes['INDEXES'] as $table_name => $indexes) {
  1246. if(isset($indexes['ChangedIndexes'])) {
  1247. $changedindexes = $indexes['ChangedIndexes'];
  1248. foreach($changedindexes as $index_name => $index) {
  1249. $result = $this->database->createIndex($table_name, $index_name,
  1250. $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name]);
  1251. if(MDB::isError($result)) {
  1252. break;
  1253. }
  1254. $alterations++;
  1255. }
  1256. }
  1257. if(!MDB::isError($result)
  1258. && isset($indexes['AddedIndexes'])
  1259. ) {
  1260. $addedindexes = $indexes['AddedIndexes'];
  1261. foreach($addedindexes as $index_name => $index) {
  1262. $result = $this->database->createIndex($table_name, $index_name,
  1263. $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name]);
  1264. if(MDB::isError($result)) {
  1265. break;
  1266. }
  1267. $alterations++;
  1268. }
  1269. }
  1270. if(MDB::isError($result)) {
  1271. break;
  1272. }
  1273. }
  1274. }
  1275. if($alterations && MDB::isError($result)) {
  1276. if($support_transactions) {
  1277. $res = $this->database->rollback();
  1278. if(MDB::isError($res))
  1279. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1280. 'Could not rollback the partially created database alterations ('
  1281. .$result->getMessage().' ('.$result->getUserinfo(),'))',
  1282. 'MDB_Error', TRUE);
  1283. } else {
  1284. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1285. 'the requested database alterations were only partially implemented ('
  1286. .$result->getMessage().' ('.$result->getUserinfo(),'))',
  1287. 'MDB_Error', TRUE);
  1288. }
  1289. }
  1290. if($support_transactions) {
  1291. $result = $this->database->autoCommit(TRUE);
  1292. if(MDB::isError($result)) {
  1293. $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1294. 'Could not end transaction after successfully implemented the requested database alterations ('
  1295. .$result->getMessage().' ('.$result->getUserinfo(),'))',
  1296. 'MDB_Error', TRUE);
  1297. }
  1298. }
  1299. $this->database->setDatabase($previous_database_name);
  1300. return($result);
  1301. }
  1302. // }}}
  1303. // {{{ _escapeSpecialCharacters()
  1304. /**
  1305. * add escapecharacters to all special characters in a string
  1306. *
  1307. * @param string $string string that should be escaped
  1308. * @return string escaped string
  1309. * @access private
  1310. */
  1311. function _escapeSpecialCharacters($string)
  1312. {
  1313. if(gettype($string) != 'string') {
  1314. $string = strval($string);
  1315. }
  1316. for($escaped = '', $character = 0;
  1317. $character < strlen($string);
  1318. $character++)
  1319. {
  1320. switch($string[$character]) {
  1321. case '\"':
  1322. case '>':
  1323. case '<':
  1324. case '&':
  1325. $code = ord($string[$character]);
  1326. break;
  1327. default:
  1328. $code = ord($string[$character]);
  1329. if($code < 32 || $code>127) {
  1330. break;
  1331. }
  1332. $escaped .= $string[$character];
  1333. continue 2;
  1334. }
  1335. $escaped .= "&#$code;";
  1336. }
  1337. return($escaped);
  1338. }
  1339. // }}}
  1340. // {{{ _dumpSequence()
  1341. /**
  1342. * dump the structure of a sequence
  1343. *
  1344. * @param string $sequence_name
  1345. * @param string $eol
  1346. * @return mixed string with xml seqeunce definition on success, or a MDB error object
  1347. * @access private
  1348. */
  1349. function _dumpSequence($sequence_name, $eol, $dump = MDB_MANAGER_DUMP_ALL)
  1350. {
  1351. $sequence_definition = $this->database_definition['SEQUENCES'][$sequence_name];
  1352. $buffer = "$eol <sequence>$eol <name>$sequence_name</name>$eol";
  1353. if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_CONTENT) {
  1354. if(isset($sequence_definition['start'])) {
  1355. $start = $sequence_definition['start'];
  1356. $buffer .= " <start>$start</start>$eol";
  1357. }
  1358. }
  1359. if(isset($sequence_definition['on'])) {
  1360. $buffer .= " <on>$eol <table>".$sequence_definition['on']['table']."</table>$eol <field>".$sequence_definition['on']['field']."</field>$eol </on>$eol";
  1361. }
  1362. $buffer .= " </sequence>$eol";
  1363. return($buffer);
  1364. }
  1365. // }}}
  1366. // {{{ parseDatabaseDefinitionFile()
  1367. /**
  1368. * Parse a database definition file by creating a Metabase schema format
  1369. * parser object and passing the file contents as parser input data stream.
  1370. *
  1371. * @param string $input_file the path of the database schema file.
  1372. * @param array $variables an associative array that the defines the text
  1373. * string values that are meant to be used to replace the variables that are
  1374. * used in the schema description.
  1375. * @param bool $fail_on_invalid_names (optional) make function fail on invalid
  1376. * names
  1377. * @return mixed MDB_OK on success, or a MDB error object
  1378. * @access public
  1379. */
  1380. function parseDatabaseDefinitionFile($input_file, $variables, $fail_on_invalid_names = 1)
  1381. {
  1382. $parser =& new MDB_Parser($variables, $fail_on_invalid_names);
  1383. $result = $parser->setInputFile($input_file);
  1384. if(MDB::isError($result)) {
  1385. return($result);
  1386. };
  1387. $result = $parser->parse();
  1388. if(MDB::isError($result)) {
  1389. return($result);
  1390. };
  1391. if(MDB::isError($parser->error)) {
  1392. return($parser->error);
  1393. }
  1394. return($parser->database_definition);
  1395. }
  1396. // }}}
  1397. // {{{ _debugDatabaseChanges()
  1398. /**
  1399. * Dump the changes between two database definitions.
  1400. *
  1401. * @param array $changes an associative array that specifies the list
  1402. * of database definitions changes as returned by the _compareDefinitions
  1403. * manager class function.
  1404. * @return mixed MDB_OK on success, or a MDB error object
  1405. * @access private
  1406. */
  1407. function _debugDatabaseChanges($changes)
  1408. {
  1409. if(isset($changes['TABLES'])) {
  1410. foreach($changes['TABLES'] as $table_name => $table)
  1411. {
  1412. $this->database->debug("$table_name:");
  1413. if(isset($table['Add'])) {
  1414. $this->database->debug("\tAdded table '$table_name'");
  1415. } elseif(isset($table['Remove'])) {
  1416. $this->database->debug("\tRemoved table '$table_name'");
  1417. } else {
  1418. if(isset($table['name'])) {
  1419. $this->database->debug("\tRenamed table '$table_name' to '".$table['name']."'");
  1420. }
  1421. if(isset($table['AddedFields'])) {
  1422. foreach($table['AddedFields'] as $field_name => $field) {
  1423. $this->database->debug("\tAdded field '".$field_name."'");
  1424. }
  1425. }
  1426. if(isset($table['RemovedFields'])) {
  1427. foreach($table['RemovedFields'] as $field_name => $field) {
  1428. $this->database->debug("\tRemoved field '".$field_name."'");
  1429. }
  1430. }
  1431. if(isset($table['RenamedFields'])) {
  1432. foreach($table['RenamedFields'] as $field_name => $field) {
  1433. $this->database->debug("\tRenamed field '".$field_name."' to '".$field['name']."'");
  1434. }
  1435. }
  1436. if(isset($table['ChangedFields'])) {
  1437. foreach($table['ChangedFields'] as $field_name => $field) {
  1438. if(isset($field['type'])) {
  1439. $this->database->debug(
  1440. "\tChanged field '$field_name' type to '".$field['type']."'");
  1441. }
  1442. if(isset($field['unsigned'])) {
  1443. $this->database->debug(
  1444. "\tChanged field '$field_name' type to '".
  1445. ($field['unsigned'] ? '' : 'not ')."unsigned'");
  1446. }
  1447. if(isset($field['length'])) {
  1448. $this->database->debug(
  1449. "\tChanged field '$field_name' length to '".
  1450. ($field['length'] == 0 ? 'no length' : $field['length'])."'");
  1451. }
  1452. if(isset($field['ChangedDefault'])) {
  1453. $this->database->debug(
  1454. "\tChanged field '$field_name' default to ".
  1455. (isset($field['default']) ? "'".$field['default']."'" : 'NULL'));
  1456. }
  1457. if(isset($field['ChangedNotNull'])) {
  1458. $this->database->debug(
  1459. "\tChanged field '$field_name' notnull to ".(isset($field['notnull']) ? "'1'" : '0'));
  1460. }
  1461. }
  1462. }
  1463. }
  1464. }
  1465. }
  1466. if(isset($changes['SEQUENCES'])) {
  1467. foreach($changes['SEQUENCES'] as $sequence_name => $sequence)
  1468. {
  1469. $this->database->debug("$sequence_name:");
  1470. if(isset($sequence['Add'])) {
  1471. $this->database->debug("\tAdded sequence '$sequence_name'");
  1472. } elseif(isset($sequence['Remove'])) {
  1473. $this->database->debug("\tRemoved sequence '$sequence_name'");
  1474. } else {
  1475. if(isset($sequence['name'])) {
  1476. $this->database->debug("\tRenamed sequence '$sequence_name' to '".$sequence['name']."'");
  1477. }
  1478. if(isset($sequence['Change'])) {
  1479. foreach($sequence['Change'] as $sequence_name => $sequence) {
  1480. if(isset($sequence['start'])) {
  1481. $this->database->debug(
  1482. "\tChanged sequence '$sequence_name' start to '".$sequence['start']."'");
  1483. }
  1484. }
  1485. }
  1486. }
  1487. }
  1488. }
  1489. if(isset($changes['INDEXES'])) {
  1490. foreach($changes['INDEXES'] as $table_name => $table)
  1491. {
  1492. $this->database->debug("$table_name:");
  1493. if(isset($table['AddedIndexes'])) {
  1494. foreach($table['AddedIndexes'] as $index_name => $index) {
  1495. $this->database->debug("\tAdded index '".$index_name."' of table '$table_name'");
  1496. }
  1497. }
  1498. if(isset($table['RemovedIndexes'])) {
  1499. foreach($table['RemovedIndexes'] as $index_name => $index) {
  1500. $this->database->debug("\tRemoved index '".$index_name."' of table '$table_name'");
  1501. }
  1502. }
  1503. if(isset($table['ChangedIndexes'])) {
  1504. foreach($table['ChangedIndexes'] as $index_name => $index) {
  1505. if(isset($index['name'])) {
  1506. $this->database->debug(
  1507. "\tRenamed index '".$index_name."' to '".$index['name']."' on table '$table_name'");
  1508. }
  1509. if(isset($index['ChangedUnique'])) {
  1510. $this->database->debug(
  1511. "\tChanged index '".$index_name."' unique to '".
  1512. isset($index['unique'])."' on table '$table_name'");
  1513. }
  1514. if(isset($index['ChangedFields'])) {
  1515. $this->database->debug("\tChanged index '".$index_name."' on table '$table_name'");
  1516. }
  1517. }
  1518. }
  1519. }
  1520. }
  1521. return(MDB_OK);
  1522. }
  1523. // }}}
  1524. // {{{ _dumpDatabaseContents()
  1525. /**
  1526. * Parse a database schema definition file and dump the respective structure
  1527. * and contents.
  1528. *
  1529. * @param string $schema_file path of the database schema file.
  1530. * @param mixed $setup_arguments an associative array that takes pairs of tag names and values
  1531. * that define the setup arguments that are passed to the
  1532. * MDB_Manager::connect function.
  1533. * @param array $dump_arguments an associative array that takes pairs of tag names and values
  1534. * that define dump options as defined for the MDB_Manager::DumpDatabase
  1535. * function.
  1536. * @param array $variables an associative array that the defines the text string values
  1537. * that are meant to be used to replace the variables that are used in the
  1538. * schema description as defined for the
  1539. * MDB_Manager::parseDatabaseDefinitionFile function.
  1540. * @return mixed MDB_OK on success, or a MDB error object
  1541. * @access private
  1542. */
  1543. function _dumpDatabaseContents($schema_file, $setup_arguments, $dump_arguments, $variables)
  1544. {
  1545. $database_definition = $this->parseDatabaseDefinitionFile($schema_file,
  1546. $variables, $this->options['fail_on_invalid_names']);
  1547. if(MDB::isError($database_definition)) {
  1548. return($database_definition);
  1549. }
  1550. $this->database_definition = $database_definition;
  1551. $result = $this->connect($setup_arguments);
  1552. if(MDB::isError($result)) {
  1553. return($result);
  1554. }
  1555. return($this->dumpDatabase($dump_arguments));
  1556. }
  1557. // }}}
  1558. // {{{ getDefinitionFromDatabase()
  1559. /**
  1560. * Attempt to reverse engineer a schema structure from an existing MDB
  1561. * This method can be used if no xml schema file exists yet.
  1562. * The resulting xml schema file may need some manual adjustments.
  1563. *
  1564. * @return mixed MDB_OK or array with all ambiguities on success, or a MDB error object
  1565. * @access public
  1566. */
  1567. function getDefinitionFromDatabase()
  1568. {
  1569. $database = $this->database->database_name;
  1570. if(strlen($database) == 0) {
  1571. return('it was not specified a valid database name');
  1572. }
  1573. $this->database_definition = array(
  1574. 'name' => $database,
  1575. 'create' => 1,
  1576. 'TABLES' => array()
  1577. );
  1578. $tables = $this->database->listTables();
  1579. if(MDB::isError($tables)) {
  1580. return($tables);
  1581. }
  1582. for($table = 0; $table < count($tables); $table++) {
  1583. $table_name = $tables[$table];
  1584. $fields = $this->database->listTableFields($table_name);
  1585. if(MDB::isError($fields)) {
  1586. return($fields);
  1587. }
  1588. $this->database_definition['TABLES'][$table_name] = array('FIELDS' => array());
  1589. for($field = 0; $field < count($fields); $field++)
  1590. {
  1591. $field_name = $fields[$field];
  1592. $definition = $this->database->getTableFieldDefinition($table_name, $field_name);
  1593. if(MDB::isError($definition)) {
  1594. return($definition);
  1595. }
  1596. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name] = $definition[0][0];
  1597. $field_choices = count($definition[0]);
  1598. if($field_choices > 1) {
  1599. $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
  1600. $field_choice_cnt = 1;
  1601. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'] = array();
  1602. foreach($definition[0] as $field_choice) {
  1603. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'][] = $field_choice;
  1604. $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
  1605. $field_choice_cnt++;
  1606. }
  1607. $this->warnings[] = $warning;
  1608. }
  1609. if(isset($definition[1])) {
  1610. $sequence = $definition[1]['definition'];
  1611. $sequence_name = $definition[1]['name'];
  1612. $this->database->debug('Implicitly defining sequence: '.$sequence_name);
  1613. if(!isset($this->database_definition['SEQUENCES'])) {
  1614. $this->database_definition['SEQUENCES'] = array();
  1615. }
  1616. $this->database_definition['SEQUENCES'][$sequence_name] = $sequence;
  1617. }
  1618. if(isset($definition[2])) {
  1619. $index = $definition[2]['definition'];
  1620. $index_name = $definition[2]['name'];
  1621. $this->database->debug('Implicitly defining index: '.$index_name);
  1622. if(!isset($this->database_definition['TABLES'][$table_name]['INDEXES'])) {
  1623. $this->database_definition['TABLES'][$table_name]['INDEXES'] = array();
  1624. }
  1625. $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name] = $index;
  1626. }
  1627. }
  1628. $indexes = $this->database->listTableIndexes($table_name);
  1629. if(MDB::isError($indexes)) {
  1630. return($indexes);
  1631. }
  1632. if(is_array($indexes) && count($indexes) > 0 && !isset($this->database_definition['TABLES'][$table_name]['INDEXES'])) {
  1633. $this->database_definition['TABLES'][$table_name]['INDEXES'] = array();
  1634. }
  1635. for($index = 0, $index_cnt = count($indexes); $index < $index_cnt; $index++)
  1636. {
  1637. $index_name = $indexes[$index];
  1638. $definition = $this->database->getTableIndexDefinition($table_name, $index_name);
  1639. if(MDB::isError($definition)) {
  1640. return($definition);
  1641. }
  1642. $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name] = $definition;
  1643. }
  1644. // ensure that all fields that have an index on them are set to not null
  1645. if(isset($this->database_definition['TABLES'][$table_name]['INDEXES'])
  1646. && is_array($this->database_definition['TABLES'][$table_name]['INDEXES'])
  1647. && count($this->database_definition['TABLES'][$table_name]['INDEXES']) > 0
  1648. ) {
  1649. foreach($this->database_definition['TABLES'][$table_name]['INDEXES'] as $index_check_null) {
  1650. foreach($index_check_null['FIELDS'] as $field_name_check_null => $field_check_null) {
  1651. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name_check_null]['notnull'] = 1;
  1652. }
  1653. }
  1654. }
  1655. // ensure that all fields that are set to not null also have a default value
  1656. if(is_array($this->database_definition['TABLES'][$table_name]['FIELDS'])
  1657. && count($this->database_definition['TABLES'][$table_name]['FIELDS']) > 0
  1658. ) {
  1659. foreach($this->database_definition['TABLES'][$table_name]['FIELDS'] as $field_set_default_name => $field_set_default) {
  1660. if(isset($field_set_default['notnull']) && $field_set_default['notnull']
  1661. && !isset($field_set_default['default'])
  1662. ) {
  1663. if(isset($this->default_values[$field_set_default['type']])) {
  1664. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = $this->default_values[$field_set_default['type']];
  1665. } else {
  1666. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = 0;
  1667. }
  1668. }
  1669. if(isset($field_set_default['CHOICES']) && is_array($field_set_default['CHOICES'])) {
  1670. foreach($field_set_default['CHOICES'] as $field_choices_set_default_name => $field_choices_set_default) {
  1671. if(isset($field_choices_set_default['notnull'])
  1672. && $field_choices_set_default['notnull']
  1673. && !isset($field_choices_set_default['default'])
  1674. ) {
  1675. if(isset($this->default_values[$field_choices_set_default['type']])) {
  1676. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES']
  1677. [$field_choices_set_default_name]['default'] = $this->default_values[$field_choices_set_default['type']];
  1678. } else {
  1679. $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES']
  1680. [$field_choices_set_default_name]['default'] = 0;
  1681. }
  1682. }
  1683. }
  1684. }
  1685. }
  1686. }
  1687. }
  1688. $sequences = $this->database->listSequences();
  1689. if(MDB::isError($sequences)) {
  1690. return($sequences);
  1691. }
  1692. if(is_array($sequences) && count($sequences) > 0 && !isset($this->database_definition['SEQUENCES'])) {
  1693. $this->database_definition['SEQUENCES'] = array();
  1694. }
  1695. for($sequence = 0; $sequence < count($sequences); $sequence++) {
  1696. $sequence_name = $sequences[$sequence];
  1697. $definition = $this->database->getSequenceDefinition($sequence_name);
  1698. if(MDB::isError($definition)) {
  1699. return($definition);
  1700. }
  1701. $this->database_definition['SEQUENCES'][$sequence_name] = $definition;
  1702. }
  1703. return(MDB_OK);
  1704. }
  1705. // }}}
  1706. // {{{ dumpDatabase()
  1707. /**
  1708. * Dump a previously parsed database structure in the Metabase schema
  1709. * XML based format suitable for the Metabase parser. This function
  1710. * may optionally dump the database definition with initialization
  1711. * commands that specify the data that is currently present in the tables.
  1712. *
  1713. * @param array $arguments an associative array that takes pairs of tag
  1714. * names and values that define dump options.
  1715. * array (
  1716. * 'Definition' => Boolean
  1717. * TRUE : dump currently parsed definition
  1718. * default: dump currently connected database
  1719. * 'Output_Mode' => String
  1720. * 'file' : dump into a file
  1721. * default: dump using a function
  1722. * 'Output' => String
  1723. * depending on the 'Output_Mode'
  1724. * name of the file
  1725. * name of the function
  1726. * 'EndOfLine' => String
  1727. * end of line delimiter that should be used
  1728. * default: "\n"
  1729. * );
  1730. * @param integer $dump constant that determines what data to dump
  1731. * MDB_MANAGER_DUMP_ALL : the entire db
  1732. * MDB_MANAGER_DUMP_STRUCTURE : only the structure of the db
  1733. * MDB_MANAGER_DUMP_CONTENT : only the content of the db
  1734. * @return mixed MDB_OK on success, or a MDB error object
  1735. * @access public
  1736. */
  1737. function dumpDatabase($arguments, $dump = MDB_MANAGER_DUMP_ALL)
  1738. {
  1739. if(isset($arguments['Definition']) && $arguments['Definition']) {
  1740. $dump_definition = TRUE;
  1741. } else {
  1742. if(!$this->database) {
  1743. return($this->raiseError(MDB_ERROR_NODBSELECTED,
  1744. NULL, NULL, 'please connect to a RDBMS first'));
  1745. }
  1746. $error = $this->getDefinitionFromDatabase();
  1747. if(MDB::isError($error)) {
  1748. return($error);
  1749. }
  1750. $dump_definition = FALSE;
  1751. }
  1752. if(isset($arguments['Output'])) {
  1753. if(isset($arguments['Output_Mode']) && $arguments['Output_Mode'] == 'file') {
  1754. $fp = fopen($arguments['Output'], 'w');
  1755. $output = FALSE;
  1756. } elseif(function_exists($arguments['Output'])) {
  1757. $output = $arguments['Output'];
  1758. } else {
  1759. return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1760. 'no valid output function specified'));
  1761. }
  1762. } else {
  1763. return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1764. 'no output method specified'));
  1765. }
  1766. if(isset($arguments['EndOfLine'])) {
  1767. $eol = $arguments['EndOfLine'];
  1768. } else {
  1769. $eol = "\n";
  1770. }
  1771. $sequences = array();
  1772. if(isset($this->database_definition['SEQUENCES'])
  1773. && is_array($this->database_definition['SEQUENCES'])
  1774. ) {
  1775. foreach($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) {
  1776. if(isset($sequence['on'])) {
  1777. $table = $sequence['on']['table'];
  1778. } else {
  1779. $table = '';
  1780. }
  1781. $sequences[$table][] = $sequence_name;
  1782. }
  1783. }
  1784. $previous_database_name = (strcmp($this->database_definition['name'], '') ? $this->database->setDatabase($this->database_definition['name']) : '');
  1785. $buffer = ('<?xml version="1.0" encoding="ISO-8859-1" ?>'.$eol);
  1786. $buffer .= ("<database>$eol$eol <name>".$this->database_definition['name']."</name>$eol <create>".$this->database_definition['create']."</create>$eol");
  1787. if($output) {
  1788. $output($buffer);
  1789. } else {
  1790. fwrite($fp, $buffer);
  1791. }
  1792. $buffer = '';
  1793. if(isset($this->database_definition['TABLES']) && is_array($this->database_definition['TABLES'])) {
  1794. foreach($this->database_definition['TABLES'] as $table_name => $table) {
  1795. $buffer = ("$eol <table>$eol$eol <name>$table_name</name>$eol");
  1796. if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_STRUCTURE) {
  1797. $buffer .= ("$eol <declaration>$eol");
  1798. if(isset($table['FIELDS']) && is_array($table['FIELDS'])) {
  1799. foreach($table['FIELDS'] as $field_name => $field) {
  1800. if(!isset($field['type'])) {
  1801. return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1802. 'it was not specified the type of the field "'.$field_name.'" of the table "'.$table_name));
  1803. }
  1804. $buffer .=("$eol <field>$eol <name>$field_name</name>$eol <type>".$field['type']."</type>$eol");
  1805. if(in_array($field_name, array_keys($this->invalid_names))) {
  1806. $this->warnings[] = "invalid field name: $field_name. You will need to set the class var \$fail_on_invalid_names to FALSE or change the field name.";
  1807. }
  1808. switch($field['type']) {
  1809. case 'integer':
  1810. if(isset($field['unsigned'])) {
  1811. $buffer .=(" <unsigned>1</unsigned>$eol");
  1812. }
  1813. break;
  1814. case 'text':
  1815. case 'clob':
  1816. case 'blob':
  1817. if(isset($field['length'])) {
  1818. $buffer .=(' <length>'.$field['length']."</length>$eol");
  1819. }
  1820. break;
  1821. case 'boolean':
  1822. case 'date':
  1823. case 'timestamp':
  1824. case 'time':
  1825. case 'float':
  1826. case 'decimal':
  1827. break;
  1828. default:
  1829. return('type "'.$field['type'].'" is not yet supported');
  1830. }
  1831. if(isset($field['notnull'])) {
  1832. $buffer .=(" <notnull>1</notnull>$eol");
  1833. }
  1834. if(isset($field['default'])) {
  1835. $buffer .=(' <default>'.$this->_escapeSpecialCharacters($field['default'])."</default>$eol");
  1836. }
  1837. $buffer .=(" </field>$eol");
  1838. }
  1839. }
  1840. if(isset($table['INDEXES']) && is_array($table['INDEXES'])) {
  1841. foreach($table['INDEXES'] as $index_name => $index) {
  1842. $buffer .=("$eol <index>$eol <name>$index_name</name>$eol");
  1843. if(isset($index['unique'])) {
  1844. $buffer .=(" <unique>1</unique>$eol");
  1845. }
  1846. foreach($index['FIELDS'] as $field_name => $field) {
  1847. $buffer .=(" <field>$eol <name>$field_name</name>$eol");
  1848. if(is_array($field) && isset($field['sorting'])) {
  1849. $buffer .=(' <sorting>'.$field['sorting']."</sorting>$eol");
  1850. }
  1851. $buffer .=(" </field>$eol");
  1852. }
  1853. $buffer .=(" </index>$eol");
  1854. }
  1855. }
  1856. $buffer .= ("$eol </declaration>$eol");
  1857. }
  1858. if($output) {
  1859. $output($buffer);
  1860. } else {
  1861. fwrite($fp, $buffer);
  1862. }
  1863. $buffer = '';
  1864. if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_CONTENT) {
  1865. if($dump_definition) {
  1866. if(isset($table['initialization']) && is_array($table['initialization'])) {
  1867. $buffer = ("$eol <initialization>$eol");
  1868. foreach($table['initialization'] as $instruction_name => $instruction) {
  1869. switch($instruction['type']) {
  1870. case 'insert':
  1871. $buffer .= ("$eol <insert>$eol");
  1872. foreach($instruction['FIELDS'] as $field_name => $field) {
  1873. $buffer .= ("$eol <field>$eol <name>$field_name</name>$eol <value>".$this->_escapeSpecialCharacters($field)."</value>$eol </field>$eol");
  1874. }
  1875. $buffer .= ("$eol </insert>$eol");
  1876. break;
  1877. }
  1878. }
  1879. $buffer .= ("$eol </initialization>$eol");
  1880. }
  1881. } else {
  1882. $types = array();
  1883. foreach($table['FIELDS'] as $field) {
  1884. $types[] = $field['type'];
  1885. }
  1886. $query = 'SELECT '.implode(',',array_keys($table['FIELDS']))." FROM $table_name";
  1887. $result = $this->database->queryAll($query, $types, MDB_FETCHMODE_ASSOC);
  1888. if(MDB::isError($result)) {
  1889. return($result);
  1890. }
  1891. $rows = count($result);
  1892. if($rows > 0) {
  1893. $buffer = ("$eol <initialization>$eol");
  1894. if($output) {
  1895. $output($buffer);
  1896. } else {
  1897. fwrite($fp, $buffer);
  1898. }
  1899. for($row = 0; $row < $rows; $row++) {
  1900. $buffer = ("$eol <insert>$eol");
  1901. $values = $result[$row];
  1902. if(!is_array($values)) {
  1903. break;
  1904. } else {
  1905. foreach($values as $field_name => $field) {
  1906. $buffer .= ("$eol <field>$eol <name>$field_name</name>$eol <value>");
  1907. $buffer .= $this->_escapeSpecialCharacters($values[$field_name]);
  1908. $buffer .= ("</value>$eol </field>$eol");
  1909. }
  1910. }
  1911. $buffer .= ("$eol </insert>$eol");
  1912. if($output) {
  1913. $output($buffer);
  1914. } else {
  1915. fwrite($fp, $buffer);
  1916. }
  1917. $buffer = '';
  1918. }
  1919. $buffer = ("$eol </initialization>$eol");
  1920. if($output) {
  1921. $output($buffer);
  1922. } else {
  1923. fwrite($fp, $buffer);
  1924. }
  1925. $buffer = '';
  1926. }
  1927. }
  1928. }
  1929. $buffer .= ("$eol </table>$eol");
  1930. if($output) {
  1931. $output($buffer);
  1932. } else {
  1933. fwrite($fp, $buffer);
  1934. }
  1935. if(isset($sequences[$table_name])) {
  1936. for($sequence = 0, $j = count($sequences[$table_name]);
  1937. $sequence < $j;
  1938. $sequence++)
  1939. {
  1940. $result = $this->_dumpSequence($sequences[$table_name][$sequence], $eol, $dump);
  1941. if(MDB::isError($result)) {
  1942. return($result);
  1943. }
  1944. if($output) {
  1945. $output($result);
  1946. } else {
  1947. fwrite($fp, $result);
  1948. }
  1949. }
  1950. }
  1951. }
  1952. }
  1953. if(isset($sequences[''])) {
  1954. for($sequence = 0;
  1955. $sequence < count($sequences['']);
  1956. $sequence++)
  1957. {
  1958. $result = $this->_dumpSequence($sequences[''][$sequence], $eol, $dump);
  1959. if(MDB::isError($result)) {
  1960. return($result);
  1961. }
  1962. if($output) {
  1963. $output($result);
  1964. } else {
  1965. fwrite($fp, $result);
  1966. }
  1967. }
  1968. }
  1969. $buffer = ("$eol</database>$eol");
  1970. if($output) {
  1971. $output($buffer);
  1972. } else {
  1973. fwrite($fp, $buffer);
  1974. fclose($fp);
  1975. }
  1976. if(strcmp($previous_database_name, '')) {
  1977. $this->database->setDatabase($previous_database_name);
  1978. }
  1979. return(MDB_OK);
  1980. }
  1981. // }}}
  1982. // {{{ updateDatabase()
  1983. /**
  1984. * Compare the correspondent files of two versions of a database schema
  1985. * definition: the previously installed and the one that defines the schema
  1986. * that is meant to update the database.
  1987. * If the specified previous definition file does not exist, this function
  1988. * will create the database from the definition specified in the current
  1989. * schema file.
  1990. * If both files exist, the function assumes that the database was previously
  1991. * installed based on the previous schema file and will update it by just
  1992. * applying the changes.
  1993. * If this function succeeds, the contents of the current schema file are
  1994. * copied to replace the previous schema file contents. Any subsequent schema
  1995. * changes should only be done on the file specified by the $current_schema_file
  1996. * to let this function make a consistent evaluation of the exact changes that
  1997. * need to be applied.
  1998. *
  1999. * @param string $current_schema_file name of the updated database schema
  2000. * definition file.
  2001. * @param string $previous_schema_file name the previously installed database
  2002. * schema definition file.
  2003. * @param array $variables an associative array that is passed to the argument
  2004. * of the same name to the parseDatabaseDefinitionFile function. (there third
  2005. * param)
  2006. * @return mixed MDB_OK on success, or a MDB error object
  2007. * @access public
  2008. */
  2009. function updateDatabase($current_schema_file, $previous_schema_file = FALSE, $variables = array())
  2010. {
  2011. $database_definition = $this->parseDatabaseDefinitionFile($current_schema_file,
  2012. $variables, $this->options['fail_on_invalid_names']);
  2013. if(MDB::isError($database_definition)) {
  2014. return($database_definition);
  2015. }
  2016. $this->database_definition = $database_definition;
  2017. $copy = 0;
  2018. /*
  2019. $this->expectError(MDB_ERROR_UNSUPPORTED);
  2020. $databases = $this->database->listDatabases();
  2021. $this->popExpect();
  2022. if((MDB::isError($databases) || (is_array($databases) && in_array($this->database_definition['name'], $databases)))
  2023. && $previous_schema_file && file_exists($previous_schema_file))
  2024. {
  2025. */
  2026. if($previous_schema_file && file_exists($previous_schema_file)) {
  2027. $previous_definition = $this->parseDatabaseDefinitionFile($previous_schema_file, $variables, 0);
  2028. if(MDB::isError($previous_definition)) {
  2029. return($previous_definition);
  2030. }
  2031. $changes = $this->_compareDefinitions($previous_definition);
  2032. if(MDB::isError($changes)) {
  2033. return($changes);
  2034. }
  2035. if(isset($changes) && is_array($changes)) {
  2036. $result = $this->_alterDatabase($previous_definition, $changes);
  2037. if(MDB::isError($result)) {
  2038. return($result);
  2039. }
  2040. $copy = 1;
  2041. if($this->options['debug']) {
  2042. $result = $this->_debugDatabaseChanges($changes);
  2043. if(MDB::isError($result)) {
  2044. return($result);
  2045. }
  2046. }
  2047. }
  2048. } else {
  2049. $result = $this->_createDatabase();
  2050. if(MDB::isError($result)) {
  2051. return($result);
  2052. }
  2053. $copy = 1;
  2054. }
  2055. if($copy && $previous_schema_file && !copy($current_schema_file, $previous_schema_file)) {
  2056. return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  2057. 'Could not copy the new database definition file to the current file'));
  2058. }
  2059. return(MDB_OK);
  2060. }
  2061. // }}}
  2062. }
  2063. ?>