PageRenderTime 72ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/php-pear-MDB2-Schema-0.8.5/MDB2_Schema-0.8.5/MDB2/Schema.php

#
PHP | 2767 lines | 1775 code | 283 blank | 709 comment | 494 complexity | bc5c139674e5780f76186b77308f126a MD5 | raw file
  1. <?php
  2. /**
  3. * PHP version 4, 5
  4. *
  5. * Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,
  6. * Stig. S. Bakken, Lukas Smith, Igor Feghali
  7. * All rights reserved.
  8. *
  9. * MDB2_Schema enables users to maintain RDBMS independant schema files
  10. * in XML that can be used to manipulate both data and database schemas
  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, Igor Feghali nor the names of his contributors may be
  26. * used to endorse or promote products derived from this software
  27. * without specific prior 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. * Author: Igor Feghali <ifeghali@php.net>
  44. *
  45. * @category Database
  46. * @package MDB2_Schema
  47. * @author Lukas Smith <smith@pooteeweet.org>
  48. * @author Igor Feghali <ifeghali@php.net>
  49. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  50. * @version CVS: $Id: Schema.php,v 1.132 2009/02/22 21:43:22 ifeghali Exp $
  51. * @link http://pear.php.net/packages/MDB2_Schema
  52. */
  53. require_once 'MDB2.php';
  54. define('MDB2_SCHEMA_DUMP_ALL', 0);
  55. define('MDB2_SCHEMA_DUMP_STRUCTURE', 1);
  56. define('MDB2_SCHEMA_DUMP_CONTENT', 2);
  57. /**
  58. * If you add an error code here, make sure you also add a textual
  59. * version of it in MDB2_Schema::errorMessage().
  60. */
  61. define('MDB2_SCHEMA_ERROR', -1);
  62. define('MDB2_SCHEMA_ERROR_PARSE', -2);
  63. define('MDB2_SCHEMA_ERROR_VALIDATE', -3);
  64. define('MDB2_SCHEMA_ERROR_UNSUPPORTED', -4); // Driver does not support this function
  65. define('MDB2_SCHEMA_ERROR_INVALID', -5); // Invalid attribute value
  66. define('MDB2_SCHEMA_ERROR_WRITER', -6);
  67. /**
  68. * The database manager is a class that provides a set of database
  69. * management services like installing, altering and dumping the data
  70. * structures of databases.
  71. *
  72. * @category Database
  73. * @package MDB2_Schema
  74. * @author Lukas Smith <smith@pooteeweet.org>
  75. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  76. * @link http://pear.php.net/packages/MDB2_Schema
  77. */
  78. class MDB2_Schema extends PEAR
  79. {
  80. // {{{ properties
  81. var $db;
  82. var $warnings = array();
  83. var $options = array(
  84. 'fail_on_invalid_names' => true,
  85. 'dtd_file' => false,
  86. 'valid_types' => array(),
  87. 'force_defaults' => true,
  88. 'parser' => 'MDB2_Schema_Parser',
  89. 'writer' => 'MDB2_Schema_Writer',
  90. 'validate' => 'MDB2_Schema_Validate',
  91. 'drop_missing_tables' => false
  92. );
  93. // }}}
  94. // {{{ apiVersion()
  95. /**
  96. * Return the MDB2 API version
  97. *
  98. * @return string the MDB2 API version number
  99. * @access public
  100. */
  101. function apiVersion()
  102. {
  103. return '0.4.3';
  104. }
  105. // }}}
  106. // {{{ arrayMergeClobber()
  107. /**
  108. * Clobbers two arrays together
  109. *
  110. * @param array $a1 array that should be clobbered
  111. * @param array $a2 array that should be clobbered
  112. *
  113. * @return array|false array on success and false on error
  114. *
  115. * @access public
  116. * @author kc@hireability.com
  117. */
  118. function arrayMergeClobber($a1, $a2)
  119. {
  120. if (!is_array($a1) || !is_array($a2)) {
  121. return false;
  122. }
  123. foreach ($a2 as $key => $val) {
  124. if (is_array($val) && array_key_exists($key, $a1) && is_array($a1[$key])) {
  125. $a1[$key] = MDB2_Schema::arrayMergeClobber($a1[$key], $val);
  126. } else {
  127. $a1[$key] = $val;
  128. }
  129. }
  130. return $a1;
  131. }
  132. // }}}
  133. // {{{ resetWarnings()
  134. /**
  135. * reset the warning array
  136. *
  137. * @access public
  138. * @return void
  139. */
  140. function resetWarnings()
  141. {
  142. $this->warnings = array();
  143. }
  144. // }}}
  145. // {{{ getWarnings()
  146. /**
  147. * Get all warnings in reverse order
  148. *
  149. * This means that the last warning is the first element in the array
  150. *
  151. * @return array with warnings
  152. * @access public
  153. * @see resetWarnings()
  154. */
  155. function getWarnings()
  156. {
  157. return array_reverse($this->warnings);
  158. }
  159. // }}}
  160. // {{{ setOption()
  161. /**
  162. * Sets the option for the db class
  163. *
  164. * @param string $option option name
  165. * @param mixed $value value for the option
  166. *
  167. * @return bool|MDB2_Error MDB2_OK or error object
  168. * @access public
  169. */
  170. function setOption($option, $value)
  171. {
  172. if (isset($this->options[$option])) {
  173. if (is_null($value)) {
  174. return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  175. 'may not set an option to value null');
  176. }
  177. $this->options[$option] = $value;
  178. return MDB2_OK;
  179. }
  180. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
  181. "unknown option $option");
  182. }
  183. // }}}
  184. // {{{ getOption()
  185. /**
  186. * returns the value of an option
  187. *
  188. * @param string $option option name
  189. *
  190. * @return mixed the option value or error object
  191. * @access public
  192. */
  193. function getOption($option)
  194. {
  195. if (isset($this->options[$option])) {
  196. return $this->options[$option];
  197. }
  198. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED,
  199. null, null, "unknown option $option");
  200. }
  201. // }}}
  202. // {{{ factory()
  203. /**
  204. * Create a new MDB2 object for the specified database type
  205. * type
  206. *
  207. * @param string|array|MDB2_Driver_Common &$db 'data source name', see the
  208. * MDB2::parseDSN method for a description of the dsn format.
  209. * Can also be specified as an array of the
  210. * format returned by @see MDB2::parseDSN.
  211. * Finally you can also pass an existing db object to be used.
  212. * @param array $options An associative array of option names and their values.
  213. *
  214. * @return bool|MDB2_Error MDB2_OK or error object
  215. * @access public
  216. * @see MDB2::parseDSN
  217. */
  218. function &factory(&$db, $options = array())
  219. {
  220. $obj =& new MDB2_Schema();
  221. $result = $obj->connect($db, $options);
  222. if (PEAR::isError($result)) {
  223. return $result;
  224. }
  225. return $obj;
  226. }
  227. // }}}
  228. // {{{ connect()
  229. /**
  230. * Create a new MDB2 connection object and connect to the specified
  231. * database
  232. *
  233. * @param string|array|MDB2_Driver_Common &$db 'data source name', see the
  234. * MDB2::parseDSN method for a description of the dsn format.
  235. * Can also be specified as an array of the
  236. * format returned by MDB2::parseDSN.
  237. * Finally you can also pass an existing db object to be used.
  238. * @param array $options An associative array of option names and their values.
  239. *
  240. * @return bool|MDB2_Error MDB2_OK or error object
  241. * @access public
  242. * @see MDB2::parseDSN
  243. */
  244. function connect(&$db, $options = array())
  245. {
  246. $db_options = array();
  247. if (is_array($options)) {
  248. foreach ($options as $option => $value) {
  249. if (array_key_exists($option, $this->options)) {
  250. $result = $this->setOption($option, $value);
  251. if (PEAR::isError($result)) {
  252. return $result;
  253. }
  254. } else {
  255. $db_options[$option] = $value;
  256. }
  257. }
  258. }
  259. $this->disconnect();
  260. if (!MDB2::isConnection($db)) {
  261. $db =& MDB2::factory($db, $db_options);
  262. }
  263. if (PEAR::isError($db)) {
  264. return $db;
  265. }
  266. $this->db =& $db;
  267. $this->db->loadModule('Datatype');
  268. $this->db->loadModule('Manager');
  269. $this->db->loadModule('Reverse');
  270. $this->db->loadModule('Function');
  271. if (empty($this->options['valid_types'])) {
  272. $this->options['valid_types'] = $this->db->datatype->getValidTypes();
  273. }
  274. return MDB2_OK;
  275. }
  276. // }}}
  277. // {{{ disconnect()
  278. /**
  279. * Log out and disconnect from the database.
  280. *
  281. * @access public
  282. * @return void
  283. */
  284. function disconnect()
  285. {
  286. if (MDB2::isConnection($this->db)) {
  287. $this->db->disconnect();
  288. unset($this->db);
  289. }
  290. }
  291. // }}}
  292. // {{{ parseDatabaseDefinition()
  293. /**
  294. * Parse a database definition from a file or an array
  295. *
  296. * @param string|array $schema the database schema array or file name
  297. * @param bool $skip_unreadable if non readable files should be skipped
  298. * @param array $variables associative array that the defines the text string values
  299. * that are meant to be used to replace the variables that are
  300. * used in the schema description.
  301. * @param bool $fail_on_invalid_names make function fail on invalid names
  302. * @param array $structure database structure definition
  303. *
  304. * @access public
  305. * @return array
  306. */
  307. function parseDatabaseDefinition($schema, $skip_unreadable = false, $variables = array(),
  308. $fail_on_invalid_names = true, $structure = false)
  309. {
  310. $database_definition = false;
  311. if (is_string($schema)) {
  312. // if $schema is not readable then we just skip it
  313. // and simply copy the $current_schema file to that file name
  314. if (is_readable($schema)) {
  315. $database_definition = $this->parseDatabaseDefinitionFile($schema, $variables, $fail_on_invalid_names, $structure);
  316. }
  317. } elseif (is_array($schema)) {
  318. $database_definition = $schema;
  319. }
  320. if (!$database_definition && !$skip_unreadable) {
  321. $database_definition = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  322. 'invalid data type of schema or unreadable data source');
  323. }
  324. return $database_definition;
  325. }
  326. // }}}
  327. // {{{ parseDatabaseDefinitionFile()
  328. /**
  329. * Parse a database definition file by creating a schema format
  330. * parser object and passing the file contents as parser input data stream.
  331. *
  332. * @param string $input_file the database schema file.
  333. * @param array $variables associative array that the defines the text string values
  334. * that are meant to be used to replace the variables that are
  335. * used in the schema description.
  336. * @param bool $fail_on_invalid_names make function fail on invalid names
  337. * @param array $structure database structure definition
  338. *
  339. * @access public
  340. * @return array
  341. */
  342. function parseDatabaseDefinitionFile($input_file, $variables = array(),
  343. $fail_on_invalid_names = true, $structure = false)
  344. {
  345. $dtd_file = $this->options['dtd_file'];
  346. if ($dtd_file) {
  347. include_once 'XML/DTD/XmlValidator.php';
  348. $dtd =& new XML_DTD_XmlValidator;
  349. if (!$dtd->isValid($dtd_file, $input_file)) {
  350. return $this->raiseError(MDB2_SCHEMA_ERROR_PARSE, null, null, $dtd->getMessage());
  351. }
  352. }
  353. $class_name = $this->options['parser'];
  354. $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
  355. if (PEAR::isError($result)) {
  356. return $result;
  357. }
  358. $parser =& new $class_name($variables, $fail_on_invalid_names, $structure, $this->options['valid_types'], $this->options['force_defaults']);
  359. $result = $parser->setInputFile($input_file);
  360. if (PEAR::isError($result)) {
  361. return $result;
  362. }
  363. $result = $parser->parse();
  364. if (PEAR::isError($result)) {
  365. return $result;
  366. }
  367. if (PEAR::isError($parser->error)) {
  368. return $parser->error;
  369. }
  370. return $parser->database_definition;
  371. }
  372. // }}}
  373. // {{{ getDefinitionFromDatabase()
  374. /**
  375. * Attempt to reverse engineer a schema structure from an existing MDB2
  376. * This method can be used if no xml schema file exists yet.
  377. * The resulting xml schema file may need some manual adjustments.
  378. *
  379. * @return array|MDB2_Error array with definition or error object
  380. * @access public
  381. */
  382. function getDefinitionFromDatabase()
  383. {
  384. $database = $this->db->database_name;
  385. if (empty($database)) {
  386. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  387. 'it was not specified a valid database name');
  388. }
  389. $class_name = $this->options['validate'];
  390. $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
  391. if (PEAR::isError($result)) {
  392. return $result;
  393. }
  394. $val =& new $class_name($this->options['fail_on_invalid_names'], $this->options['valid_types'], $this->options['force_defaults']);
  395. $database_definition = array(
  396. 'name' => $database,
  397. 'create' => true,
  398. 'overwrite' => false,
  399. 'charset' => 'utf8',
  400. 'description' => '',
  401. 'comments' => '',
  402. 'tables' => array(),
  403. 'sequences' => array(),
  404. );
  405. $tables = $this->db->manager->listTables();
  406. if (PEAR::isError($tables)) {
  407. return $tables;
  408. }
  409. foreach ($tables as $table_name) {
  410. $fields = $this->db->manager->listTableFields($table_name);
  411. if (PEAR::isError($fields)) {
  412. return $fields;
  413. }
  414. $database_definition['tables'][$table_name] = array(
  415. 'was' => '',
  416. 'description' => '',
  417. 'comments' => '',
  418. 'fields' => array(),
  419. 'indexes' => array(),
  420. 'constraints' => array(),
  421. 'initialization' => array()
  422. );
  423. $table_definition =& $database_definition['tables'][$table_name];
  424. foreach ($fields as $field_name) {
  425. $definition = $this->db->reverse->getTableFieldDefinition($table_name, $field_name);
  426. if (PEAR::isError($definition)) {
  427. return $definition;
  428. }
  429. if (!empty($definition[0]['autoincrement'])) {
  430. $definition[0]['default'] = '0';
  431. }
  432. $table_definition['fields'][$field_name] = $definition[0];
  433. $field_choices = count($definition);
  434. if ($field_choices > 1) {
  435. $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
  436. $field_choice_cnt = 1;
  437. $table_definition['fields'][$field_name]['choices'] = array();
  438. foreach ($definition as $field_choice) {
  439. $table_definition['fields'][$field_name]['choices'][] = $field_choice;
  440. $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
  441. $field_choice_cnt++;
  442. }
  443. $this->warnings[] = $warning;
  444. }
  445. /**
  446. * The first parameter is used to verify if there are duplicated
  447. * fields which we can guarantee that won't happen when reverse engineering
  448. */
  449. $result = $val->validateField(array(), $table_definition['fields'][$field_name], $field_name);
  450. if (PEAR::isError($result)) {
  451. return $result;
  452. }
  453. }
  454. $keys = array();
  455. $indexes = $this->db->manager->listTableIndexes($table_name);
  456. if (PEAR::isError($indexes)) {
  457. return $indexes;
  458. }
  459. if (is_array($indexes)) {
  460. foreach ($indexes as $index_name) {
  461. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  462. $definition = $this->db->reverse->getTableIndexDefinition($table_name, $index_name);
  463. $this->db->popExpect();
  464. if (PEAR::isError($definition)) {
  465. if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
  466. continue;
  467. }
  468. return $definition;
  469. }
  470. $keys[$index_name] = $definition;
  471. }
  472. }
  473. $constraints = $this->db->manager->listTableConstraints($table_name);
  474. if (PEAR::isError($constraints)) {
  475. return $constraints;
  476. }
  477. if (is_array($constraints)) {
  478. foreach ($constraints as $constraint_name) {
  479. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  480. $definition = $this->db->reverse->getTableConstraintDefinition($table_name, $constraint_name);
  481. $this->db->popExpect();
  482. if (PEAR::isError($definition)) {
  483. if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
  484. continue;
  485. }
  486. return $definition;
  487. }
  488. $keys[$constraint_name] = $definition;
  489. }
  490. }
  491. foreach ($keys as $key_name => $definition) {
  492. if (array_key_exists('foreign', $definition)
  493. && $definition['foreign']
  494. ) {
  495. /**
  496. * The first parameter is used to verify if there are duplicated
  497. * foreign keys which we can guarantee that won't happen when reverse engineering
  498. */
  499. $result = $val->validateConstraint(array(), $definition, $key_name);
  500. if (PEAR::isError($result)) {
  501. return $result;
  502. }
  503. foreach ($definition['fields'] as $field_name => $field) {
  504. /**
  505. * The first parameter is used to verify if there are duplicated
  506. * referencing fields which we can guarantee that won't happen when reverse engineering
  507. */
  508. $result = $val->validateConstraintField(array(), $field_name);
  509. if (PEAR::isError($result)) {
  510. return $result;
  511. }
  512. $definition['fields'][$field_name] = '';
  513. }
  514. foreach ($definition['references']['fields'] as $field_name => $field) {
  515. /**
  516. * The first parameter is used to verify if there are duplicated
  517. * referenced fields which we can guarantee that won't happen when reverse engineering
  518. */
  519. $result = $val->validateConstraintReferencedField(array(), $field_name);
  520. if (PEAR::isError($result)) {
  521. return $result;
  522. }
  523. $definition['references']['fields'][$field_name] = '';
  524. }
  525. $table_definition['constraints'][$key_name] = $definition;
  526. } else {
  527. /**
  528. * The first parameter is used to verify if there are duplicated
  529. * indices which we can guarantee that won't happen when reverse engineering
  530. */
  531. $result = $val->validateIndex(array(), $definition, $key_name);
  532. if (PEAR::isError($result)) {
  533. return $result;
  534. }
  535. foreach ($definition['fields'] as $field_name => $field) {
  536. /**
  537. * The first parameter is used to verify if there are duplicated
  538. * index fields which we can guarantee that won't happen when reverse engineering
  539. */
  540. $result = $val->validateIndexField(array(), $field, $field_name);
  541. if (PEAR::isError($result)) {
  542. return $result;
  543. }
  544. $definition['fields'][$field_name] = $field;
  545. }
  546. $table_definition['indexes'][$key_name] = $definition;
  547. }
  548. }
  549. /**
  550. * The first parameter is used to verify if there are duplicated
  551. * tables which we can guarantee that won't happen when reverse engineering
  552. */
  553. $result = $val->validateTable(array(), $table_definition, $table_name);
  554. if (PEAR::isError($result)) {
  555. return $result;
  556. }
  557. }
  558. $sequences = $this->db->manager->listSequences();
  559. if (PEAR::isError($sequences)) {
  560. return $sequences;
  561. }
  562. if (is_array($sequences)) {
  563. foreach ($sequences as $sequence_name) {
  564. $definition = $this->db->reverse->getSequenceDefinition($sequence_name);
  565. if (PEAR::isError($definition)) {
  566. return $definition;
  567. }
  568. if (isset($database_definition['tables'][$sequence_name])
  569. && isset($database_definition['tables'][$sequence_name]['indexes'])
  570. ) {
  571. foreach ($database_definition['tables'][$sequence_name]['indexes'] as $index) {
  572. if (isset($index['primary']) && $index['primary']
  573. && count($index['fields'] == 1)
  574. ) {
  575. $definition['on'] = array(
  576. 'table' => $sequence_name,
  577. 'field' => key($index['fields']),
  578. );
  579. break;
  580. }
  581. }
  582. }
  583. /**
  584. * The first parameter is used to verify if there are duplicated
  585. * sequences which we can guarantee that won't happen when reverse engineering
  586. */
  587. $result = $val->validateSequence(array(), $definition, $sequence_name);
  588. if (PEAR::isError($result)) {
  589. return $result;
  590. }
  591. $database_definition['sequences'][$sequence_name] = $definition;
  592. }
  593. }
  594. $result = $val->validateDatabase($database_definition);
  595. if (PEAR::isError($result)) {
  596. return $result;
  597. }
  598. return $database_definition;
  599. }
  600. // }}}
  601. // {{{ createTableIndexes()
  602. /**
  603. * A method to create indexes for an existing table
  604. *
  605. * @param string $table_name Name of the table
  606. * @param array $indexes An array of indexes to be created
  607. * @param boolean $overwrite If the table/index should be overwritten if it already exists
  608. *
  609. * @return mixed MDB2_Error if there is an error creating an index, MDB2_OK otherwise
  610. * @access public
  611. */
  612. function createTableIndexes($table_name, $indexes, $overwrite = false)
  613. {
  614. if (!$this->db->supports('indexes')) {
  615. $this->db->debug('Indexes are not supported', __FUNCTION__);
  616. return MDB2_OK;
  617. }
  618. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  619. foreach ($indexes as $index_name => $index) {
  620. // Does the index already exist, and if so, should it be overwritten?
  621. $create_index = true;
  622. $this->db->expectError($errorcodes);
  623. if (!empty($index['primary']) || !empty($index['unique'])) {
  624. $current_indexes = $this->db->manager->listTableConstraints($table_name);
  625. } else {
  626. $current_indexes = $this->db->manager->listTableIndexes($table_name);
  627. }
  628. $this->db->popExpect();
  629. if (PEAR::isError($current_indexes)) {
  630. if (!MDB2::isError($current_indexes, $errorcodes)) {
  631. return $current_indexes;
  632. }
  633. } elseif (is_array($current_indexes) && in_array($index_name, $current_indexes)) {
  634. if (!$overwrite) {
  635. $this->db->debug('Index already exists: '.$index_name, __FUNCTION__);
  636. $create_index = false;
  637. } else {
  638. $this->db->debug('Preparing to overwrite index: '.$index_name, __FUNCTION__);
  639. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  640. if (!empty($index['primary']) || !empty($index['unique'])) {
  641. $result = $this->db->manager->dropConstraint($table_name, $index_name);
  642. } else {
  643. $result = $this->db->manager->dropIndex($table_name, $index_name);
  644. }
  645. $this->db->popExpect();
  646. if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) {
  647. return $result;
  648. }
  649. }
  650. }
  651. // Check if primary is being used and if it's supported
  652. if (!empty($index['primary']) && !$this->db->supports('primary_key')) {
  653. // Primary not supported so we fallback to UNIQUE and making the field NOT NULL
  654. $index['unique'] = true;
  655. $changes = array();
  656. foreach ($index['fields'] as $field => $empty) {
  657. $field_info = $this->db->reverse->getTableFieldDefinition($table_name, $field);
  658. if (PEAR::isError($field_info)) {
  659. return $field_info;
  660. }
  661. if (!$field_info[0]['notnull']) {
  662. $changes['change'][$field] = $field_info[0];
  663. $changes['change'][$field]['notnull'] = true;
  664. }
  665. }
  666. if (!empty($changes)) {
  667. $this->db->manager->alterTable($table_name, $changes, false);
  668. }
  669. }
  670. // Should the index be created?
  671. if ($create_index) {
  672. if (!empty($index['primary']) || !empty($index['unique'])) {
  673. $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
  674. } else {
  675. $result = $this->db->manager->createIndex($table_name, $index_name, $index);
  676. }
  677. if (PEAR::isError($result)) {
  678. return $result;
  679. }
  680. }
  681. }
  682. return MDB2_OK;
  683. }
  684. // }}}
  685. // {{{ createTableConstraints()
  686. /**
  687. * A method to create foreign keys for an existing table
  688. *
  689. * @param string $table_name Name of the table
  690. * @param array $constraints An array of foreign keys to be created
  691. * @param boolean $overwrite If the foreign key should be overwritten if it already exists
  692. *
  693. * @return mixed MDB2_Error if there is an error creating a foreign key, MDB2_OK otherwise
  694. * @access public
  695. */
  696. function createTableConstraints($table_name, $constraints, $overwrite = false)
  697. {
  698. if (!$this->db->supports('indexes')) {
  699. $this->db->debug('Indexes are not supported', __FUNCTION__);
  700. return MDB2_OK;
  701. }
  702. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  703. foreach ($constraints as $constraint_name => $constraint) {
  704. // Does the foreign key already exist, and if so, should it be overwritten?
  705. $create_constraint = true;
  706. $this->db->expectError($errorcodes);
  707. $current_constraints = $this->db->manager->listTableConstraints($table_name);
  708. $this->db->popExpect();
  709. if (PEAR::isError($current_constraints)) {
  710. if (!MDB2::isError($current_constraints, $errorcodes)) {
  711. return $current_constraints;
  712. }
  713. } elseif (is_array($current_constraints) && in_array($constraint_name, $current_constraints)) {
  714. if (!$overwrite) {
  715. $this->db->debug('Foreign key already exists: '.$constraint_name, __FUNCTION__);
  716. $create_constraint = false;
  717. } else {
  718. $this->db->debug('Preparing to overwrite foreign key: '.$constraint_name, __FUNCTION__);
  719. $result = $this->db->manager->dropConstraint($table_name, $constraint_name);
  720. if (PEAR::isError($result)) {
  721. return $result;
  722. }
  723. }
  724. }
  725. // Should the foreign key be created?
  726. if ($create_constraint) {
  727. $result = $this->db->manager->createConstraint($table_name, $constraint_name, $constraint);
  728. if (PEAR::isError($result)) {
  729. return $result;
  730. }
  731. }
  732. }
  733. return MDB2_OK;
  734. }
  735. // }}}
  736. // {{{ createTable()
  737. /**
  738. * Create a table and inititialize the table if data is available
  739. *
  740. * @param string $table_name name of the table to be created
  741. * @param array $table multi dimensional array that contains the
  742. * structure and optional data of the table
  743. * @param bool $overwrite if the table/index should be overwritten if it already exists
  744. * @param array $options an array of options to be passed to the database specific driver
  745. * version of MDB2_Driver_Manager_Common::createTable().
  746. *
  747. * @return bool|MDB2_Error MDB2_OK or error object
  748. * @access public
  749. */
  750. function createTable($table_name, $table, $overwrite = false, $options = array())
  751. {
  752. $create = true;
  753. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  754. $this->db->expectError($errorcodes);
  755. $tables = $this->db->manager->listTables();
  756. $this->db->popExpect();
  757. if (PEAR::isError($tables)) {
  758. if (!MDB2::isError($tables, $errorcodes)) {
  759. return $tables;
  760. }
  761. } elseif (is_array($tables) && in_array($table_name, $tables)) {
  762. if (!$overwrite) {
  763. $create = false;
  764. $this->db->debug('Table already exists: '.$table_name, __FUNCTION__);
  765. } else {
  766. $result = $this->db->manager->dropTable($table_name);
  767. if (PEAR::isError($result)) {
  768. return $result;
  769. }
  770. $this->db->debug('Overwritting table: '.$table_name, __FUNCTION__);
  771. }
  772. }
  773. if ($create) {
  774. $result = $this->db->manager->createTable($table_name, $table['fields'], $options);
  775. if (PEAR::isError($result)) {
  776. return $result;
  777. }
  778. }
  779. if (!empty($table['initialization']) && is_array($table['initialization'])) {
  780. $result = $this->initializeTable($table_name, $table);
  781. if (PEAR::isError($result)) {
  782. return $result;
  783. }
  784. }
  785. if (!empty($table['indexes']) && is_array($table['indexes'])) {
  786. $result = $this->createTableIndexes($table_name, $table['indexes'], $overwrite);
  787. if (PEAR::isError($result)) {
  788. return $result;
  789. }
  790. }
  791. if (!empty($table['constraints']) && is_array($table['constraints'])) {
  792. $result = $this->createTableConstraints($table_name, $table['constraints'], $overwrite);
  793. if (PEAR::isError($result)) {
  794. return $result;
  795. }
  796. }
  797. return MDB2_OK;
  798. }
  799. // }}}
  800. // {{{ initializeTable()
  801. /**
  802. * Inititialize the table with data
  803. *
  804. * @param string $table_name name of the table
  805. * @param array $table multi dimensional array that contains the
  806. * structure and optional data of the table
  807. *
  808. * @return bool|MDB2_Error MDB2_OK or error object
  809. * @access public
  810. */
  811. function initializeTable($table_name, $table)
  812. {
  813. $query_insertselect = 'INSERT INTO %s (%s) (SELECT %s FROM %s %s)';
  814. $query_insert = 'INSERT INTO %s (%s) VALUES (%s)';
  815. $query_update = 'UPDATE %s SET %s %s';
  816. $query_delete = 'DELETE FROM %s %s';
  817. $table_name = $this->db->quoteIdentifier($table_name, true);
  818. $result = MDB2_OK;
  819. $support_transactions = $this->db->supports('transactions');
  820. foreach ($table['initialization'] as $instruction) {
  821. $query = '';
  822. switch ($instruction['type']) {
  823. case 'insert':
  824. if (!isset($instruction['data']['select'])) {
  825. $data = $this->getInstructionFields($instruction['data'], $table['fields']);
  826. if (!empty($data)) {
  827. $fields = implode(', ', array_keys($data));
  828. $values = implode(', ', array_values($data));
  829. $query = sprintf($query_insert, $table_name, $fields, $values);
  830. }
  831. } else {
  832. $data = $this->getInstructionFields($instruction['data']['select'], $table['fields']);
  833. $where = $this->getInstructionWhere($instruction['data']['select'], $table['fields']);
  834. $select_table_name = $this->db->quoteIdentifier($instruction['data']['select']['table'], true);
  835. if (!empty($data)) {
  836. $fields = implode(', ', array_keys($data));
  837. $values = implode(', ', array_values($data));
  838. $query = sprintf($query_insertselect, $table_name, $fields, $values, $select_table_name, $where);
  839. }
  840. }
  841. break;
  842. case 'update':
  843. $data = $this->getInstructionFields($instruction['data'], $table['fields']);
  844. $where = $this->getInstructionWhere($instruction['data'], $table['fields']);
  845. if (!empty($data)) {
  846. array_walk($data, array($this, 'buildFieldValue'));
  847. $fields_values = implode(', ', $data);
  848. $query = sprintf($query_update, $table_name, $fields_values, $where);
  849. }
  850. break;
  851. case 'delete':
  852. $where = $this->getInstructionWhere($instruction['data'], $table['fields']);
  853. $query = sprintf($query_delete, $table_name, $where);
  854. break;
  855. }
  856. if ($query) {
  857. if ($support_transactions && PEAR::isError($res = $this->db->beginNestedTransaction())) {
  858. return $res;
  859. }
  860. $result = $this->db->exec($query);
  861. if (PEAR::isError($result)) {
  862. return $result;
  863. }
  864. if ($support_transactions && PEAR::isError($res = $this->db->completeNestedTransaction())) {
  865. return $res;
  866. }
  867. }
  868. }
  869. return $result;
  870. }
  871. // }}}
  872. // {{{ buildFieldValue()
  873. /**
  874. * Appends the contents of second argument + '=' to the beginning of first
  875. * argument.
  876. *
  877. * Used with array_walk() in initializeTable() for UPDATEs.
  878. *
  879. * @param string &$element value of array's element
  880. * @param string $key key of array's element
  881. *
  882. * @return void
  883. *
  884. * @access public
  885. * @see MDB2_Schema::initializeTable()
  886. */
  887. function buildFieldValue(&$element, $key)
  888. {
  889. $element = $key."=$element";
  890. }
  891. // }}}
  892. // {{{ getExpression()
  893. /**
  894. * Generates a string that represents a value that would be associated
  895. * with a column in a DML instruction.
  896. *
  897. * @param array $element multi dimensional array that contains the
  898. * structure of the current DML instruction.
  899. * @param array $fields_definition multi dimensional array that contains the
  900. * definition for current table's fields
  901. * @param string $type type of given field
  902. *
  903. * @return string
  904. *
  905. * @access public
  906. * @see MDB2_Schema::getInstructionFields(), MDB2_Schema::getInstructionWhere()
  907. */
  908. function getExpression($element, $fields_definition = array(), $type = null)
  909. {
  910. $str = '';
  911. switch ($element['type']) {
  912. case 'null':
  913. $str .= 'NULL';
  914. break;
  915. case 'value':
  916. $str .= $this->db->quote($element['data'], $type);
  917. break;
  918. case 'column':
  919. $str .= $this->db->quoteIdentifier($element['data'], true);
  920. break;
  921. case 'function':
  922. $arguments = array();
  923. if (!empty($element['data']['arguments'])
  924. && is_array($element['data']['arguments'])
  925. ) {
  926. foreach ($element['data']['arguments'] as $v) {
  927. $arguments[] = $this->getExpression($v, $fields_definition);
  928. }
  929. }
  930. if (method_exists($this->db->function, $element['data']['name'])) {
  931. $user_func = array(&$this->db->function, $element['data']['name']);
  932. $str .= call_user_func_array($user_func, $arguments);
  933. } else {
  934. $str .= $element['data']['name'].'(';
  935. $str .= implode(', ', $arguments);
  936. $str .= ')';
  937. }
  938. break;
  939. case 'expression':
  940. $type0 = $type1 = null;
  941. if ($element['data']['operants'][0]['type'] == 'column'
  942. && array_key_exists($element['data']['operants'][0]['data'], $fields_definition)
  943. ) {
  944. $type0 = $fields_definition[$element['data']['operants'][0]['data']]['type'];
  945. }
  946. if ($element['data']['operants'][1]['type'] == 'column'
  947. && array_key_exists($element['data']['operants'][1]['data'], $fields_definition)
  948. ) {
  949. $type1 = $fields_definition[$element['data']['operants'][1]['data']]['type'];
  950. }
  951. $str .= '(';
  952. $str .= $this->getExpression($element['data']['operants'][0], $fields_definition, $type1);
  953. $str .= $this->getOperator($element['data']['operator']);
  954. $str .= $this->getExpression($element['data']['operants'][1], $fields_definition, $type0);
  955. $str .= ')';
  956. break;
  957. }
  958. return $str;
  959. }
  960. // }}}
  961. // {{{ getOperator()
  962. /**
  963. * Returns the matching SQL operator
  964. *
  965. * @param string $op parsed descriptive operator
  966. *
  967. * @return string matching SQL operator
  968. *
  969. * @access public
  970. * @static
  971. * @see MDB2_Schema::getExpression()
  972. */
  973. function getOperator($op)
  974. {
  975. switch ($op) {
  976. case 'PLUS':
  977. return ' + ';
  978. case 'MINUS':
  979. return ' - ';
  980. case 'TIMES':
  981. return ' * ';
  982. case 'DIVIDED':
  983. return ' / ';
  984. case 'EQUAL':
  985. return ' = ';
  986. case 'NOT EQUAL':
  987. return ' != ';
  988. case 'LESS THAN':
  989. return ' < ';
  990. case 'GREATER THAN':
  991. return ' > ';
  992. case 'LESS THAN OR EQUAL':
  993. return ' <= ';
  994. case 'GREATER THAN OR EQUAL':
  995. return ' >= ';
  996. default:
  997. return ' '.$op.' ';
  998. }
  999. }
  1000. // }}}
  1001. // {{{ getInstructionFields()
  1002. /**
  1003. * Walks the parsed DML instruction array, field by field,
  1004. * storing them and their processed values inside a new array.
  1005. *
  1006. * @param array $instruction multi dimensional array that contains the
  1007. * structure of the current DML instruction.
  1008. * @param array $fields_definition multi dimensional array that contains the
  1009. * definition for current table's fields
  1010. *
  1011. * @return array array of strings in the form 'field_name' => 'value'
  1012. *
  1013. * @access public
  1014. * @static
  1015. * @see MDB2_Schema::initializeTable()
  1016. */
  1017. function getInstructionFields($instruction, $fields_definition = array())
  1018. {
  1019. $fields = array();
  1020. if (!empty($instruction['field']) && is_array($instruction['field'])) {
  1021. foreach ($instruction['field'] as $field) {
  1022. $field_name = $this->db->quoteIdentifier($field['name'], true);
  1023. $fields[$field_name] = $this->getExpression($field['group'], $fields_definition);
  1024. }
  1025. }
  1026. return $fields;
  1027. }
  1028. // }}}
  1029. // {{{ getInstructionWhere()
  1030. /**
  1031. * Translates the parsed WHERE expression of a DML instruction
  1032. * (array structure) to a SQL WHERE clause (string).
  1033. *
  1034. * @param array $instruction multi dimensional array that contains the
  1035. * structure of the current DML instruction.
  1036. * @param array $fields_definition multi dimensional array that contains the
  1037. * definition for current table's fields.
  1038. *
  1039. * @return string SQL WHERE clause
  1040. *
  1041. * @access public
  1042. * @static
  1043. * @see MDB2_Schema::initializeTable()
  1044. */
  1045. function getInstructionWhere($instruction, $fields_definition = array())
  1046. {
  1047. $where = '';
  1048. if (!empty($instruction['where'])) {
  1049. $where = 'WHERE '.$this->getExpression($instruction['where'], $fields_definition);
  1050. }
  1051. return $where;
  1052. }
  1053. // }}}
  1054. // {{{ createSequence()
  1055. /**
  1056. * Create a sequence
  1057. *
  1058. * @param string $sequence_name name of the sequence to be created
  1059. * @param array $sequence multi dimensional array that contains the
  1060. * structure and optional data of the table
  1061. * @param bool $overwrite if the sequence should be overwritten if it already exists
  1062. *
  1063. * @return bool|MDB2_Error MDB2_OK or error object
  1064. * @access public
  1065. */
  1066. function createSequence($sequence_name, $sequence, $overwrite = false)
  1067. {
  1068. if (!$this->db->supports('sequences')) {
  1069. $this->db->debug('Sequences are not supported', __FUNCTION__);
  1070. return MDB2_OK;
  1071. }
  1072. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  1073. $this->db->expectError($errorcodes);
  1074. $sequences = $this->db->manager->listSequences();
  1075. $this->db->popExpect();
  1076. if (PEAR::isError($sequences)) {
  1077. if (!MDB2::isError($sequences, $errorcodes)) {
  1078. return $sequences;
  1079. }
  1080. } elseif (is_array($sequence) && in_array($sequence_name, $sequences)) {
  1081. if (!$overwrite) {
  1082. $this->db->debug('Sequence already exists: '.$sequence_name, __FUNCTION__);
  1083. return MDB2_OK;
  1084. }
  1085. $result = $this->db->manager->dropSequence($sequence_name);
  1086. if (PEAR::isError($result)) {
  1087. return $result;
  1088. }
  1089. $this->db->debug('Overwritting sequence: '.$sequence_name, __FUNCTION__);
  1090. }
  1091. $start = 1;
  1092. $field = '';
  1093. if (!empty($sequence['on'])) {
  1094. $table = $sequence['on']['table'];
  1095. $field = $sequence['on']['field'];
  1096. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  1097. $this->db->expectError($errorcodes);
  1098. $tables = $this->db->manager->listTables();
  1099. $this->db->popExpect();
  1100. if (PEAR::isError($tables) && !MDB2::isError($tables, $errorcodes)) {
  1101. return $tables;
  1102. }
  1103. if (!PEAR::isError($tables) && is_array($tables) && in_array($table, $tables)) {
  1104. if ($this->db->supports('summary_functions')) {
  1105. $query = "SELECT MAX($field) FROM ".$this->db->quoteIdentifier($table, true);
  1106. } else {
  1107. $query = "SELECT $field FROM ".$this->db->quoteIdentifier($table, true)." ORDER BY $field DESC";
  1108. }
  1109. $start = $this->db->queryOne($query, 'integer');
  1110. if (PEAR::isError($start)) {
  1111. return $start;
  1112. }
  1113. ++$start;
  1114. } else {
  1115. $this->warnings[] = 'Could not sync sequence: '.$sequence_name;
  1116. }
  1117. } elseif (!empty($sequence['start']) && is_numeric($sequence['start'])) {
  1118. $start = $sequence['start'];
  1119. $table = '';
  1120. }
  1121. $result = $this->db->manager->createSequence($sequence_name, $start);
  1122. if (PEAR::isError($result)) {
  1123. return $result;
  1124. }
  1125. return MDB2_OK;
  1126. }
  1127. // }}}
  1128. // {{{ createDatabase()
  1129. /**
  1130. * Create a database space within which may be created database objects
  1131. * like tables, indexes and sequences. The implementation of this function
  1132. * is highly DBMS specific and may require special permissions to run
  1133. * successfully. Consult the documentation or the DBMS drivers that you
  1134. * use to be aware of eventual configuration requirements.
  1135. *
  1136. * @param array $database_definition multi dimensional array that contains the current definition
  1137. * @param array $options an array of options to be passed to the
  1138. * database specific driver version of
  1139. * MDB2_Driver_Manager_Common::createTable().
  1140. *
  1141. * @return bool|MDB2_Error MDB2_OK or error object
  1142. * @access public
  1143. */
  1144. function createDatabase($database_definition, $options = array())
  1145. {
  1146. if (!isset($database_definition['name']) || !$database_definition['name']) {
  1147. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1148. 'no valid database name specified');
  1149. }
  1150. $create = (isset($database_definition['create']) && $database_definition['create']);
  1151. $overwrite = (isset($database_definition['overwrite']) && $database_definition['overwrite']);
  1152. /**
  1153. *
  1154. * We need to clean up database name before any query to prevent
  1155. * database driver from using a inexistent database
  1156. *
  1157. */
  1158. $previous_database_name = $this->db->setDatabase('');
  1159. // Lower / Upper case the db name if the portability deems so.
  1160. if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
  1161. $func = $this->db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper';
  1162. $db_name = $func($database_definition['name']);
  1163. } else {
  1164. $db_name = $database_definition['name'];
  1165. }
  1166. if ($create) {
  1167. $dbExists = $this->db->databaseExists($db_name);
  1168. if (PEAR::isError($dbExists)) {
  1169. return $dbExists;
  1170. }
  1171. if ($dbExists && $overwrite) {
  1172. $this->db->expectError(MDB2_ERROR_CANNOT_DROP);
  1173. $result = $this->db->manager->dropDatabase($db_name);
  1174. $this->db->popExpect();
  1175. if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_CANNOT_DROP)) {
  1176. return $result;
  1177. }
  1178. $dbExists = false;
  1179. $this->db->debug('Overwritting database: ' . $db_name, __FUNCTION__);
  1180. }
  1181. $dbOptions = array();
  1182. if (array_key_exists('charset', $database_definition)
  1183. && !empty($database_definition['charset'])) {
  1184. $dbOptions['charset'] = $database_definition['charset'];
  1185. }
  1186. if ($dbExists) {
  1187. $this->db->debug('Database already exists: ' . $db_name, __FUNCTION__);
  1188. if (!empty($dbOptions)) {
  1189. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NO_PERMISSION);
  1190. $this->db->expectError($errorcodes);
  1191. $result = $this->db->manager->alterDatabase($db_name, $dbOptions);
  1192. $this->db->popExpect();
  1193. if (PEAR::isError($result) && !MDB2::isError($result, $errorcodes)) {
  1194. return $result;
  1195. }
  1196. }
  1197. $create = false;
  1198. } else {
  1199. $this->db->expectError(MDB2_ERROR_UNSUPPORTED);
  1200. $result = $this->db->manager->createDatabase($db_name, $dbOptions);
  1201. $this->db->popExpect();
  1202. if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_UNSUPPORTED)) {
  1203. return $result;
  1204. }
  1205. $this->db->debug('Creating database: ' . $db_name, __FUNCTION__);
  1206. }
  1207. }
  1208. $this->db->setDatabase($db_name);
  1209. if (($support_transactions = $this->db->supports('transactions'))
  1210. && PEAR::isError($result = $this->db->beginNestedTransaction())
  1211. ) {
  1212. return $result;
  1213. }
  1214. $created_objects = 0;
  1215. if (isset($database_definition['tables'])
  1216. && is_array($database_definition['tables'])
  1217. ) {
  1218. foreach ($database_definition['tables'] as $table_name => $table) {
  1219. $result = $this->createTable($table_name, $table, $overwrite, $options);
  1220. if (PEAR::isError($result)) {
  1221. break;
  1222. }
  1223. $created_objects++;
  1224. }
  1225. }
  1226. if (!PEAR::isError($result)
  1227. && isset($database_definition['sequences'])
  1228. && is_array($database_definition['sequences'])
  1229. ) {
  1230. foreach ($database_definition['sequences'] as $sequence_name => $sequence) {
  1231. $result = $this->createSequence($sequence_name, $sequence, false, $overwrite);
  1232. if (PEAR::isError($result)) {
  1233. break;
  1234. }
  1235. $created_objects++;
  1236. }
  1237. }
  1238. if ($support_transactions) {
  1239. $res = $this->db->completeNestedTransaction();
  1240. if (PEAR::isError($res)) {
  1241. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  1242. 'Could not end transaction ('.
  1243. $res->getMessage().' ('.$res->getUserinfo().'))');
  1244. }
  1245. } elseif (PEAR::isError($result) && $created_objects) {
  1246. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  1247. 'the database was only partially created ('.
  1248. $result->getMessage().' ('.$result->getUserinfo().'))');
  1249. }
  1250. $this->db->setDatabase($previous_database_name);
  1251. if (PEAR::isError($result) && $create
  1252. && PEAR::isError($result2 = $this->db->manager->dropDatabase($db_name))
  1253. ) {
  1254. if (!MDB2::isError($result2, MDB2_ERROR_UNSUPPORTED)) {
  1255. return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  1256. 'Could not drop the created database after unsuccessful creation attempt ('.
  1257. $result2->getMessage().' ('.$result2->getUserinfo().'))');
  1258. }
  1259. }
  1260. return $result;
  1261. }
  1262. // }}}
  1263. // {{{ compareDefinitions()
  1264. /**
  1265. * Compare a previous definition with the currently parsed definition
  1266. *
  1267. * @param array $current_definition multi dimensional array that contains the current definition
  1268. * @param array $previous_definition multi dimensional array that contains the previous definition
  1269. *
  1270. * @return array|MDB2_Error array of changes on success, or a error object
  1271. * @access public
  1272. */
  1273. function compareDefinitions($current_definition, $previous_definition)
  1274. {
  1275. $changes = array();
  1276. if (!empty($current_definition['tables']) && is_array($current_definition['tables'])) {
  1277. $changes['tables'] = $defined_tables = array();
  1278. foreach ($current_definition['tables'] as $table_name => $table) {
  1279. $previous_tables = array();
  1280. if (!empty($previous_definition) && is_array($previous_definition)) {
  1281. $previous_tables = $previous_definition['tables'];
  1282. }
  1283. $change = $this->compareTableDefinitions($table_name, $table, $previous_tables, $defined_tables);
  1284. if (PEAR::isError($change)) {
  1285. return $change;
  1286. }
  1287. if (!empty($change)) {
  1288. $changes['tables'] = MDB2_Schema::arrayMergeClobber($changes['tables'], $change);
  1289. }
  1290. }
  1291. if (!empty($previous_definition['tables'])
  1292. && is_array($previous_definition['tables'])) {
  1293. foreach ($previous_definition['tables'] as $table_name => $table) {
  1294. if (empty($defined_tables[$table_name])) {
  1295. $changes['tables']['remove'][$table_name] = true;
  1296. }
  1297. }
  1298. }
  1299. }
  1300. if (!empty($current_definition['sequences']) && is_array($current_definition['sequences'])) {
  1301. $changes['sequences'] = $defined_sequences = array();
  1302. foreach ($current_definition['sequences'] as $sequence_name => $sequence) {
  1303. $previous_sequences = array();
  1304. if (!empty($previous_definition) && is_array($previous_definition)) {
  1305. $previous_sequences = $previous_definition['sequences'];
  1306. }
  1307. $change = $this->compareSequenceDefinitions($sequence_name,
  1308. $sequence,
  1309. $previous_sequences,
  1310. $defined_sequences);
  1311. if (PEAR::isError($change)) {
  1312. return $change;
  1313. }
  1314. if (!empty($change)) {
  1315. $changes['sequences'] = MDB2_Schema::arrayMergeClobber($changes['sequences'], $change);
  1316. }
  1317. }
  1318. if (!empty($previous_definition['sequences']) && is_array($previous_definition['sequences'])) {
  1319. foreach ($previous_definition['sequences'] as $sequence_name => $sequence) {
  1320. if (empty($defined_sequences[$sequence_name])) {
  1321. $changes['sequences']['remove'][$sequence_name] = true;
  1322. }
  1323. }
  1324. }
  1325. }
  1326. return $changes;
  1327. }
  1328. // }}}
  1329. // {{{ compareTableFieldsDefinitions()
  1330. /**
  1331. * Compare a previous definition with the currently parsed definition
  1332. *
  1333. * @param string $table_name name of the table
  1334. * @param array $current_definition multi dimensional array that contains the current definition
  1335. * @param array $previous_definition multi dimensional array that contains the previous definition
  1336. *
  1337. * @return array|MDB2_Error array of changes on success, or a error object
  1338. * @access public
  1339. */
  1340. function compareTableFieldsDefinitions($table_name, $current_definition,
  1341. $previous_definition)
  1342. {
  1343. $changes = $defined_fields = array();
  1344. if (is_array($current_definition)) {
  1345. foreach ($current_definition as $field_name => $field) {
  1346. $was_field_name = $field['was'];
  1347. if (!empty($previous_definition[$field_name])
  1348. && (
  1349. (isset($previous_definition[$field_name]['was'])
  1350. && $previous_definition[$field_name]['was'] == $was_field_name)
  1351. || !isset($previous_definition[$was_field_name])
  1352. )) {
  1353. $was_field_name = $field_name;
  1354. }
  1355. if (!empty($previous_definition[$was_field_name])) {
  1356. if ($was_field_name != $field_name) {
  1357. $changes['rename'][$was_field_name] = array('name' => $field_name, 'definition' => $field);
  1358. }
  1359. if (!empty($defined_fields[$was_field_name])) {
  1360. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1361. 'the field "'.$was_field_name.
  1362. '" was specified for more than one field of table');
  1363. }
  1364. $defined_fields[$was_field_name] = true;
  1365. $change = $this->db->compareDefinition($field, $previous_definition[$was_field_name]);
  1366. if (PEAR::isError($change)) {
  1367. return $change;
  1368. }
  1369. if (!empty($change)) {
  1370. if (array_key_exists('default', $change)
  1371. && $change['default']
  1372. && !array_key_exists('default', $field)) {
  1373. $field['default'] = null;
  1374. }
  1375. $change['definition'] = $field;
  1376. $changes['change'][$field_name] = $change;
  1377. }
  1378. } else {
  1379. if ($field_name != $was_field_name) {
  1380. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1381. 'it was specified a previous field name ("'.
  1382. $was_field_name.'") for field "'.$field_name.'" of table "'.
  1383. $table_name.'" that does not exist');
  1384. }
  1385. $changes['add'][$field_name] = $field;
  1386. }
  1387. }
  1388. }
  1389. if (isset($previous_definition) && is_array($previous_definition)) {
  1390. foreach ($previous_definition as $field_previous_name => $field_previous) {
  1391. if (empty($defined_fields[$field_previous_name])) {
  1392. $changes['remove'][$field_previous_name] = true;
  1393. }
  1394. }
  1395. }
  1396. return $changes;
  1397. }
  1398. // }}}
  1399. // {{{ compareTableIndexesDefinitions()
  1400. /**
  1401. * Compare a previous definition with the currently parsed definition
  1402. *
  1403. * @param string $table_name name of the table
  1404. * @param array $current_definition multi dimensional array that contains the current definition
  1405. * @param array $previous_definition multi dimensional array that contains the previous definition
  1406. *
  1407. * @return array|MDB2_Error array of changes on success, or a error object
  1408. * @access public
  1409. */
  1410. function compareTableIndexesDefinitions($table_name, $current_definition,
  1411. $previous_definition)
  1412. {
  1413. $changes = $defined_indexes = array();
  1414. if (is_array($current_definition)) {
  1415. foreach ($current_definition as $index_name => $index) {
  1416. $was_index_name = $index['was'];
  1417. if (!empty($previous_definition[$index_name])
  1418. && isset($previous_definition[$index_name]['was'])
  1419. && $previous_definition[$index_name]['was'] == $was_index_name
  1420. ) {
  1421. $was_index_name = $index_name;
  1422. }
  1423. if (!empty($previous_definition[$was_index_name])) {
  1424. $change = array();
  1425. if ($was_index_name != $index_name) {
  1426. $change['name'] = $was_index_name;
  1427. }
  1428. if (!empty($defined_indexes[$was_index_name])) {
  1429. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1430. 'the index "'.$was_index_name.'" was specified for'.
  1431. ' more than one index of table "'.$table_name.'"');
  1432. }
  1433. $defined_indexes[$was_index_name] = true;
  1434. $previous_unique = array_key_exists('unique', $previous_definition[$was_index_name])
  1435. ? $previous_definition[$was_index_name]['unique'] : false;
  1436. $unique = array_key_exists('unique', $index) ? $index['unique'] : false;
  1437. if ($previous_unique != $unique) {
  1438. $change['unique'] = $unique;
  1439. }
  1440. $previous_primary = array_key_exists('primary', $previous_definition[$was_index_name])
  1441. ? $previous_definition[$was_index_name]['primary'] : false;
  1442. $primary = array_key_exists('primary', $index) ? $index['primary'] : false;
  1443. if ($previous_primary != $primary) {
  1444. $change['primary'] = $primary;
  1445. }
  1446. $defined_fields = array();
  1447. $previous_fields = $previous_definition[$was_index_name]['fields'];
  1448. if (!empty($index['fields']) && is_array($index['fields'])) {
  1449. foreach ($index['fields'] as $field_name => $field) {
  1450. if (!empty($previous_fields[$field_name])) {
  1451. $defined_fields[$field_name] = true;
  1452. $previous_sorting = array_key_exists('sorting', $previous_fields[$field_name])
  1453. ? $previous_fields[$field_name]['sorting'] : '';
  1454. $sorting = array_key_exists('sorting', $field) ? $field['sorting'] : '';
  1455. if ($previous_sorting != $sorting) {
  1456. $change['change'] = true;
  1457. }
  1458. } else {
  1459. $change['change'] = true;
  1460. }
  1461. }
  1462. }
  1463. if (isset($previous_fields) && is_array($previous_fields)) {
  1464. foreach ($previous_fields as $field_name => $field) {
  1465. if (empty($defined_fields[$field_name])) {
  1466. $change['change'] = true;
  1467. }
  1468. }
  1469. }
  1470. if (!empty($change)) {
  1471. $changes['change'][$index_name] = $current_definition[$index_name];
  1472. }
  1473. } else {
  1474. if ($index_name != $was_index_name) {
  1475. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1476. 'it was specified a previous index name ("'.$was_index_name.
  1477. ') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist');
  1478. }
  1479. $changes['add'][$index_name] = $current_definition[$index_name];
  1480. }
  1481. }
  1482. }
  1483. foreach ($previous_definition as $index_previous_name => $index_previous) {
  1484. if (empty($defined_indexes[$index_previous_name])) {
  1485. $changes['remove'][$index_previous_name] = $index_previous;
  1486. }
  1487. }
  1488. return $changes;
  1489. }
  1490. // }}}
  1491. // {{{ compareTableDefinitions()
  1492. /**
  1493. * Compare a previous definition with the currently parsed definition
  1494. *
  1495. * @param string $table_name name of the table
  1496. * @param array $current_definition multi dimensional array that contains the current definition
  1497. * @param array $previous_definition multi dimensional array that contains the previous definition
  1498. * @param array &$defined_tables table names in the schema
  1499. *
  1500. * @return array|MDB2_Error array of changes on success, or a error object
  1501. * @access public
  1502. */
  1503. function compareTableDefinitions($table_name, $current_definition,
  1504. $previous_definition, &$defined_tables)
  1505. {
  1506. $changes = array();
  1507. if (is_array($current_definition)) {
  1508. $was_table_name = $table_name;
  1509. if (!empty($current_definition['was'])) {
  1510. $was_table_name = $current_definition['was'];
  1511. }
  1512. if (!empty($previous_definition[$was_table_name])) {
  1513. $changes['change'][$was_table_name] = array();
  1514. if ($was_table_name != $table_name) {
  1515. $changes['change'][$was_table_name] = array('name' => $table_name);
  1516. }
  1517. if (!empty($defined_tables[$was_table_name])) {
  1518. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1519. 'the table "'.$was_table_name.
  1520. '" was specified for more than one table of the database');
  1521. }
  1522. $defined_tables[$was_table_name] = true;
  1523. if (!empty($current_definition['fields']) && is_array($current_definition['fields'])) {
  1524. $previous_fields = array();
  1525. if (isset($previous_definition[$was_table_name]['fields'])
  1526. && is_array($previous_definition[$was_table_name]['fields'])) {
  1527. $previous_fields = $previous_definition[$was_table_name]['fields'];
  1528. }
  1529. $change = $this->compareTableFieldsDefinitions($table_name,
  1530. $current_definition['fields'],
  1531. $previous_fields);
  1532. if (PEAR::isError($change)) {
  1533. return $change;
  1534. }
  1535. if (!empty($change)) {
  1536. $changes['change'][$was_table_name] =
  1537. MDB2_Schema::arrayMergeClobber($changes['change'][$was_table_name], $change);
  1538. }
  1539. }
  1540. if (!empty($current_definition['indexes']) && is_array($current_definition['indexes'])) {
  1541. $previous_indexes = array();
  1542. if (isset($previous_definition[$was_table_name]['indexes'])
  1543. && is_array($previous_definition[$was_table_name]['indexes'])) {
  1544. $previous_indexes = $previous_definition[$was_table_name]['indexes'];
  1545. }
  1546. $change = $this->compareTableIndexesDefinitions($table_name,
  1547. $current_definition['indexes'],
  1548. $previous_indexes);
  1549. if (PEAR::isError($change)) {
  1550. return $change;
  1551. }
  1552. if (!empty($change)) {
  1553. $changes['change'][$was_table_name]['indexes'] = $change;
  1554. }
  1555. }
  1556. if (empty($changes['change'][$was_table_name])) {
  1557. unset($changes['change'][$was_table_name]);
  1558. }
  1559. if (empty($changes['change'])) {
  1560. unset($changes['change']);
  1561. }
  1562. } else {
  1563. if ($table_name != $was_table_name) {
  1564. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1565. 'it was specified a previous table name ("'.$was_table_name.
  1566. '") for table "'.$table_name.'" that does not exist');
  1567. }
  1568. $changes['add'][$table_name] = true;
  1569. }
  1570. }
  1571. return $changes;
  1572. }
  1573. // }}}
  1574. // {{{ compareSequenceDefinitions()
  1575. /**
  1576. * Compare a previous definition with the currently parsed definition
  1577. *
  1578. * @param string $sequence_name name of the sequence
  1579. * @param array $current_definition multi dimensional array that contains the current definition
  1580. * @param array $previous_definition multi dimensional array that contains the previous definition
  1581. * @param array &$defined_sequences names in the schema
  1582. *
  1583. * @return array|MDB2_Error array of changes on success, or a error object
  1584. * @access public
  1585. */
  1586. function compareSequenceDefinitions($sequence_name, $current_definition,
  1587. $previous_definition, &$defined_sequences)
  1588. {
  1589. $changes = array();
  1590. if (is_array($current_definition)) {
  1591. $was_sequence_name = $sequence_name;
  1592. if (!empty($previous_definition[$sequence_name])
  1593. && isset($previous_definition[$sequence_name]['was'])
  1594. && $previous_definition[$sequence_name]['was'] == $was_sequence_name
  1595. ) {
  1596. $was_sequence_name = $sequence_name;
  1597. } elseif (!empty($current_definition['was'])) {
  1598. $was_sequence_name = $current_definition['was'];
  1599. }
  1600. if (!empty($previous_definition[$was_sequence_name])) {
  1601. if ($was_sequence_name != $sequence_name) {
  1602. $changes['change'][$was_sequence_name]['name'] = $sequence_name;
  1603. }
  1604. if (!empty($defined_sequences[$was_sequence_name])) {
  1605. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1606. 'the sequence "'.$was_sequence_name.'" was specified as base'.
  1607. ' of more than of sequence of the database');
  1608. }
  1609. $defined_sequences[$was_sequence_name] = true;
  1610. $change = array();
  1611. if (!empty($current_definition['start'])
  1612. && isset($previous_definition[$was_sequence_name]['start'])
  1613. && $current_definition['start'] != $previous_definition[$was_sequence_name]['start']
  1614. ) {
  1615. $change['start'] = $previous_definition[$sequence_name]['start'];
  1616. }
  1617. if (isset($current_definition['on']['table'])
  1618. && isset($previous_definition[$was_sequence_name]['on']['table'])
  1619. && $current_definition['on']['table'] != $previous_definition[$was_sequence_name]['on']['table']
  1620. && isset($current_definition['on']['field'])
  1621. && isset($previous_definition[$was_sequence_name]['on']['field'])
  1622. && $current_definition['on']['field'] != $previous_definition[$was_sequence_name]['on']['field']
  1623. ) {
  1624. $change['on'] = $current_definition['on'];
  1625. }
  1626. if (!empty($change)) {
  1627. $changes['change'][$was_sequence_name][$sequence_name] = $change;
  1628. }
  1629. } else {
  1630. if ($sequence_name != $was_sequence_name) {
  1631. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1632. 'it was specified a previous sequence name ("'.$was_sequence_name.
  1633. '") for sequence "'.$sequence_name.'" that does not exist');
  1634. }
  1635. $changes['add'][$sequence_name] = true;
  1636. }
  1637. }
  1638. return $changes;
  1639. }
  1640. // }}}
  1641. // {{{ verifyAlterDatabase()
  1642. /**
  1643. * Verify that the changes requested are supported
  1644. *
  1645. * @param array $changes associative array that contains the definition of the changes
  1646. * that are meant to be applied to the database structure.
  1647. *
  1648. * @return bool|MDB2_Error MDB2_OK or error object
  1649. * @access public
  1650. */
  1651. function verifyAlterDatabase($changes)
  1652. {
  1653. if (!empty($changes['tables']['change']) && is_array($changes['tables']['change'])) {
  1654. foreach ($changes['tables']['change'] as $table_name => $table) {
  1655. if (!empty($table['indexes']) && is_array($table['indexes'])) {
  1656. if (!$this->db->supports('indexes')) {
  1657. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
  1658. 'indexes are not supported');
  1659. }
  1660. $table_changes = count($table['indexes']);
  1661. if (!empty($table['indexes']['add'])) {
  1662. $table_changes--;
  1663. }
  1664. if (!empty($table['indexes']['remove'])) {
  1665. $table_changes--;
  1666. }
  1667. if (!empty($table['indexes']['change'])) {
  1668. $table_changes--;
  1669. }
  1670. if ($table_changes) {
  1671. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
  1672. 'index alteration not yet supported: '.implode(', ', array_keys($table['indexes'])));
  1673. }
  1674. }
  1675. unset($table['indexes']);
  1676. $result = $this->db->manager->alterTable($table_name, $table, true);
  1677. if (PEAR::isError($result)) {
  1678. return $result;
  1679. }
  1680. }
  1681. }
  1682. if (!empty($changes['sequences']) && is_array($changes['sequences'])) {
  1683. if (!$this->db->supports('sequences')) {
  1684. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
  1685. 'sequences are not supported');
  1686. }
  1687. $sequence_changes = count($changes['sequences']);
  1688. if (!empty($changes['sequences']['add'])) {
  1689. $sequence_changes--;
  1690. }
  1691. if (!empty($changes['sequences']['remove'])) {
  1692. $sequence_changes--;
  1693. }
  1694. if (!empty($changes['sequences']['change'])) {
  1695. $sequence_changes--;
  1696. }
  1697. if ($sequence_changes) {
  1698. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
  1699. 'sequence alteration not yet supported: '.implode(', ', array_keys($changes['sequences'])));
  1700. }
  1701. }
  1702. return MDB2_OK;
  1703. }
  1704. // }}}
  1705. // {{{ alterDatabaseIndexes()
  1706. /**
  1707. * Execute the necessary actions to implement the requested changes
  1708. * in the indexes inside a database structure.
  1709. *
  1710. * @param string $table_name name of the table
  1711. * @param array $changes associative array that contains the definition of the changes
  1712. * that are meant to be applied to the database structure.
  1713. *
  1714. * @return bool|MDB2_Error MDB2_OK or error object
  1715. * @access public
  1716. */
  1717. function alterDatabaseIndexes($table_name, $changes)
  1718. {
  1719. $alterations = 0;
  1720. if (empty($changes)) {
  1721. return $alterations;
  1722. }
  1723. if (!empty($changes['remove']) && is_array($changes['remove'])) {
  1724. foreach ($changes['remove'] as $index_name => $index) {
  1725. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  1726. if (!empty($index['primary']) || !empty($index['unique'])) {
  1727. $result = $this->db->manager->dropConstraint($table_name, $index_name, !empty($index['primary']));
  1728. } else {
  1729. $result = $this->db->manager->dropIndex($table_name, $index_name);
  1730. }
  1731. $this->db->popExpect();
  1732. if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) {
  1733. return $result;
  1734. }
  1735. $alterations++;
  1736. }
  1737. }
  1738. if (!empty($changes['change']) && is_array($changes['change'])) {
  1739. foreach ($changes['change'] as $index_name => $index) {
  1740. /**
  1741. * Drop existing index/constraint first.
  1742. * Since $changes doesn't tell us whether it's an index or a constraint before the change,
  1743. * we have to find out and call the appropriate method.
  1744. */
  1745. if (in_array($index_name, $this->db->manager->listTableIndexes($table_name))) {
  1746. $result = $this->db->manager->dropIndex($table_name, $index_name);
  1747. } elseif (in_array($index_name, $this->db->manager->listTableConstraints($table_name))) {
  1748. $result = $this->db->manager->dropConstraint($table_name, $index_name);
  1749. }
  1750. if (!empty($result) && PEAR::isError($result)) {
  1751. return $result;
  1752. }
  1753. if (!empty($index['primary']) || !empty($index['unique'])) {
  1754. $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
  1755. } else {
  1756. $result = $this->db->manager->createIndex($table_name, $index_name, $index);
  1757. }
  1758. if (PEAR::isError($result)) {
  1759. return $result;
  1760. }
  1761. $alterations++;
  1762. }
  1763. }
  1764. if (!empty($changes['add']) && is_array($changes['add'])) {
  1765. foreach ($changes['add'] as $index_name => $index) {
  1766. if (!empty($index['primary']) || !empty($index['unique'])) {
  1767. $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
  1768. } else {
  1769. $result = $this->db->manager->createIndex($table_name, $index_name, $index);
  1770. }
  1771. if (PEAR::isError($result)) {
  1772. return $result;
  1773. }
  1774. $alterations++;
  1775. }
  1776. }
  1777. return $alterations;
  1778. }
  1779. // }}}
  1780. // {{{ alterDatabaseTables()
  1781. /**
  1782. * Execute the necessary actions to implement the requested changes
  1783. * in the tables inside a database structure.
  1784. *
  1785. * @param array $current_definition multi dimensional array that contains the current definition
  1786. * @param array $previous_definition multi dimensional array that contains the previous definition
  1787. * @param array $changes associative array that contains the definition of the changes
  1788. * that are meant to be applied to the database structure.
  1789. *
  1790. * @return bool|MDB2_Error MDB2_OK or error object
  1791. * @access public
  1792. */
  1793. function alterDatabaseTables($current_definition, $previous_definition, $changes)
  1794. {
  1795. /* FIXME: tables marked to be added are initialized by createTable(), others don't */
  1796. $alterations = 0;
  1797. if (empty($changes)) {
  1798. return $alterations;
  1799. }
  1800. if (!empty($changes['add']) && is_array($changes['add'])) {
  1801. foreach ($changes['add'] as $table_name => $table) {
  1802. $result = $this->createTable($table_name, $current_definition[$table_name]);
  1803. if (PEAR::isError($result)) {
  1804. return $result;
  1805. }
  1806. $alterations++;
  1807. }
  1808. }
  1809. if ($this->options['drop_missing_tables']
  1810. && !empty($changes['remove'])
  1811. && is_array($changes['remove'])) {
  1812. foreach ($changes['remove'] as $table_name => $table) {
  1813. $result = $this->db->manager->dropTable($table_name);
  1814. if (PEAR::isError($result)) {
  1815. return $result;
  1816. }
  1817. $alterations++;
  1818. }
  1819. }
  1820. if (!empty($changes['change']) && is_array($changes['change'])) {
  1821. foreach ($changes['change'] as $table_name => $table) {
  1822. $indexes = array();
  1823. if (!empty($table['indexes'])) {
  1824. $indexes = $table['indexes'];
  1825. unset($table['indexes']);
  1826. }
  1827. if (!empty($indexes['remove'])) {
  1828. $result = $this->alterDatabaseIndexes($table_name, array('remove' => $indexes['remove']));
  1829. if (PEAR::isError($result)) {
  1830. return $result;
  1831. }
  1832. unset($indexes['remove']);
  1833. $alterations += $result;
  1834. }
  1835. $result = $this->db->manager->alterTable($table_name, $table, false);
  1836. if (PEAR::isError($result)) {
  1837. return $result;
  1838. }
  1839. $alterations++;
  1840. // table may be renamed at this point
  1841. if (!empty($table['name'])) {
  1842. $table_name = $table['name'];
  1843. }
  1844. if (!empty($indexes)) {
  1845. $result = $this->alterDatabaseIndexes($table_name, $indexes);
  1846. if (PEAR::isError($result)) {
  1847. return $result;
  1848. }
  1849. $alterations += $result;
  1850. }
  1851. }
  1852. }
  1853. return $alterations;
  1854. }
  1855. // }}}
  1856. // {{{ alterDatabaseSequences()
  1857. /**
  1858. * Execute the necessary actions to implement the requested changes
  1859. * in the sequences inside a database structure.
  1860. *
  1861. * @param array $current_definition multi dimensional array that contains the current definition
  1862. * @param array $previous_definition multi dimensional array that contains the previous definition
  1863. * @param array $changes associative array that contains the definition of the changes
  1864. * that are meant to be applied to the database structure.
  1865. *
  1866. * @return bool|MDB2_Error MDB2_OK or error object
  1867. * @access public
  1868. */
  1869. function alterDatabaseSequences($current_definition, $previous_definition, $changes)
  1870. {
  1871. $alterations = 0;
  1872. if (empty($changes)) {
  1873. return $alterations;
  1874. }
  1875. if (!empty($changes['add']) && is_array($changes['add'])) {
  1876. foreach ($changes['add'] as $sequence_name => $sequence) {
  1877. $result = $this->createSequence($sequence_name, $current_definition[$sequence_name]);
  1878. if (PEAR::isError($result)) {
  1879. return $result;
  1880. }
  1881. $alterations++;
  1882. }
  1883. }
  1884. if (!empty($changes['remove']) && is_array($changes['remove'])) {
  1885. foreach ($changes['remove'] as $sequence_name => $sequence) {
  1886. $result = $this->db->manager->dropSequence($sequence_name);
  1887. if (PEAR::isError($result)) {
  1888. return $result;
  1889. }
  1890. $alterations++;
  1891. }
  1892. }
  1893. if (!empty($changes['change']) && is_array($changes['change'])) {
  1894. foreach ($changes['change'] as $sequence_name => $sequence) {
  1895. $result = $this->db->manager->dropSequence($previous_definition[$sequence_name]['was']);
  1896. if (PEAR::isError($result)) {
  1897. return $result;
  1898. }
  1899. $result = $this->createSequence($sequence_name, $sequence);
  1900. if (PEAR::isError($result)) {
  1901. return $result;
  1902. }
  1903. $alterations++;
  1904. }
  1905. }
  1906. return $alterations;
  1907. }
  1908. // }}}
  1909. // {{{ alterDatabase()
  1910. /**
  1911. * Execute the necessary actions to implement the requested changes
  1912. * in a database structure.
  1913. *
  1914. * @param array $current_definition multi dimensional array that contains the current definition
  1915. * @param array $previous_definition multi dimensional array that contains the previous definition
  1916. * @param array $changes associative array that contains the definition of the changes
  1917. * that are meant to be applied to the database structure.
  1918. *
  1919. * @return bool|MDB2_Error MDB2_OK or error object
  1920. * @access public
  1921. */
  1922. function alterDatabase($current_definition, $previous_definition, $changes)
  1923. {
  1924. $alterations = 0;
  1925. if (empty($changes)) {
  1926. return $alterations;
  1927. }
  1928. $result = $this->verifyAlterDatabase($changes);
  1929. if (PEAR::isError($result)) {
  1930. return $result;
  1931. }
  1932. if (!empty($current_definition['name'])) {
  1933. $previous_database_name = $this->db->setDatabase($current_definition['name']);
  1934. }
  1935. if (($support_transactions = $this->db->supports('transactions'))
  1936. && PEAR::isError($result = $this->db->beginNestedTransaction())
  1937. ) {
  1938. return $result;
  1939. }
  1940. if (!empty($changes['tables']) && !empty($current_definition['tables'])) {
  1941. $current_tables = isset($current_definition['tables']) ? $current_definition['tables'] : array();
  1942. $previous_tables = isset($previous_definition['tables']) ? $previous_definition['tables'] : array();
  1943. $result = $this->alterDatabaseTables($current_tables, $previous_tables, $changes['tables']);
  1944. if (is_numeric($result)) {
  1945. $alterations += $result;
  1946. }
  1947. }
  1948. if (!PEAR::isError($result) && !empty($changes['sequences'])) {
  1949. $current_sequences = isset($current_definition['sequences']) ? $current_definition['sequences'] : array();
  1950. $previous_sequences = isset($previous_definition['sequences']) ? $previous_definition['sequences'] : array();
  1951. $result = $this->alterDatabaseSequences($current_sequences, $previous_sequences, $changes['sequences']);
  1952. if (is_numeric($result)) {
  1953. $alterations += $result;
  1954. }
  1955. }
  1956. if ($support_transactions) {
  1957. $res = $this->db->completeNestedTransaction();
  1958. if (PEAR::isError($res)) {
  1959. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  1960. 'Could not end transaction ('.
  1961. $res->getMessage().' ('.$res->getUserinfo().'))');
  1962. }
  1963. } elseif (PEAR::isError($result) && $alterations) {
  1964. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  1965. 'the requested database alterations were only partially implemented ('.
  1966. $result->getMessage().' ('.$result->getUserinfo().'))');
  1967. }
  1968. if (isset($previous_database_name)) {
  1969. $this->db->setDatabase($previous_database_name);
  1970. }
  1971. return $result;
  1972. }
  1973. // }}}
  1974. // {{{ dumpDatabaseChanges()
  1975. /**
  1976. * Dump the changes between two database definitions.
  1977. *
  1978. * @param array $changes associative array that specifies the list of database
  1979. * definitions changes as returned by the _compareDefinitions
  1980. * manager class function.
  1981. *
  1982. * @return bool|MDB2_Error MDB2_OK or error object
  1983. * @access public
  1984. */
  1985. function dumpDatabaseChanges($changes)
  1986. {
  1987. if (!empty($changes['tables'])) {
  1988. if (!empty($changes['tables']['add']) && is_array($changes['tables']['add'])) {
  1989. foreach ($changes['tables']['add'] as $table_name => $table) {
  1990. $this->db->debug("$table_name:", __FUNCTION__);
  1991. $this->db->debug("\tAdded table '$table_name'", __FUNCTION__);
  1992. }
  1993. }
  1994. if (!empty($changes['tables']['remove']) && is_array($changes['tables']['remove'])) {
  1995. if ($this->options['drop_missing_tables']) {
  1996. foreach ($changes['tables']['remove'] as $table_name => $table) {
  1997. $this->db->debug("$table_name:", __FUNCTION__);
  1998. $this->db->debug("\tRemoved table '$table_name'", __FUNCTION__);
  1999. }
  2000. } else {
  2001. foreach ($changes['tables']['remove'] as $table_name => $table) {
  2002. $this->db->debug("\tObsolete table '$table_name' left as is", __FUNCTION__);
  2003. }
  2004. }
  2005. }
  2006. if (!empty($changes['tables']['change']) && is_array($changes['tables']['change'])) {
  2007. foreach ($changes['tables']['change'] as $table_name => $table) {
  2008. if (array_key_exists('name', $table)) {
  2009. $this->db->debug("\tRenamed table '$table_name' to '".$table['name']."'", __FUNCTION__);
  2010. }
  2011. if (!empty($table['add']) && is_array($table['add'])) {
  2012. foreach ($table['add'] as $field_name => $field) {
  2013. $this->db->debug("\tAdded field '".$field_name."'", __FUNCTION__);
  2014. }
  2015. }
  2016. if (!empty($table['remove']) && is_array($table['remove'])) {
  2017. foreach ($table['remove'] as $field_name => $field) {
  2018. $this->db->debug("\tRemoved field '".$field_name."'", __FUNCTION__);
  2019. }
  2020. }
  2021. if (!empty($table['rename']) && is_array($table['rename'])) {
  2022. foreach ($table['rename'] as $field_name => $field) {
  2023. $this->db->debug("\tRenamed field '".$field_name."' to '".$field['name']."'", __FUNCTION__);
  2024. }
  2025. }
  2026. if (!empty($table['change']) && is_array($table['change'])) {
  2027. foreach ($table['change'] as $field_name => $field) {
  2028. $field = $field['definition'];
  2029. if (array_key_exists('type', $field)) {
  2030. $this->db->debug("\tChanged field '$field_name' type to '".$field['type']."'", __FUNCTION__);
  2031. }
  2032. if (array_key_exists('unsigned', $field)) {
  2033. $this->db->debug("\tChanged field '$field_name' type to '".
  2034. (!empty($field['unsigned']) && $field['unsigned'] ? '' : 'not ')."unsigned'",
  2035. __FUNCTION__);
  2036. }
  2037. if (array_key_exists('length', $field)) {
  2038. $this->db->debug("\tChanged field '$field_name' length to '".
  2039. (!empty($field['length']) ? $field['length']: 'no length')."'", __FUNCTION__);
  2040. }
  2041. if (array_key_exists('default', $field)) {
  2042. $this->db->debug("\tChanged field '$field_name' default to ".
  2043. (isset($field['default']) ? "'".$field['default']."'" : 'NULL'), __FUNCTION__);
  2044. }
  2045. if (array_key_exists('notnull', $field)) {
  2046. $this->db->debug("\tChanged field '$field_name' notnull to ".
  2047. (!empty($field['notnull']) && $field['notnull'] ? 'true' : 'false'),
  2048. __FUNCTION__);
  2049. }
  2050. }
  2051. }
  2052. if (!empty($table['indexes']) && is_array($table['indexes'])) {
  2053. if (!empty($table['indexes']['add']) && is_array($table['indexes']['add'])) {
  2054. foreach ($table['indexes']['add'] as $index_name => $index) {
  2055. $this->db->debug("\tAdded index '".$index_name.
  2056. "' of table '$table_name'", __FUNCTION__);
  2057. }
  2058. }
  2059. if (!empty($table['indexes']['remove']) && is_array($table['indexes']['remove'])) {
  2060. foreach ($table['indexes']['remove'] as $index_name => $index) {
  2061. $this->db->debug("\tRemoved index '".$index_name.
  2062. "' of table '$table_name'", __FUNCTION__);
  2063. }
  2064. }
  2065. if (!empty($table['indexes']['change']) && is_array($table['indexes']['change'])) {
  2066. foreach ($table['indexes']['change'] as $index_name => $index) {
  2067. if (array_key_exists('name', $index)) {
  2068. $this->db->debug("\tRenamed index '".$index_name."' to '".$index['name'].
  2069. "' on table '$table_name'", __FUNCTION__);
  2070. }
  2071. if (array_key_exists('unique', $index)) {
  2072. $this->db->debug("\tChanged index '".$index_name."' unique to '".
  2073. !empty($index['unique'])."' on table '$table_name'", __FUNCTION__);
  2074. }
  2075. if (array_key_exists('primary', $index)) {
  2076. $this->db->debug("\tChanged index '".$index_name."' primary to '".
  2077. !empty($index['primary'])."' on table '$table_name'", __FUNCTION__);
  2078. }
  2079. if (array_key_exists('change', $index)) {
  2080. $this->db->debug("\tChanged index '".$index_name.
  2081. "' on table '$table_name'", __FUNCTION__);
  2082. }
  2083. }
  2084. }
  2085. }
  2086. }
  2087. }
  2088. }
  2089. if (!empty($changes['sequences'])) {
  2090. if (!empty($changes['sequences']['add']) && is_array($changes['sequences']['add'])) {
  2091. foreach ($changes['sequences']['add'] as $sequence_name => $sequence) {
  2092. $this->db->debug("$sequence_name:", __FUNCTION__);
  2093. $this->db->debug("\tAdded sequence '$sequence_name'", __FUNCTION__);
  2094. }
  2095. }
  2096. if (!empty($changes['sequences']['remove']) && is_array($changes['sequences']['remove'])) {
  2097. foreach ($changes['sequences']['remove'] as $sequence_name => $sequence) {
  2098. $this->db->debug("$sequence_name:", __FUNCTION__);
  2099. $this->db->debug("\tAdded sequence '$sequence_name'", __FUNCTION__);
  2100. }
  2101. }
  2102. if (!empty($changes['sequences']['change']) && is_array($changes['sequences']['change'])) {
  2103. foreach ($changes['sequences']['change'] as $sequence_name => $sequence) {
  2104. if (array_key_exists('name', $sequence)) {
  2105. $this->db->debug("\tRenamed sequence '$sequence_name' to '".
  2106. $sequence['name']."'", __FUNCTION__);
  2107. }
  2108. if (!empty($sequence['change']) && is_array($sequence['change'])) {
  2109. foreach ($sequence['change'] as $sequence_name => $sequence) {
  2110. if (array_key_exists('start', $sequence)) {
  2111. $this->db->debug("\tChanged sequence '$sequence_name' start to '".
  2112. $sequence['start']."'", __FUNCTION__);
  2113. }
  2114. }
  2115. }
  2116. }
  2117. }
  2118. }
  2119. return MDB2_OK;
  2120. }
  2121. // }}}
  2122. // {{{ dumpDatabase()
  2123. /**
  2124. * Dump a previously parsed database structure in the Metabase schema
  2125. * XML based format suitable for the Metabase parser. This function
  2126. * may optionally dump the database definition with initialization
  2127. * commands that specify the data that is currently present in the tables.
  2128. *
  2129. * @param array $database_definition multi dimensional array that contains the current definition
  2130. * @param array $arguments associative array that takes pairs of tag
  2131. * names and values that define dump options.
  2132. * <pre>array (
  2133. * 'output_mode' => String
  2134. * 'file' : dump into a file
  2135. * default: dump using a function
  2136. * 'output' => String
  2137. * depending on the 'Output_Mode'
  2138. * name of the file
  2139. * name of the function
  2140. * 'end_of_line' => String
  2141. * end of line delimiter that should be used
  2142. * default: "\n"
  2143. * );</pre>
  2144. * @param int $dump Int that determines what data to dump
  2145. * + MDB2_SCHEMA_DUMP_ALL : the entire db
  2146. * + MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db
  2147. * + MDB2_SCHEMA_DUMP_CONTENT : only the content of the db
  2148. *
  2149. * @return bool|MDB2_Error MDB2_OK or error object
  2150. * @access public
  2151. */
  2152. function dumpDatabase($database_definition, $arguments, $dump = MDB2_SCHEMA_DUMP_ALL)
  2153. {
  2154. $class_name = $this->options['writer'];
  2155. $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
  2156. if (PEAR::isError($result)) {
  2157. return $result;
  2158. }
  2159. // get initialization data
  2160. if (isset($database_definition['tables']) && is_array($database_definition['tables'])
  2161. && $dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT
  2162. ) {
  2163. foreach ($database_definition['tables'] as $table_name => $table) {
  2164. $fields = array();
  2165. $fieldsq = array();
  2166. foreach ($table['fields'] as $field_name => $field) {
  2167. $fields[$field_name] = $field['type'];
  2168. $fieldsq[] = $this->db->quoteIdentifier($field_name, true);
  2169. }
  2170. $query = 'SELECT '.implode(', ', $fieldsq).' FROM ';
  2171. $query .= $this->db->quoteIdentifier($table_name, true);
  2172. $data = $this->db->queryAll($query, $fields, MDB2_FETCHMODE_ASSOC);
  2173. if (PEAR::isError($data)) {
  2174. return $data;
  2175. }
  2176. if (!empty($data)) {
  2177. $initialization = array();
  2178. $lob_buffer_length = $this->db->getOption('lob_buffer_length');
  2179. foreach ($data as $row) {
  2180. $rows = array();
  2181. foreach ($row as $key => $lob) {
  2182. if (is_resource($lob)) {
  2183. $value = '';
  2184. while (!feof($lob)) {
  2185. $value .= fread($lob, $lob_buffer_length);
  2186. }
  2187. $row[$key] = $value;
  2188. }
  2189. $rows[] = array('name' => $key, 'group' => array('type' => 'value', 'data' => $row[$key]));
  2190. }
  2191. $initialization[] = array('type' => 'insert', 'data' => array('field' => $rows));
  2192. }
  2193. $database_definition['tables'][$table_name]['initialization'] = $initialization;
  2194. }
  2195. }
  2196. }
  2197. $writer =& new $class_name($this->options['valid_types']);
  2198. return $writer->dumpDatabase($database_definition, $arguments, $dump);
  2199. }
  2200. // }}}
  2201. // {{{ writeInitialization()
  2202. /**
  2203. * Write initialization and sequences
  2204. *
  2205. * @param string|array $data data file or data array
  2206. * @param string|array $structure structure file or array
  2207. * @param array $variables associative array that is passed to the argument
  2208. * of the same name to the parseDatabaseDefinitionFile function. (there third
  2209. * param)
  2210. *
  2211. * @return bool|MDB2_Error MDB2_OK or error object
  2212. * @access public
  2213. */
  2214. function writeInitialization($data, $structure = false, $variables = array())
  2215. {
  2216. if ($structure) {
  2217. $structure = $this->parseDatabaseDefinition($structure, false, $variables);
  2218. if (PEAR::isError($structure)) {
  2219. return $structure;
  2220. }
  2221. }
  2222. $data = $this->parseDatabaseDefinition($data, false, $variables, false, $structure);
  2223. if (PEAR::isError($data)) {
  2224. return $data;
  2225. }
  2226. $previous_database_name = null;
  2227. if (!empty($data['name'])) {
  2228. $previous_database_name = $this->db->setDatabase($data['name']);
  2229. } elseif (!empty($structure['name'])) {
  2230. $previous_database_name = $this->db->setDatabase($structure['name']);
  2231. }
  2232. if (!empty($data['tables']) && is_array($data['tables'])) {
  2233. foreach ($data['tables'] as $table_name => $table) {
  2234. if (empty($table['initialization'])) {
  2235. continue;
  2236. }
  2237. $result = $this->initializeTable($table_name, $table);
  2238. if (PEAR::isError($result)) {
  2239. return $result;
  2240. }
  2241. }
  2242. }
  2243. if (!empty($structure['sequences']) && is_array($structure['sequences'])) {
  2244. foreach ($structure['sequences'] as $sequence_name => $sequence) {
  2245. if (isset($data['sequences'][$sequence_name])
  2246. || !isset($sequence['on']['table'])
  2247. || !isset($data['tables'][$sequence['on']['table']])
  2248. ) {
  2249. continue;
  2250. }
  2251. $result = $this->createSequence($sequence_name, $sequence, true);
  2252. if (PEAR::isError($result)) {
  2253. return $result;
  2254. }
  2255. }
  2256. }
  2257. if (!empty($data['sequences']) && is_array($data['sequences'])) {
  2258. foreach ($data['sequences'] as $sequence_name => $sequence) {
  2259. $result = $this->createSequence($sequence_name, $sequence, true);
  2260. if (PEAR::isError($result)) {
  2261. return $result;
  2262. }
  2263. }
  2264. }
  2265. if (isset($previous_database_name)) {
  2266. $this->db->setDatabase($previous_database_name);
  2267. }
  2268. return MDB2_OK;
  2269. }
  2270. // }}}
  2271. // {{{ updateDatabase()
  2272. /**
  2273. * Compare the correspondent files of two versions of a database schema
  2274. * definition: the previously installed and the one that defines the schema
  2275. * that is meant to update the database.
  2276. * If the specified previous definition file does not exist, this function
  2277. * will create the database from the definition specified in the current
  2278. * schema file.
  2279. * If both files exist, the function assumes that the database was previously
  2280. * installed based on the previous schema file and will update it by just
  2281. * applying the changes.
  2282. * If this function succeeds, the contents of the current schema file are
  2283. * copied to replace the previous schema file contents. Any subsequent schema
  2284. * changes should only be done on the file specified by the $current_schema_file
  2285. * to let this function make a consistent evaluation of the exact changes that
  2286. * need to be applied.
  2287. *
  2288. * @param string|array $current_schema filename or array of the updated database schema definition.
  2289. * @param string|array $previous_schema filename or array of the previously installed database schema definition.
  2290. * @param array $variables associative array that is passed to the argument of the same
  2291. * name to the parseDatabaseDefinitionFile function. (there third param)
  2292. * @param bool $disable_query determines if the disable_query option should be set to true
  2293. * for the alterDatabase() or createDatabase() call
  2294. * @param bool $overwrite_old_schema_file Overwrite?
  2295. *
  2296. * @return bool|MDB2_Error MDB2_OK or error object
  2297. * @access public
  2298. */
  2299. function updateDatabase($current_schema, $previous_schema = false,
  2300. $variables = array(), $disable_query = false,
  2301. $overwrite_old_schema_file = false)
  2302. {
  2303. $current_definition = $this->parseDatabaseDefinition($current_schema, false, $variables,
  2304. $this->options['fail_on_invalid_names']);
  2305. if (PEAR::isError($current_definition)) {
  2306. return $current_definition;
  2307. }
  2308. $previous_definition = false;
  2309. if ($previous_schema) {
  2310. $previous_definition = $this->parseDatabaseDefinition($previous_schema, true, $variables,
  2311. $this->options['fail_on_invalid_names']);
  2312. if (PEAR::isError($previous_definition)) {
  2313. return $previous_definition;
  2314. }
  2315. }
  2316. if ($previous_definition) {
  2317. $dbExists = $this->db->databaseExists($current_definition['name']);
  2318. if (PEAR::isError($dbExists)) {
  2319. return $dbExists;
  2320. }
  2321. if (!$dbExists) {
  2322. return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  2323. 'database to update does not exist: '.$current_definition['name']);
  2324. }
  2325. $changes = $this->compareDefinitions($current_definition, $previous_definition);
  2326. if (PEAR::isError($changes)) {
  2327. return $changes;
  2328. }
  2329. if (is_array($changes)) {
  2330. $this->db->setOption('disable_query', $disable_query);
  2331. $result = $this->alterDatabase($current_definition, $previous_definition, $changes);
  2332. $this->db->setOption('disable_query', false);
  2333. if (PEAR::isError($result)) {
  2334. return $result;
  2335. }
  2336. $copy = true;
  2337. if ($this->db->options['debug']) {
  2338. $result = $this->dumpDatabaseChanges($changes);
  2339. if (PEAR::isError($result)) {
  2340. return $result;
  2341. }
  2342. }
  2343. }
  2344. } else {
  2345. $this->db->setOption('disable_query', $disable_query);
  2346. $result = $this->createDatabase($current_definition);
  2347. $this->db->setOption('disable_query', false);
  2348. if (PEAR::isError($result)) {
  2349. return $result;
  2350. }
  2351. }
  2352. if ($overwrite_old_schema_file
  2353. && !$disable_query
  2354. && is_string($previous_schema) && is_string($current_schema)
  2355. && !copy($current_schema, $previous_schema)) {
  2356. return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  2357. 'Could not copy the new database definition file to the current file');
  2358. }
  2359. return MDB2_OK;
  2360. }
  2361. // }}}
  2362. // {{{ errorMessage()
  2363. /**
  2364. * Return a textual error message for a MDB2 error code
  2365. *
  2366. * @param int|array $value integer error code, <code>null</code> to get the
  2367. * current error code-message map,
  2368. * or an array with a new error code-message map
  2369. *
  2370. * @return string error message, or false if the error code was not recognized
  2371. * @access public
  2372. */
  2373. function errorMessage($value = null)
  2374. {
  2375. static $errorMessages;
  2376. if (is_array($value)) {
  2377. $errorMessages = $value;
  2378. return MDB2_OK;
  2379. } elseif (!isset($errorMessages)) {
  2380. $errorMessages = array(
  2381. MDB2_SCHEMA_ERROR => 'unknown error',
  2382. MDB2_SCHEMA_ERROR_PARSE => 'schema parse error',
  2383. MDB2_SCHEMA_ERROR_VALIDATE => 'schema validation error',
  2384. MDB2_SCHEMA_ERROR_INVALID => 'invalid',
  2385. MDB2_SCHEMA_ERROR_UNSUPPORTED => 'not supported',
  2386. MDB2_SCHEMA_ERROR_WRITER => 'schema writer error',
  2387. );
  2388. }
  2389. if (is_null($value)) {
  2390. return $errorMessages;
  2391. }
  2392. if (PEAR::isError($value)) {
  2393. $value = $value->getCode();
  2394. }
  2395. return !empty($errorMessages[$value]) ?
  2396. $errorMessages[$value] : $errorMessages[MDB2_SCHEMA_ERROR];
  2397. }
  2398. // }}}
  2399. // {{{ raiseError()
  2400. /**
  2401. * This method is used to communicate an error and invoke error
  2402. * callbacks etc. Basically a wrapper for PEAR::raiseError
  2403. * without the message string.
  2404. *
  2405. * @param int|PEAR_Error $code integer error code or and PEAR_Error instance
  2406. * @param int $mode error mode, see PEAR_Error docs
  2407. * error level (E_USER_NOTICE etc). If error mode is
  2408. * PEAR_ERROR_CALLBACK, this is the callback function,
  2409. * either as a function name, or as an array of an
  2410. * object and method name. For other error modes this
  2411. * parameter is ignored.
  2412. * @param array $options Options, depending on the mode, @see PEAR::setErrorHandling
  2413. * @param string $userinfo Extra debug information. Defaults to the last
  2414. * query and native error code.
  2415. *
  2416. * @return object a PEAR error object
  2417. * @access public
  2418. * @see PEAR_Error
  2419. */
  2420. function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
  2421. {
  2422. $err =& PEAR::raiseError(null, $code, $mode, $options,
  2423. $userinfo, 'MDB2_Schema_Error', true);
  2424. return $err;
  2425. }
  2426. // }}}
  2427. // {{{ isError()
  2428. /**
  2429. * Tell whether a value is an MDB2_Schema error.
  2430. *
  2431. * @param mixed $data the value to test
  2432. * @param int $code if $data is an error object, return true only if $code is
  2433. * a string and $db->getMessage() == $code or
  2434. * $code is an integer and $db->getCode() == $code
  2435. *
  2436. * @return bool true if parameter is an error
  2437. * @access public
  2438. */
  2439. function isError($data, $code = null)
  2440. {
  2441. if (is_a($data, 'MDB2_Schema_Error')) {
  2442. if (is_null($code)) {
  2443. return true;
  2444. } elseif (is_string($code)) {
  2445. return $data->getMessage() === $code;
  2446. } else {
  2447. $code = (array)$code;
  2448. return in_array($data->getCode(), $code);
  2449. }
  2450. }
  2451. return false;
  2452. }
  2453. // }}}
  2454. }
  2455. /**
  2456. * MDB2_Schema_Error implements a class for reporting portable database error
  2457. * messages.
  2458. *
  2459. * @category Database
  2460. * @package MDB2_Schema
  2461. * @author Stig Bakken <ssb@fast.no>
  2462. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  2463. * @link http://pear.php.net/packages/MDB2_Schema
  2464. */
  2465. class MDB2_Schema_Error extends PEAR_Error
  2466. {
  2467. /**
  2468. * MDB2_Schema_Error constructor.
  2469. *
  2470. * @param mixed $code error code, or string with error message.
  2471. * @param int $mode what 'error mode' to operate in
  2472. * @param int $level what error level to use for $mode & PEAR_ERROR_TRIGGER
  2473. * @param mixed $debuginfo additional debug info, such as the last query
  2474. *
  2475. * @access public
  2476. */
  2477. function MDB2_Schema_Error($code = MDB2_SCHEMA_ERROR, $mode = PEAR_ERROR_RETURN,
  2478. $level = E_USER_NOTICE, $debuginfo = null)
  2479. {
  2480. $this->PEAR_Error('MDB2_Schema Error: ' . MDB2_Schema::errorMessage($code), $code,
  2481. $mode, $level, $debuginfo);
  2482. }
  2483. }
  2484. ?>