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

/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

Large files files are truncated, but you can click here to view the full 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, __

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