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