PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/MDB2/Schema.php

https://github.com/chregu/fluxcms
PHP | 2152 lines | 1495 code | 152 blank | 505 comment | 408 complexity | 38ce94d8f46ae98d2ee5e0cd61af9058 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP versions 4 and 5 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox, |
  6. // | Stig. S. Bakken, Lukas Smith |
  7. // | All rights reserved. |
  8. // +----------------------------------------------------------------------+
  9. // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB |
  10. // | API as well as database abstraction for PHP applications. |
  11. // | This LICENSE is in the BSD license style. |
  12. // | |
  13. // | Redistribution and use in source and binary forms, with or without |
  14. // | modification, are permitted provided that the following conditions |
  15. // | are met: |
  16. // | |
  17. // | Redistributions of source code must retain the above copyright |
  18. // | notice, this list of conditions and the following disclaimer. |
  19. // | |
  20. // | Redistributions in binary form must reproduce the above copyright |
  21. // | notice, this list of conditions and the following disclaimer in the |
  22. // | documentation and/or other materials provided with the distribution. |
  23. // | |
  24. // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, |
  25. // | Lukas Smith nor the names of his contributors may be used to endorse |
  26. // | or promote products derived from this software without specific prior|
  27. // | written permission. |
  28. // | |
  29. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  30. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  31. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
  32. // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
  33. // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
  34. // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
  35. // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
  36. // | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
  37. // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  38. // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
  39. // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
  40. // | POSSIBILITY OF SUCH DAMAGE. |
  41. // +----------------------------------------------------------------------+
  42. // | Author: Lukas Smith <smith@pooteeweet.org> |
  43. // +----------------------------------------------------------------------+
  44. //
  45. // $Id: Schema.php,v 1.54 2006/01/13 09:58:37 lsmith Exp $
  46. //
  47. require_once 'MDB2.php';
  48. define('MDB2_SCHEMA_DUMP_ALL', 0);
  49. define('MDB2_SCHEMA_DUMP_STRUCTURE', 1);
  50. define('MDB2_SCHEMA_DUMP_CONTENT', 2);
  51. /**
  52. * The method mapErrorCode in each MDB2_Schema_dbtype implementation maps
  53. * native error codes to one of these.
  54. *
  55. * If you add an error code here, make sure you also add a textual
  56. * version of it in MDB2_Schema::errorMessage().
  57. */
  58. define('MDB2_SCHEMA_ERROR', -1);
  59. define('MDB2_SCHEMA_ERROR_PARSE', -2);
  60. define('MDB2_SCHEMA_ERROR_NOT_CAPABLE', -3);
  61. define('MDB2_SCHEMA_ERROR_UNSUPPORTED', -4); // Driver does not support this function
  62. define('MDB2_SCHEMA_ERROR_INVALID', -5); // Invalid attribute value
  63. define('MDB2_SCHEMA_ERROR_NODBSELECTED', -6);
  64. /**
  65. * The database manager is a class that provides a set of database
  66. * management services like installing, altering and dumping the data
  67. * structures of databases.
  68. *
  69. * @package MDB2_Schema
  70. * @category Database
  71. * @author Lukas Smith <smith@pooteeweet.org>
  72. */
  73. class MDB2_Schema extends PEAR
  74. {
  75. // {{{ properties
  76. var $db;
  77. var $warnings = array();
  78. var $options = array(
  79. 'fail_on_invalid_names' => true,
  80. 'dtd_file' => false,
  81. );
  82. var $database_definition = array(
  83. 'name' => '',
  84. 'create' => false,
  85. 'tables' => array()
  86. );
  87. // }}}
  88. // {{{ apiVersion()
  89. /**
  90. * Return the MDB2 API version
  91. *
  92. * @return string the MDB2 API version number
  93. * @access public
  94. */
  95. function apiVersion()
  96. {
  97. return '0.4.0';
  98. }
  99. // }}}
  100. // {{{ resetWarnings()
  101. /**
  102. * reset the warning array
  103. *
  104. * @access public
  105. */
  106. function resetWarnings()
  107. {
  108. $this->warnings = array();
  109. }
  110. // }}}
  111. // {{{ getWarnings()
  112. /**
  113. * get all warnings in reverse order.
  114. * This means that the last warning is the first element in the array
  115. *
  116. * @return array with warnings
  117. * @access public
  118. * @see resetWarnings()
  119. */
  120. function getWarnings()
  121. {
  122. return array_reverse($this->warnings);
  123. }
  124. // }}}
  125. // {{{ setOption()
  126. /**
  127. * set the option for the db class
  128. *
  129. * @param string $option option name
  130. * @param mixed $value value for the option
  131. * @return mixed MDB2_OK or MDB2 Error Object
  132. * @access public
  133. */
  134. function setOption($option, $value)
  135. {
  136. if (isset($this->options[$option])) {
  137. if (is_null($value)) {
  138. return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  139. 'may not set an option to value null');
  140. }
  141. $this->options[$option] = $value;
  142. return MDB2_OK;
  143. }
  144. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
  145. "unknown option $option");
  146. }
  147. // }}}
  148. // {{{ getOption()
  149. /**
  150. * returns the value of an option
  151. *
  152. * @param string $option option name
  153. * @return mixed the option value or error object
  154. * @access public
  155. */
  156. function getOption($option)
  157. {
  158. if (isset($this->options[$option])) {
  159. return $this->options[$option];
  160. }
  161. return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED,
  162. null, null, "unknown option $option");
  163. }
  164. // }}}
  165. // {{{ factory()
  166. /**
  167. * Create a new MDB2 object for the specified database type
  168. * type
  169. *
  170. * @param mixed $db 'data source name', see the MDB2::parseDSN
  171. * method for a description of the dsn format.
  172. * Can also be specified as an array of the
  173. * format returned by MDB2::parseDSN.
  174. * Finally you can also pass an existing db
  175. * object to be used.
  176. * @param mixed $options An associative array of option names and
  177. * their values.
  178. * @return mixed MDB2_OK on success, or a MDB2 error object
  179. * @access public
  180. * @see MDB2::parseDSN
  181. */
  182. function &factory(&$db, $options = array())
  183. {
  184. $obj =& new MDB2_Schema();
  185. $err = $obj->connect($db, $options);
  186. if (PEAR::isError($err)) {
  187. return $err;
  188. }
  189. return $obj;
  190. }
  191. // }}}
  192. // {{{ connect()
  193. /**
  194. * Create a new MDB2 connection object and connect to the specified
  195. * database
  196. *
  197. * @param mixed $db 'data source name', see the MDB2::parseDSN
  198. * method for a description of the dsn format.
  199. * Can also be specified as an array of the
  200. * format returned by MDB2::parseDSN.
  201. * Finally you can also pass an existing db
  202. * object to be used.
  203. * @param mixed $options An associative array of option names and
  204. * their values.
  205. * @return mixed MDB2_OK on success, or a MDB2 error object
  206. * @access public
  207. * @see MDB2::parseDSN
  208. */
  209. function connect(&$db, $options = array())
  210. {
  211. $db_options = array();
  212. if (is_array($options) && !empty($options)) {
  213. foreach ($options as $option => $value) {
  214. if (array_key_exists($option, $this->options)) {
  215. $err = $this->setOption($option, $value);
  216. if (PEAR::isError($err)) {
  217. return $err;
  218. }
  219. } else {
  220. $db_options[$option] = $value;
  221. }
  222. }
  223. }
  224. $this->disconnect();
  225. if (!MDB2::isConnection($db)) {
  226. $db =& MDB2::factory($db, $db_options);
  227. }
  228. if (PEAR::isError($db)) {
  229. return $db;
  230. }
  231. $this->db =& $db;
  232. $this->db->loadModule('Manager');
  233. $this->db->loadModule('Reverse');
  234. return MDB2_OK;
  235. }
  236. // }}}
  237. // {{{ disconnect()
  238. /**
  239. * Log out and disconnect from the database.
  240. *
  241. * @access public
  242. */
  243. function disconnect()
  244. {
  245. if (MDB2::isConnection($this->db)) {
  246. $this->db->disconnect();
  247. unset($this->db);
  248. }
  249. }
  250. // }}}
  251. // {{{ parseDatabaseDefinitionFile()
  252. /**
  253. * Parse a database definition file by creating a Metabase schema format
  254. * parser object and passing the file contents as parser input data stream.
  255. *
  256. * @param string $input_file the path of the database schema file.
  257. * @param array $variables an associative array that the defines the text
  258. * string values that are meant to be used to replace the variables that are
  259. * used in the schema description.
  260. * @param bool $fail_on_invalid_names (optional) make function fail on invalid
  261. * names
  262. * @return mixed MDB2_OK on success, or a MDB2 error object
  263. * @access public
  264. */
  265. function parseDatabaseDefinitionFile($input_file, $variables = array(),
  266. $fail_on_invalid_names = true, $structure = false)
  267. {
  268. $dtd_file = $this->getOption('dtd_file');
  269. if ($dtd_file) {
  270. require_once 'XML/DTD/XmlValidator.php';
  271. $dtd =& new XML_DTD_XmlValidator;
  272. if (!$dtd->isValid($dtd_file, $input_file)) {
  273. return $this->raiseError(MDB2_SCHEMA_ERROR_PARSE, null, null, $dtd->getMessage());
  274. }
  275. }
  276. require_once 'MDB2/Schema/Parser.php';
  277. $parser =& new MDB2_Schema_Parser($variables, $fail_on_invalid_names, $structure);
  278. $result = $parser->setInputFile($input_file);
  279. if (PEAR::isError($result)) {
  280. return $result;
  281. }
  282. $result = $parser->parse();
  283. if (PEAR::isError($result)) {
  284. return $result;
  285. }
  286. if (PEAR::isError($parser->error)) {
  287. return $parser->error;
  288. }
  289. return $parser->database_definition;
  290. }
  291. // }}}
  292. // {{{ getDefinitionFromDatabase()
  293. /**
  294. * Attempt to reverse engineer a schema structure from an existing MDB2
  295. * This method can be used if no xml schema file exists yet.
  296. * The resulting xml schema file may need some manual adjustments.
  297. *
  298. * @return mixed MDB2_OK or array with all ambiguities on success, or a MDB2 error object
  299. * @access public
  300. */
  301. function getDefinitionFromDatabase()
  302. {
  303. $database = $this->db->database_name;
  304. if (empty($database)) {
  305. return $this->raiseError('it was not specified a valid database name');
  306. }
  307. $this->database_definition = array(
  308. 'name' => $database,
  309. 'create' => true,
  310. 'tables' => array(),
  311. 'sequences' => array(),
  312. );
  313. $tables = $this->db->manager->listTables();
  314. if (PEAR::isError($tables)) {
  315. return $tables;
  316. }
  317. foreach ($tables as $table_name) {
  318. $fields = $this->db->manager->listTableFields($table_name);
  319. if (PEAR::isError($fields)) {
  320. return $fields;
  321. }
  322. $this->database_definition['tables'][$table_name] = array('fields' => array());
  323. $table_definition =& $this->database_definition['tables'][$table_name];
  324. foreach ($fields as $field_name) {
  325. $definition = $this->db->reverse->getTableFieldDefinition($table_name, $field_name);
  326. if (PEAR::isError($definition)) {
  327. return $definition;
  328. }
  329. if (array_key_exists('autoincrement', $definition[0])
  330. && $definition[0]['autoincrement']
  331. ) {
  332. $definition[0]['default'] = 0;
  333. }
  334. $table_definition['fields'][$field_name] = $definition[0];
  335. $field_choices = count($definition);
  336. if ($field_choices > 1) {
  337. $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
  338. $field_choice_cnt = 1;
  339. $table_definition['fields'][$field_name]['choices'] = array();
  340. foreach ($definition as $field_choice) {
  341. $table_definition['fields'][$field_name]['choices'][] = $field_choice;
  342. $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
  343. $field_choice_cnt++;
  344. }
  345. $this->warnings[] = $warning;
  346. }
  347. }
  348. $index_definitions = array();
  349. $indexes = $this->db->manager->listTableIndexes($table_name);
  350. if (PEAR::isError($indexes)) {
  351. return $indexes;
  352. }
  353. if (is_array($indexes) && !empty($indexes)
  354. && !array_key_exists('indexes', $table_definition)
  355. ) {
  356. $table_definition['indexes'] = array();
  357. foreach ($indexes as $index_name) {
  358. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  359. $definition = $this->db->reverse->getTableIndexDefinition($table_name, $index_name);
  360. $this->db->popExpect();
  361. if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
  362. continue;
  363. }
  364. if (PEAR::isError($definition)) {
  365. return $definition;
  366. }
  367. $index_definitions[$index_name] = $definition;
  368. }
  369. }
  370. $constraints = $this->db->manager->listTableConstraints($table_name);
  371. if (PEAR::isError($constraints)) {
  372. return $constraints;
  373. }
  374. if (is_array($constraints) && !empty($constraints)
  375. && !array_key_exists('indexes', $table_definition)
  376. ) {
  377. $table_definition['indexes'] = array();
  378. foreach ($constraints as $index_name) {
  379. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  380. $definition = $this->db->reverse->getTableConstraintDefinition($table_name, $index_name);
  381. $this->db->popExpect();
  382. if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
  383. continue;
  384. }
  385. if (PEAR::isError($definition)) {
  386. return $definition;
  387. }
  388. $index_definitions[$index_name] = $definition;
  389. }
  390. }
  391. if (!empty($index_definitions)) {
  392. $table_definition['indexes'] = $index_definitions;
  393. }
  394. }
  395. $sequences = $this->db->manager->listSequences();
  396. if (PEAR::isError($sequences)) {
  397. return $sequences;
  398. }
  399. if (is_array($sequences) && !empty($sequences)) {
  400. foreach ($sequences as $sequence_name) {
  401. $definition = $this->db->reverse->getSequenceDefinition($sequence_name);
  402. if (PEAR::isError($definition)) {
  403. return $definition;
  404. }
  405. $this->database_definition['sequences'][$sequence_name] = $definition;
  406. }
  407. }
  408. return MDB2_OK;
  409. }
  410. // }}}
  411. // {{{ createTableIndexes()
  412. /**
  413. * create a indexes om a table
  414. *
  415. * @param string $table_name name of the table
  416. * @param array $indexes indexes to be created
  417. * @return mixed MDB2_OK on success, or a MDB2 error object
  418. * @param boolean $overwrite determine if the table/index should be
  419. overwritten if it already exists
  420. * @access public
  421. */
  422. function createTableIndexes($table_name, $indexes, $overwrite = false)
  423. {
  424. if (!$this->db->supports('indexes')) {
  425. $this->db->debug('Indexes are not supported');
  426. return MDB2_OK;
  427. }
  428. $supports_primary_key = $this->db->supports('primary_key');
  429. foreach ($indexes as $index_name => $index) {
  430. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  431. $this->db->expectError($errorcodes);
  432. if (array_key_exists('primary', $index) || array_key_exists('unique', $index)) {
  433. $indexes = $this->db->manager->listTableConstraints($table_name);
  434. } else {
  435. $indexes = $this->db->manager->listTableIndexes($table_name);
  436. }
  437. $this->db->popExpect();
  438. if (PEAR::isError($indexes)) {
  439. if (!MDB2::isError($indexes, $errorcodes)) {
  440. return $indexes;
  441. }
  442. } elseif (is_array($indexes) && in_array($index_name, $indexes)) {
  443. if (!$overwrite) {
  444. $this->db->debug('Index already exists: '.$index_name);
  445. return MDB2_OK;
  446. }
  447. if (array_key_exists('primary', $index) || array_key_exists('unique', $index)) {
  448. $result = $this->db->manager->dropConstraint($table_name, $index_name);
  449. } else {
  450. $result = $this->db->manager->dropIndex($table_name, $index_name);
  451. }
  452. if (PEAR::isError($result)) {
  453. return $result;
  454. }
  455. $this->db->debug('Overwritting index: '.$index_name);
  456. }
  457. // check if primary is being used and if it's supported
  458. if (array_key_exists('primary', $index) && !$supports_primary_key) {
  459. /**
  460. * Primary not supported so we fallback to UNIQUE
  461. * and making the field NOT NULL
  462. */
  463. unset($index['primary']);
  464. $index['unique'] = true;
  465. $fields = $index['fields'];
  466. $changes = array();
  467. foreach ($fields as $field => $empty) {
  468. $field_info = $this->db->reverse->getTableFieldDefinition($table_name, $field);
  469. if (PEAR::isError($field_info)) {
  470. return $field_info;
  471. }
  472. $changes['change'][$field] = $field_info[0][0];
  473. $changes['change'][$field]['notnull'] = true;
  474. }
  475. $this->db->manager->alterTable($table_name, $changes, false);
  476. }
  477. if (array_key_exists('primary', $index) || array_key_exists('unique', $index)) {
  478. $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
  479. } else {
  480. $result = $this->db->manager->createIndex($table_name, $index_name, $index);
  481. }
  482. if (PEAR::isError($result)) {
  483. return $result;
  484. }
  485. }
  486. return MDB2_OK;
  487. }
  488. // }}}
  489. // {{{ createTable()
  490. /**
  491. * create a table and inititialize the table if data is available
  492. *
  493. * @param string $table_name name of the table to be created
  494. * @param array $table multi dimensional array that containts the
  495. * structure and optional data of the table
  496. * @param boolean $overwrite determine if the table/index should be
  497. overwritten if it already exists
  498. * @return mixed MDB2_OK on success, or a MDB2 error object
  499. * @access public
  500. */
  501. function createTable($table_name, $table, $overwrite = false)
  502. {
  503. $create = true;
  504. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  505. $this->db->expectError($errorcodes);
  506. $tables = $this->db->manager->listTables();
  507. $this->db->popExpect();
  508. if (PEAR::isError($tables)) {
  509. if (!MDB2::isError($tables, $errorcodes)) {
  510. return $tables;
  511. }
  512. } elseif (is_array($tables) && in_array($table_name, $tables)) {
  513. if (!$overwrite) {
  514. $create = false;
  515. $this->db->debug('Table already exists: '.$table_name);
  516. } else {
  517. $result = $this->db->manager->dropTable($table_name);
  518. if (PEAR::isError($result)) {
  519. return $result;
  520. }
  521. $this->db->debug('Overwritting table: '.$table_name);
  522. }
  523. }
  524. if ($create) {
  525. $result = $this->db->manager->createTable($table_name, $table['fields']);
  526. if (PEAR::isError($result)) {
  527. return $result;
  528. }
  529. }
  530. if (array_key_exists('initialization', $table) && is_array($table['initialization'])) {
  531. $result = $this->initializeTable($table_name, $table);
  532. if (PEAR::isError($result)) {
  533. return $result;
  534. }
  535. }
  536. if (array_key_exists('indexes', $table) && is_array($table['indexes'])) {
  537. $result = $this->createTableIndexes($table_name, $table['indexes'], $overwrite);
  538. if (PEAR::isError($result)) {
  539. return $result;
  540. }
  541. }
  542. return MDB2_OK;
  543. }
  544. // }}}
  545. // {{{ initializeTable()
  546. /**
  547. * inititialize the table with data
  548. *
  549. * @param string $table_name name of the table
  550. * @param array $table multi dimensional array that containts the
  551. * structure and optional data of the table
  552. * @return mixed MDB2_OK on success, or a MDB2 error object
  553. * @access public
  554. */
  555. function initializeTable($table_name, $table)
  556. {
  557. foreach ($table['fields'] as $field_name => $field) {
  558. $placeholders[$field_name] = ':'.$field_name;
  559. $types[$field_name] = $field['type'];
  560. }
  561. $fields = implode(',', array_keys($table['fields']));
  562. $placeholders = implode(',', $placeholders);
  563. $query = "INSERT INTO $table_name ($fields) VALUES ($placeholders)";
  564. $stmt = $this->db->prepare($query, $types, null, true);
  565. if (PEAR::isError($stmt)) {
  566. return $stmt;
  567. }
  568. foreach ($table['initialization'] as $instruction) {
  569. switch ($instruction['type']) {
  570. case 'insert':
  571. if (array_key_exists('fields', $instruction) && is_array($instruction['fields'])) {
  572. $result = $stmt->bindParamArray($instruction['fields']);
  573. if (PEAR::isError($result)) {
  574. return $result;
  575. }
  576. $result = $stmt->execute();
  577. if (PEAR::isError($result)) {
  578. return $result;
  579. }
  580. }
  581. break;
  582. }
  583. }
  584. return $stmt->free();
  585. }
  586. // }}}
  587. // {{{ createSequence()
  588. /**
  589. * create a sequence
  590. *
  591. * @param string $sequence_name name of the sequence to be created
  592. * @param array $sequence multi dimensional array that containts the
  593. * structure and optional data of the table
  594. * @param boolean $overwrite determine if the sequence should be overwritten
  595. if it already exists
  596. * @return mixed MDB2_OK on success, or a MDB2 error object
  597. * @access public
  598. */
  599. function createSequence($sequence_name, $sequence, $overwrite = false)
  600. {
  601. if (!$this->db->supports('sequences')) {
  602. $this->db->debug('Sequences are not supported');
  603. return MDB2_OK;
  604. }
  605. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  606. $this->db->expectError($errorcodes);
  607. $sequences = $this->db->manager->listSequences();
  608. $this->db->popExpect();
  609. if (PEAR::isError($sequences)) {
  610. if (!MDB2::isError($sequences, $errorcodes)) {
  611. return $sequences;
  612. }
  613. } elseif (is_array($sequence) && in_array($sequence_name, $sequences)) {
  614. if (!$overwrite) {
  615. $this->db->debug('Sequence already exists: '.$sequence_name);
  616. return MDB2_OK;
  617. }
  618. $result = $this->db->manager->dropSequence($sequence_name);
  619. if (PEAR::isError($result)) {
  620. return $result;
  621. }
  622. $this->db->debug('Overwritting sequence: '.$sequence_name);
  623. }
  624. $start = 1;
  625. $field = '';
  626. if (array_key_exists('on', $sequence)) {
  627. $table = $sequence['on']['table'];
  628. $field = $sequence['on']['field'];
  629. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  630. $this->db->expectError($errorcodes);
  631. $tables = $this->db->manager->listTables();
  632. $this->db->popExpect();
  633. if (PEAR::isError($tables) && !MDB2::isError($tables, $errorcodes)) {
  634. return $tables;
  635. }
  636. if (!PEAR::isError($tables) &&
  637. is_array($tables) && in_array($table, $tables)
  638. ) {
  639. if ($this->db->supports('summary_functions')) {
  640. $query = "SELECT MAX($field) FROM $table";
  641. } else {
  642. $query = "SELECT $field FROM $table ORDER BY $field DESC";
  643. }
  644. $start = $this->db->queryOne($query, 'integer');
  645. if (PEAR::isError($start)) {
  646. return $start;
  647. }
  648. ++$start;
  649. } else {
  650. $this->warnings[] = 'Could not sync sequence: '.$sequence_name;
  651. }
  652. } elseif (array_key_exists('start', $sequence) && is_numeric($sequence['start'])) {
  653. $start = $sequence['start'];
  654. $table = '';
  655. }
  656. $result = $this->db->manager->createSequence($sequence_name, $start);
  657. if (PEAR::isError($result)) {
  658. return $result;
  659. }
  660. return MDB2_OK;
  661. }
  662. // }}}
  663. // {{{ createDatabase()
  664. /**
  665. * Create a database space within which may be created database objects
  666. * like tables, indexes and sequences. The implementation of this function
  667. * is highly DBMS specific and may require special permissions to run
  668. * successfully. Consult the documentation or the DBMS drivers that you
  669. * use to be aware of eventual configuration requirements.
  670. *
  671. * @return mixed MDB2_OK on success, or a MDB2 error object
  672. * @access public
  673. */
  674. function createDatabase()
  675. {
  676. if (!isset($this->database_definition['name']) || !$this->database_definition['name']) {
  677. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  678. 'no valid database name specified');
  679. }
  680. $create = (isset($this->database_definition['create']) && $this->database_definition['create']);
  681. $overwrite = (isset($this->database_definition['overwrite']) && $this->database_definition['overwrite']);
  682. if ($create) {
  683. $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
  684. $this->db->expectError($errorcodes);
  685. $databases = $this->db->manager->listDatabases();
  686. // Lower / Upper case the db name if the portability deems so.
  687. if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
  688. $func = $this->db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper';
  689. $db_name = $func($this->database_definition['name']);
  690. }
  691. $this->db->popExpect();
  692. if (PEAR::isError($databases)) {
  693. if (!MDB2::isError($databases, $errorcodes)) {
  694. return $databases;
  695. }
  696. } elseif (is_array($databases) && in_array($db_name, $databases)) {
  697. if (!$overwrite) {
  698. $this->db->debug('Database already exists: ' . $this->database_definition['name']);
  699. $create = false;
  700. } else {
  701. $result = $this->db->manager->dropDatabase($this->database_definition['name']);
  702. if (PEAR::isError($result)) {
  703. return $result;
  704. }
  705. $this->db->debug('Overwritting database: '.$this->database_definition['name']);
  706. }
  707. }
  708. if ($create) {
  709. $this->db->expectError(MDB2_ERROR_ALREADY_EXISTS);
  710. $result = $this->db->manager->createDatabase($this->database_definition['name']);
  711. $this->db->popExpect();
  712. if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_ALREADY_EXISTS)) {
  713. return $result;
  714. }
  715. }
  716. }
  717. $previous_database_name = $this->db->setDatabase($this->database_definition['name']);
  718. if (($support_transactions = $this->db->supports('transactions'))
  719. && PEAR::isError($result = $this->db->beginTransaction())
  720. ) {
  721. return $result;
  722. }
  723. $created_objects = 0;
  724. if (isset($this->database_definition['tables'])
  725. && is_array($this->database_definition['tables'])
  726. ) {
  727. foreach ($this->database_definition['tables'] as $table_name => $table) {
  728. $result = $this->createTable($table_name, $table, $overwrite);
  729. if (PEAR::isError($result)) {
  730. break;
  731. }
  732. $created_objects++;
  733. }
  734. }
  735. if (!PEAR::isError($result)
  736. && isset($this->database_definition['sequences'])
  737. && is_array($this->database_definition['sequences'])
  738. ) {
  739. foreach ($this->database_definition['sequences'] as $sequence_name => $sequence) {
  740. $result = $this->createSequence($sequence_name, $sequence, false, $overwrite);
  741. if (PEAR::isError($result)) {
  742. break;
  743. }
  744. $created_objects++;
  745. }
  746. }
  747. if (PEAR::isError($result)) {
  748. if ($created_objects) {
  749. if ($support_transactions) {
  750. $res = $this->db->rollback();
  751. if (PEAR::isError($res))
  752. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  753. 'Could not rollback the partially created database alterations ('.
  754. $result->getMessage().' ('.$result->getUserinfo().'))');
  755. } else {
  756. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  757. 'the database was only partially created ('.
  758. $result->getMessage().' ('.$result->getUserinfo().'))');
  759. }
  760. }
  761. } else {
  762. if ($support_transactions) {
  763. $res = $this->db->commit();
  764. if (PEAR::isError($res))
  765. $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  766. 'Could not end transaction after successfully created the database ('.
  767. $res->getMessage().' ('.$res->getUserinfo().'))');
  768. }
  769. }
  770. $this->db->setDatabase($previous_database_name);
  771. if (PEAR::isError($result) && $create
  772. && PEAR::isError($result2 = $this->db->manager->dropDatabase($this->database_definition['name']))
  773. ) {
  774. return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
  775. 'Could not drop the created database after unsuccessful creation attempt ('.
  776. $result2->getMessage().' ('.$result2->getUserinfo().'))');
  777. }
  778. return $result;
  779. }
  780. // }}}
  781. // {{{ compareDefinitions()
  782. /**
  783. * compare a previous definition with the currenlty parsed definition
  784. *
  785. * @param array multi dimensional array that contains the previous definition
  786. * @param array multi dimensional array that contains the current definition
  787. * @return mixed array of changes on success, or a MDB2 error object
  788. * @access public
  789. */
  790. function compareDefinitions($previous_definition, $current_definition = null)
  791. {
  792. $current_definition = $current_definition ? $current_definition : $this->database_definition;
  793. $changes = array();
  794. if (array_key_exists('tables', $current_definition) && is_array($current_definition['tables'])) {
  795. $changes['tables'] = $defined_tables = array();
  796. foreach ($current_definition['tables'] as $table_name => $table) {
  797. $previous_tables = array();
  798. if (array_key_exists('tables', $previous_definition) && is_array($previous_definition)) {
  799. $previous_tables = $previous_definition['tables'];
  800. }
  801. $change = $this->compareTableDefinitions($table_name, $previous_tables, $table, $defined_tables);
  802. if (PEAR::isError($change)) {
  803. return $change;
  804. }
  805. if (!empty($change)) {
  806. $changes['tables']+= $change;
  807. }
  808. }
  809. if (array_key_exists('tables', $previous_definition) && is_array($previous_definition['tables'])) {
  810. foreach ($previous_definition['tables'] as $table_name => $table) {
  811. if (!array_key_exists($table_name, $defined_tables)) {
  812. $changes['remove'][$table_name] = true;
  813. }
  814. }
  815. }
  816. }
  817. if (array_key_exists('sequences', $current_definition) && is_array($current_definition['sequences'])) {
  818. $changes['sequences'] = $defined_sequences = array();
  819. foreach ($current_definition['sequences'] as $sequence_name => $sequence) {
  820. $previous_sequences = array();
  821. if (array_key_exists('sequences', $previous_definition) && is_array($previous_definition)) {
  822. $previous_sequences = $previous_definition['sequences'];
  823. }
  824. $change = $this->compareSequenceDefinitions(
  825. $sequence_name,
  826. $previous_sequences,
  827. $sequence,
  828. $defined_sequences
  829. );
  830. if (PEAR::isError($change)) {
  831. return $change;
  832. }
  833. if (!empty($change)) {
  834. $changes['sequences']+= $change;
  835. }
  836. }
  837. if (array_key_exists('sequences', $previous_definition) && is_array($previous_definition['sequences'])) {
  838. foreach ($previous_definition['sequences'] as $sequence_name => $sequence) {
  839. if (!array_key_exists($sequence_name, $defined_sequences)) {
  840. $changes['remove'][$sequence_name] = true;
  841. }
  842. }
  843. }
  844. }
  845. return $changes;
  846. }
  847. // }}}
  848. // {{{ compareTableFieldsDefinitions()
  849. /**
  850. * compare a previous definition with the currenlty parsed definition
  851. *
  852. * @param string $table_name name of the table
  853. * @param array multi dimensional array that contains the previous definition
  854. * @param array multi dimensional array that contains the current definition
  855. * @return mixed array of changes on success, or a MDB2 error object
  856. * @access public
  857. */
  858. function compareTableFieldsDefinitions($table_name, $previous_definition,
  859. $current_definition, &$defined_fields)
  860. {
  861. $changes = array();
  862. if (is_array($current_definition)) {
  863. foreach ($current_definition as $field_name => $field) {
  864. $was_field_name = $field['was'];
  865. if (array_key_exists($field_name, $previous_definition)
  866. && isset($previous_definition[$field_name]['was'])
  867. && $previous_definition[$field_name]['was'] == $was_field_name
  868. ) {
  869. $was_field_name = $field_name;
  870. }
  871. if (array_key_exists($was_field_name, $previous_definition)) {
  872. if ($was_field_name != $field_name) {
  873. $changes['rename'][$was_field_name] = array('name' => $field_name, 'definition' => $field);
  874. }
  875. if (array_key_exists($was_field_name, $defined_fields)) {
  876. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  877. 'the field "'.$was_field_name.
  878. '" was specified as base of more than one field of table');
  879. }
  880. $defined_fields[$was_field_name] = true;
  881. $change = $this->db->compareDefinition($field, $previous_definition[$was_field_name]);
  882. if (PEAR::isError($change)) {
  883. return $change;
  884. }
  885. if (!empty($change)) {
  886. $change['definition'] = $field;
  887. $changes['change'][$field_name] = $change;
  888. }
  889. } else {
  890. if ($field_name != $was_field_name) {
  891. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  892. 'it was specified a previous field name ("'.
  893. $was_field_name.'") for field "'.$field_name.'" of table "'.
  894. $table_name.'" that does not exist');
  895. }
  896. $changes['add'][$field_name] = $field;
  897. }
  898. }
  899. }
  900. if (isset($previous_definition) && is_array($previous_definition)) {
  901. foreach ($previous_definition as $field_previous_name => $field_previous) {
  902. if (!array_key_exists($field_previous_name, $defined_fields)) {
  903. $changes['remove'][$field_previous_name] = true;
  904. }
  905. }
  906. }
  907. return $changes;
  908. }
  909. // }}}
  910. // {{{ compareTableIndexesDefinitions()
  911. /**
  912. * compare a previous definition with the currenlty parsed definition
  913. *
  914. * @param string $table_name name of the table
  915. * @param array multi dimensional array that contains the previous definition
  916. * @param array multi dimensional array that contains the current definition
  917. * @return mixed array of changes on success, or a MDB2 error object
  918. * @access public
  919. */
  920. function compareTableIndexesDefinitions($table_name, $previous_definition,
  921. $current_definition, &$defined_indexes)
  922. {
  923. $changes = array();
  924. if (is_array($current_definition)) {
  925. foreach ($current_definition as $index_name => $index) {
  926. $was_index_name = $index['was'];
  927. if (array_key_exists($index_name, $previous_definition)
  928. && isset($previous_definition[$index_name]['was'])
  929. && $previous_definition[$index_name]['was'] == $was_index_name
  930. ) {
  931. $was_index_name = $index_name;
  932. }
  933. if (array_key_exists($was_index_name, $previous_definition)) {
  934. $change = array();
  935. if ($was_index_name != $index_name) {
  936. $change['name'] = $was_index_name;
  937. }
  938. if (array_key_exists($was_index_name, $defined_indexes)) {
  939. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  940. 'the index "'.$was_index_name.'" was specified as base of'.
  941. ' more than one index of table "'.$table_name.'"');
  942. }
  943. $defined_indexes[$was_index_name] = true;
  944. $previous_unique = isset($previous_definition[$was_index_name]['unique']);
  945. $unique = array_key_exists('unique', $index);
  946. if ($previous_unique != $unique) {
  947. $change['unique'] = $unique;
  948. }
  949. $defined_fields = array();
  950. $previous_fields = $previous_definition[$was_index_name]['fields'];
  951. if (array_key_exists('fields', $index) && is_array($index['fields'])) {
  952. foreach ($index['fields'] as $field_name => $field) {
  953. if (array_key_exists($field_name, $previous_fields)) {
  954. $defined_fields[$field_name] = true;
  955. $sorting = (array_key_exists('sorting', $field) ? $field['sorting'] : '');
  956. $previous_sorting = (isset($previous_fields[$field_name]['sorting'])
  957. ? $previous_fields[$field_name]['sorting'] : '');
  958. if ($sorting != $previous_sorting) {
  959. $change['change'] = true;
  960. }
  961. } else {
  962. $change['change'] = true;
  963. }
  964. }
  965. }
  966. if (isset($previous_fields) && is_array($previous_fields)) {
  967. foreach ($previous_fields as $field_name => $field) {
  968. if (!array_key_exists($field_name, $defined_fields)) {
  969. $change['change'] = true;
  970. }
  971. }
  972. }
  973. if (!empty($change)) {
  974. $changes['change'][$index_name] = $change;
  975. }
  976. } else {
  977. if ($index_name != $was_index_name) {
  978. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  979. 'it was specified a previous index name ("'.$was_index_name.
  980. ') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist');
  981. }
  982. $changes['add'][$index_name] = $current_definition[$index_name];
  983. }
  984. }
  985. }
  986. foreach ($previous_definition as $index_previous_name => $index_previous) {
  987. if (!array_key_exists($index_previous_name, $defined_indexes)) {
  988. $changes['remove'][$index_previous_name] = true;
  989. }
  990. }
  991. return $changes;
  992. }
  993. // }}}
  994. // {{{ compareTableDefinitions()
  995. /**
  996. * compare a previous definition with the currenlty parsed definition
  997. *
  998. * @param string $table_name name of the table
  999. * @param array multi dimensional array that contains the previous definition
  1000. * @param array multi dimensional array that contains the current definition
  1001. * @return mixed array of changes on success, or a MDB2 error object
  1002. * @access public
  1003. */
  1004. function compareTableDefinitions($table_name, $previous_definition,
  1005. $current_definition, &$defined_tables)
  1006. {
  1007. $changes = array();
  1008. if (is_array($current_definition)) {
  1009. $was_table_name = $table_name;
  1010. if (array_key_exists('was', $current_definition)) {
  1011. $was_table_name = $current_definition['was'];
  1012. }
  1013. if (array_key_exists($was_table_name, $previous_definition)) {
  1014. $changes['change'][$was_table_name] = array();
  1015. if ($was_table_name != $table_name) {
  1016. $changes['change'][$was_table_name]+= array('name' => $table_name);
  1017. }
  1018. if (array_key_exists($was_table_name, $defined_tables)) {
  1019. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1020. 'the table "'.$was_table_name.
  1021. '" was specified as base of more than of table of the database');
  1022. }
  1023. $defined_tables[$was_table_name] = true;
  1024. if (array_key_exists('fields', $current_definition) && is_array($current_definition['fields'])) {
  1025. $previous_fields = array();
  1026. if (isset($previous_definition[$was_table_name]['fields'])
  1027. && is_array($previous_definition[$was_table_name]['fields'])
  1028. ) {
  1029. $previous_fields = $previous_definition[$was_table_name]['fields'];
  1030. }
  1031. $defined_fields = array();
  1032. $change = $this->compareTableFieldsDefinitions(
  1033. $table_name,
  1034. $previous_fields,
  1035. $current_definition['fields'],
  1036. $defined_fields
  1037. );
  1038. if (PEAR::isError($change)) {
  1039. return $change;
  1040. }
  1041. if (!empty($change)) {
  1042. $changes['change'][$was_table_name]+= $change;
  1043. }
  1044. }
  1045. if (array_key_exists('indexes', $current_definition) && is_array($current_definition['indexes'])) {
  1046. $previous_indexes = array();
  1047. if (isset($previous_definition[$was_table_name]['indexes'])
  1048. && is_array($previous_definition[$was_table_name]['indexes'])
  1049. ) {
  1050. $previous_indexes = $previous_definition[$was_table_name]['indexes'];
  1051. }
  1052. $defined_indexes = array();
  1053. $change = $this->compareTableIndexesDefinitions(
  1054. $table_name,
  1055. $previous_indexes,
  1056. $current_definition['indexes'],
  1057. $defined_indexes
  1058. );
  1059. if (PEAR::isError($change)) {
  1060. return $change;
  1061. }
  1062. if (!empty($change)) {
  1063. if (isset($changes['change'][$was_table_name]['indexes'])) {
  1064. $changes['change'][$was_table_name]['indexes']+= $change;
  1065. } else {
  1066. $changes['change'][$was_table_name]['indexes'] = $change;
  1067. }
  1068. }
  1069. }
  1070. if (empty($changes['change'][$was_table_name])) {
  1071. unset($changes['change'][$was_table_name]);
  1072. }
  1073. if (empty($changes['change'])) {
  1074. unset($changes['change']);
  1075. }
  1076. } else {
  1077. if ($table_name != $was_table_name) {
  1078. return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
  1079. 'it was specified a previous table name ("'.$was_table_name.
  1080. '") for table "'.$table_name.'" that does not exist');
  1081. }
  1082. $changes['add'][$table_name] = true;
  1083. }
  1084. }
  1085. return $changes;
  1086. }
  1087. // }}}
  1088. // {{{ compareSequenceDefinitions()
  1089. /**
  1090. * compare a previous definition with the currenlty parsed definition
  1091. *
  1092. * @param array multi dimensional array that contains the previous definition
  1093. * @param array multi dimensional array that contains the current definition
  1094. * @return mixed array of changes on success, or a MDB2 error object
  1095. * @access public
  1096. */
  1097. function compareSequenceDefinitions($sequence_name, $previous_definition,
  1098. $current_definition, &$defined_sequences)
  1099. {
  1100. $changes = array();
  1101. if (is_array($current_definition)) {
  1102. $was_sequence_name = $sequence_name;
  1103. if (array_key_exists($sequence_name, $previous_definition)
  1104. && isset($previous_definition[$sequence_name]['was'])
  1105. && $previous_definition[$sequence_name]['was'] == $was_sequence_name
  1106. ) {
  1107. $was_sequence_name = $sequence_name;
  1108. } elseif (array_key_exists('was', $current_definition)) {
  1109. $was_sequence_name = $current_definition['was'];
  1110. }
  1111. if (array_key_exists($was_sequence_name, $previous_definition)) {
  1112. if ($was_sequence_name != $sequence_name) {
  1113. $changes['change'][$was_sequence_name]['name'] = $sequence_name;
  1114. }
  1115. if (array_key_exists($was_sequence_name, $defined_sequences)) {

Large files files are truncated, but you can click here to view the full file