PageRenderTime 71ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/MDB2/Schema.php

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