1<?php 2// +----------------------------------------------------------------------+ 3// | PHP Version 4 | 4// +----------------------------------------------------------------------+ 5// | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox, | 6// | Stig. S. Bakken, Lukas Smith | 7// | All rights reserved. | 8// +----------------------------------------------------------------------+ 9// | MDB is a merge of PEAR DB and Metabases that provides a unified DB | 10// | API as well as database abstraction for PHP applications. | 11// | This LICENSE is in the BSD license style. | 12// | | 13// | Redistribution and use in source and binary forms, with or without | 14// | modification, are permitted provided that the following conditions | 15// | are met: | 16// | | 17// | Redistributions of source code must retain the above copyright | 18// | notice, this list of conditions and the following disclaimer. | 19// | | 20// | Redistributions in binary form must reproduce the above copyright | 21// | notice, this list of conditions and the following disclaimer in the | 22// | documentation and/or other materials provided with the distribution. | 23// | | 24// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | 25// | Lukas Smith nor the names of his contributors may be used to endorse | 26// | or promote products derived from this software without specific prior| 27// | written permission. | 28// | | 29// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 30// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 31// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | 32// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | 33// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | 34// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | 35// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS| 36// | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED | 37// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 38// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY| 39// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 40// | POSSIBILITY OF SUCH DAMAGE. | 41// +----------------------------------------------------------------------+ 42// | Author: Lukas Smith <smith@backendmedia.com> | 43// +----------------------------------------------------------------------+ 44// 45// $Id: Manager.php,v 1.75.4.4 2004/03/10 14:42:59 lsmith Exp $ 46// 47 48require_once('MDB/Parser.php'); 49 50define('MDB_MANAGER_DUMP_ALL', 0); 51define('MDB_MANAGER_DUMP_STRUCTURE', 1); 52define('MDB_MANAGER_DUMP_CONTENT', 2); 53 54/** 55 * The database manager is a class that provides a set of database 56 * management services like installing, altering and dumping the data 57 * structures of databases. 58 * 59 * @package MDB 60 * @category Database 61 * @author Lukas Smith <smith@backendmedia.com> 62 */ 63class MDB_Manager extends PEAR 64{ 65 // {{{ properties 66 67 var $database; 68 69 var $options = array( 70 'fail_on_invalid_names' => 1, 71 'debug' => 0 72 ); 73 var $invalid_names = array( 74 'user' => array(), 75 'is' => array(), 76 'file' => array( 77 'oci' => array(), 78 'oracle' => array() 79 ), 80 'notify' => array( 81 'pgsql' => array() 82 ), 83 'restrict' => array( 84 'mysql' => array() 85 ), 86 'password' => array( 87 'ibase' => array() 88 ) 89 ); 90 var $default_values = array( 91 'integer' => 0, 92 'float' => 0, 93 'decimal' => 0, 94 'text' => '', 95 'timestamp' => '0001-01-01 00:00:00', 96 'date' => '0001-01-01', 97 'time' => '00:00:00' 98 ); 99 100 var $warnings = array(); 101 102 var $database_definition = array( 103 'name' => '', 104 'create' => 0, 105 'TABLES' => array() 106 ); 107 108 // }}} 109 // {{{ raiseError() 110 111 /** 112 * This method is used to communicate an error and invoke error 113 * callbacks etc. Basically a wrapper for PEAR::raiseError 114 * without the message string. 115 * 116 * @param mixed $code integer error code, or a PEAR error object (all 117 * other parameters are ignored if this parameter is an object 118 * @param int $mode error mode, see PEAR_Error docs 119 * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the 120 * error level (E_USER_NOTICE etc). If error mode is 121 * PEAR_ERROR_CALLBACK, this is the callback function, either as a 122 * function name, or as an array of an object and method name. For 123 * other error modes this parameter is ignored. 124 * @param string $userinfo Extra debug information. Defaults to the last 125 * query and native error code. 126 * @param mixed $nativecode Native error code, integer or string depending 127 * the backend. 128 * @return object a PEAR error object 129 * @access public 130 * @see PEAR_Error 131 */ 132 function &raiseError($code = MDB_MANAGER_ERROR, $mode = NULL, $options = NULL, 133 $userinfo = NULL, $nativecode = NULL) 134 { 135 // The error is yet a MDB error object 136 if(is_object($code)) { 137 $err = PEAR::raiseError($code, NULL, NULL, NULL, NULL, NULL, TRUE); 138 return($err); 139 } 140 141 $err = PEAR::raiseError(NULL, $code, $mode, $options, $userinfo, 142 'MDB_Error', TRUE); 143 return($err); 144 } 145 146 // }}} 147 // {{{ captureDebugOutput() 148 149 /** 150 * set a debug handler 151 * 152 * @param string $capture name of the function that should be used in 153 * debug() 154 * @access public 155 * @see debug() 156 */ 157 function captureDebugOutput($capture) 158 { 159 $this->options['debug'] = $capture; 160 $this->database->captureDebugOutput(1); 161 } 162 163 // }}} 164 // {{{ debugOutput() 165 166 /** 167 * output debug info 168 * 169 * @return string content of the debug_output class variable 170 * @access public 171 */ 172 function debugOutput() 173 { 174 return($this->database->debugOutput()); 175 } 176 177 // }}} 178 // {{{ resetWarnings() 179 180 /** 181 * reset the warning array 182 * 183 * @access public 184 */ 185 function resetWarnings() 186 { 187 $this->warnings = array(); 188 } 189 190 // }}} 191 // {{{ getWarnings() 192 193 /** 194 * get all warnings in reverse order. 195 * This means that the last warning is the first element in the array 196 * 197 * @return array with warnings 198 * @access public 199 * @see resetWarnings() 200 */ 201 function getWarnings() 202 { 203 return array_reverse($this->warnings); 204 } 205 206 // }}} 207 // {{{ setOption() 208 209 /** 210 * set the option for the db class 211 * 212 * @param string $option option name 213 * @param mixed $value value for the option 214 * @return mixed MDB_OK or MDB_Error 215 * @access public 216 */ 217 function setOption($option, $value) 218 { 219 if(isset($this->options[$option])) { 220 $this->options[$option] = $value; 221 return(MDB_OK); 222 } 223 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, "unknown option $option")); 224 } 225 226 // }}} 227 // {{{ getOption() 228 229 /** 230 * returns the value of an option 231 * 232 * @param string $option option name 233 * @return mixed the option value or error object 234 * @access public 235 */ 236 function getOption($option) 237 { 238 if(isset($this->options[$option])) { 239 return($this->options[$option]); 240 } 241 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, "unknown option $option")); 242 } 243 244 // }}} 245 // {{{ connect() 246 247 /** 248 * Create a new MDB connection object and connect to the specified 249 * database 250 * 251 * @param mixed $dbinfo 'data source name', see the MDB::parseDSN 252 * method for a description of the dsn format. 253 * Can also be specified as an array of the 254 * format returned by MDB::parseDSN. 255 * Finally you can also pass an existing db 256 * object to be used. 257 * @param mixed $options An associative array of option names and 258 * their values. 259 * @return mixed MDB_OK on success, or a MDB error object 260 * @access public 261 * @see MDB::parseDSN 262 */ 263 function &connect(&$dbinfo, $options = FALSE) 264 { 265 if(is_object($this->database) && !MDB::isError($this->database)) { 266 $this->disconnect(); 267 } 268 if(is_object($dbinfo)) { 269 $this->database =& $dbinfo; 270 } else { 271 $this->database =& MDB::connect($dbinfo, $options); 272 if(MDB::isError($this->database)) { 273 return($this->database); 274 } 275 } 276 if(is_array($options)) { 277 $this->options = array_merge($options, $this->options); 278 } 279 return(MDB_OK); 280 } 281 282 // }}} 283 // {{{ disconnect() 284 285 /** 286 * Log out and disconnect from the database. 287 * 288 * @access public 289 */ 290 function disconnect() 291 { 292 if(is_object($this->database) && !MDB::isError($this->database)) { 293 $this->database->disconnect(); 294 unset($this->database); 295 } 296 } 297 298 // }}} 299 // {{{ setDatabase() 300 301 /** 302 * Select a different database 303 * 304 * @param string $name name of the database that should be selected 305 * @return string name of the database previously connected to 306 * @access public 307 */ 308 function setDatabase($name) 309 { 310 return($this->database->setDatabase($name)); 311 } 312 313 // }}} 314 // {{{ _createTable() 315 316 /** 317 * create a table and inititialize the table if data is available 318 * 319 * @param string $table_name name of the table to be created 320 * @param array $table multi dimensional array that containts the 321 * structure and optional data of the table 322 * @param boolean $overwrite determine if the table/index should be 323 overwritten if it already exists 324 * @return mixed MDB_OK on success, or a MDB error object 325 * @access private 326 */ 327 function _createTable($table_name, $table, $overwrite = FALSE) 328 { 329 $this->expectError(MDB_ERROR_ALREADY_EXISTS); 330 $result = $this->database->createTable($table_name, $table['FIELDS']); 331 $this->popExpect(); 332 if(MDB::isError($result)) { 333 if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) { 334 $this->warnings[] = 'Table already exists: '.$table_name; 335 if($overwrite) { 336 $this->database->debug('Overwritting Table'); 337 $result = $this->database->dropTable($table_name); 338 if(MDB::isError($result)) { 339 return($result); 340 } 341 $result = $this->database->createTable($table_name, $table['FIELDS']); 342 if(MDB::isError($result)) { 343 return($result); 344 } 345 } else { 346 $result = MDB_OK; 347 } 348 } else { 349 $this->database->debug('Create table error: '.$table_name); 350 return($result); 351 } 352 } 353 if(isset($table['initialization']) && is_array($table['initialization'])) { 354 foreach($table['initialization'] as $instruction) { 355 switch($instruction['type']) { 356 case 'insert': 357 $query_fields = $query_values = array(); 358 if(isset($instruction['FIELDS']) && is_array($instruction['FIELDS'])) { 359 foreach($instruction['FIELDS'] as $field_name => $field) { 360 $query_fields[] = $field_name; 361 $query_values[] = '?'; 362 } 363 $query_fields = implode(',',$query_fields); 364 $query_values = implode(',',$query_values); 365 $result = $prepared_query = $this->database->prepareQuery( 366 "INSERT INTO $table_name ($query_fields) VALUES ($query_values)"); 367 } 368 if(!MDB::isError($prepared_query)) { 369 if(isset($instruction['FIELDS']) && is_array($instruction['FIELDS'])) { 370 $lobs = array(); 371 $field_number = 0; 372 foreach($instruction['FIELDS'] as $field_name => $field) { 373 $field_number++; 374 $query = $field_name; 375 switch($table['FIELDS'][$field_name]['type']) { 376 case 'integer': 377 $result = $this->database->setParamInteger($prepared_query, 378 $field_number, intval($field)); 379 break; 380 case 'text': 381 $result = $this->database->setParamText($prepared_query, 382 $field_number, $field); 383 break; 384 case 'clob': 385 $lob_definition = array( 386 'Database' => $this->database, 387 'Error' => '', 388 'Data' => $field 389 ); 390 if(MDB::isError($result = $this->database->createLob($lob_definition))) 391 { 392 break; 393 } 394 $lob = count($lobs); 395 $lobs[$lob] = $result; 396 $result = $this->database->setParamClob($prepared_query, 397 $field_number, $lobs[$lob], $field_name); 398 break; 399 case 'blob': 400 $lob_definition = array( 401 'Database' => $this->database, 402 'Error' => '', 403 'Data' => $field 404 ); 405 if(MDB::isError($result = $this->database->createLob($lob_definition))) { 406 break; 407 } 408 $lob = count($lobs); 409 $lobs[$lob] = $result; 410 $result = $this->database->setParamBlob($prepared_query, 411 $field_number, $lobs[$lob], $field_name); 412 break; 413 case 'boolean': 414 $result = $this->database->setParamBoolean($prepared_query, 415 $field_number, intval($field)); 416 break; 417 case 'date': 418 $result = $this->database->setParamDate($prepared_query, 419 $field_number, $field); 420 break; 421 case 'timestamp': 422 $result = $this->database->setParamTimestamp($prepared_query, 423 $field_number, $field); 424 break; 425 case 'time': 426 $result = $this->database->setParamTime($prepared_query, 427 $field_number, $field); 428 break; 429 case 'float': 430 $result = $this->database->setParamFloat($prepared_query, 431 $field_number, doubleval($field)); 432 break; 433 case 'decimal': 434 $result = $this->database->setParamDecimal($prepared_query, 435 $field_number, $field); 436 break; 437 default: 438 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 439 'type "'.$field['type'].'" is not yet supported'); 440 break; 441 } 442 if(MDB::isError($result)) { 443 break; 444 } 445 } 446 } 447 if(!MDB::isError($result)) { 448 $result = $this->database->executeQuery($prepared_query); 449 } 450 for($lob = 0; $lob < count($lobs); $lob++) { 451 $this->database->destroyLOB($lobs[$lob]); 452 } 453 $this->database->freePreparedQuery($prepared_query); 454 } 455 break; 456 } 457 } 458 }; 459 if(!MDB::isError($result) && isset($table['INDEXES']) && is_array($table['INDEXES'])) { 460 if(!$this->database->support('Indexes')) { 461 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 462 'indexes are not supported')); 463 } 464 foreach($table['INDEXES'] as $index_name => $index) { 465 $this->expectError(MDB_ERROR_ALREADY_EXISTS); 466 $result = $this->database->createIndex($table_name, $index_name, $index); 467 $this->popExpect(); 468 if(MDB::isError($result)) { 469 if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) { 470 $this->warnings[] = 'Index already exists: '.$index_name; 471 if($overwrite) { 472 $this->database->debug('Overwritting Index'); 473 $result = $this->database->dropIndex($table_name, $index_name); 474 if(MDB::isError($result)) { 475 break; 476 } 477 $result = $this->database->createIndex($table_name, $index_name, $index); 478 if(MDB::isError($result)) { 479 break; 480 } 481 } else { 482 $result = MDB_OK; 483 } 484 } else { 485 $this->database->debug('Create index error: '.$table_name); 486 break; 487 } 488 } 489 } 490 } 491 if(MDB::isError($result)) { 492 $result = $this->database->dropTable($table_name); 493 if(MDB::isError($result)) { 494 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 495 'could not drop the table (' 496 .$result->getMessage().' ('.$result->getUserinfo(),'))', 497 'MDB_Error', TRUE); 498 } 499 return($result); 500 } 501 return(MDB_OK); 502 } 503 504 // }}} 505 // {{{ _dropTable() 506 507 /** 508 * drop a table 509 * 510 * @param string $table_name name of the table to be dropped 511 * @return mixed MDB_OK on success, or a MDB error object 512 * @access private 513 */ 514 function _dropTable($table_name) 515 { 516 return($this->database->dropTable($table_name)); 517 } 518 519 // }}} 520 // {{{ _createSequence() 521 522 /** 523 * create a sequence 524 * 525 * @param string $sequence_name name of the sequence to be created 526 * @param array $sequence multi dimensional array that containts the 527 * structure and optional data of the table 528 * @param string $created_on_table 529 * @param boolean $overwrite determine if the sequence should be overwritten 530 if it already exists 531 * @return mixed MDB_OK on success, or a MDB error object 532 * @access private 533 */ 534 function _createSequence($sequence_name, $sequence, $created_on_table, $overwrite = FALSE) 535 { 536 if(!$this->database->support('Sequences')) { 537 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 538 'sequences are not supported')); 539 } 540 if(!isset($sequence_name) || !strcmp($sequence_name, '')) { 541 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 542 'no valid sequence name specified')); 543 } 544 $this->database->debug('Create sequence: '.$sequence_name); 545 if(isset($sequence['start']) && $sequence['start'] != '') { 546 $start = $sequence['start']; 547 } else if(isset($sequence['on']) && !$created_on_table) { 548 $table = $sequence['on']['table']; 549 $field = $sequence['on']['field']; 550 if($this->database->support('Summaryfunctions')) { 551 $field = "MAX($field)"; 552 } 553 $start = $this->database->queryOne("SELECT $field FROM $table"); 554 if(MDB::isError($start)) { 555 return($start); 556 } 557 } else { 558 $start = 1; 559 } 560 561 $this->expectError(MDB_ERROR_ALREADY_EXISTS); 562 $result = $this->database->createSequence($sequence_name, $start); 563 $this->popExpect(); 564 if(MDB::isError($result)) { 565 if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) { 566 $this->warnings[] = 'Sequence already exists: '.$sequence_name; 567 if($overwrite) { 568 $this->database->debug('Overwritting Sequence'); 569 $result = $this->database->dropSequence($sequence_name); 570 if(MDB::isError($result)) { 571 return($result); 572 } 573 $result = $this->database->createSequence($sequence_name, $start); 574 if(MDB::isError($result)) { 575 return($result); 576 } 577 } else { 578 return(MDB_OK); 579 } 580 } else { 581 $this->database->debug('Create sequence error: '.$sequence_name); 582 return($result); 583 } 584 } 585 } 586 587 // }}} 588 // {{{ _dropSequence() 589 590 /** 591 * drop a table 592 * 593 * @param string $sequence_name name of the sequence to be dropped 594 * @return mixed MDB_OK on success, or a MDB error object 595 * @access private 596 */ 597 function _dropSequence($sequence_name) 598 { 599 if(!$this->database->support('Sequences')) { 600 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 601 'sequences are not supported')); 602 } 603 $this->database->debug('Dropping sequence: '.$sequence_name); 604 if(!isset($sequence_name) || !strcmp($sequence_name, '')) { 605 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 606 'no valid sequence name specified')); 607 } 608 return($this->database->dropSequence($sequence_name)); 609 } 610 611 // }}} 612 // {{{ _createDatabase() 613 614 /** 615 * Create a database space within which may be created database objects 616 * like tables, indexes and sequences. The implementation of this function 617 * is highly DBMS specific and may require special permissions to run 618 * successfully. Consult the documentation or the DBMS drivers that you 619 * use to be aware of eventual configuration requirements. 620 * 621 * @return mixed MDB_OK on success, or a MDB error object 622 * @access private 623 */ 624 function _createDatabase() 625 { 626 if(!isset($this->database_definition['name']) 627 || !strcmp($this->database_definition['name'], '') 628 ) { 629 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 630 'no valid database name specified')); 631 } 632 $create = (isset($this->database_definition['create']) && $this->database_definition['create']); 633 $overwrite = (isset($this->database_definition['overwrite']) && $this->database_definition['overwrite']); 634 if($create) { 635 $this->database->debug('Create database: '.$this->database_definition['name']); 636 $this->expectError(MDB_ERROR_ALREADY_EXISTS); 637 $result = $this->database->createDatabase($this->database_definition['name']); 638 $this->popExpect(); 639 if(MDB::isError($result)) { 640 if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) { 641 $this->warnings[] = 'Database already exists: '.$this->database_definition['name']; 642 if($overwrite) { 643 $this->database->debug('Overwritting Database'); 644 $result = $this->database->dropDatabase($this->database_definition['name']); 645 if(MDB::isError($result)) { 646 return($result); 647 } 648 $result = $this->database->createDatabase($this->database_definition['name']); 649 if(MDB::isError($result)) { 650 return($result); 651 } 652 } else { 653 $result = MDB_OK; 654 } 655 } else { 656 $this->database->debug('Create database error.'); 657 return($result); 658 } 659 } 660 } 661 $previous_database_name = $this->database->setDatabase($this->database_definition['name']); 662 if(($support_transactions = $this->database->support('Transactions')) 663 && MDB::isError($result = $this->database->autoCommit(FALSE)) 664 ) { 665 return($result); 666 } 667 668 $created_objects = 0; 669 if(isset($this->database_definition['TABLES']) 670 && is_array($this->database_definition['TABLES']) 671 ) { 672 foreach($this->database_definition['TABLES'] as $table_name => $table) { 673 $result = $this->_createTable($table_name, $table, $overwrite); 674 if(MDB::isError($result)) { 675 break; 676 } 677 $created_objects++; 678 } 679 } 680 if(!MDB::isError($result) 681 && isset($this->database_definition['SEQUENCES']) 682 && is_array($this->database_definition['SEQUENCES']) 683 ) { 684 foreach($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) { 685 $result = $this->_createSequence($sequence_name, $sequence, 0, $overwrite); 686 687 if(MDB::isError($result)) { 688 break; 689 } 690 $created_objects++; 691 } 692 } 693 694 if(MDB::isError($result)) { 695 if($created_objects) { 696 if($support_transactions) { 697 $res = $this->database->rollback(); 698 if(MDB::isError($res)) 699 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 700 'Could not rollback the partially created database alterations (' 701 .$result->getMessage().' ('.$result->getUserinfo(),'))', 702 'MDB_Error', TRUE); 703 } else { 704 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 705 'the database was only partially created (' 706 .$result->getMessage().' ('.$result->getUserinfo(),'))', 707 'MDB_Error', TRUE); 708 } 709 } 710 } else { 711 if($support_transactions) { 712 $res = $this->database->autoCommit(TRUE); 713 if(MDB::isError($res)) 714 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 715 'Could not end transaction after successfully created the database (' 716 .$res->getMessage().' ('.$res->getUserinfo(),'))', 717 'MDB_Error', TRUE); 718 } 719 } 720 721 $this->database->setDatabase($previous_database_name); 722 723 if(MDB::isError($result) 724 && $create 725 && MDB::isError($res = $this->database->dropDatabase($this->database_definition['name'])) 726 ) { 727 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 728 'Could not drop the created database after unsuccessful creation attempt (' 729 .$res->getMessage().' ('.$res->getUserinfo(),'))', 730 'MDB_Error', TRUE)); 731 } 732 733 if(MDB::isError($result)) { 734 return($result); 735 } 736 737 return(MDB_OK); 738 } 739 740 // }}} 741 // {{{ _addDefinitionChange() 742 743 /** 744 * add change to an array of multiple changes 745 * 746 * @param array &$changes 747 * @param string $definition 748 * @param string $item 749 * @param array $change 750 * @return mixed MDB_OK on success, or a MDB error object 751 * @access private 752 */ 753 function _addDefinitionChange(&$changes, $definition, $item, $change) 754 { 755 if(!isset($changes[$definition][$item])) { 756 $changes[$definition][$item] = array(); 757 } 758 foreach($change as $change_data_name => $change_data) { 759 if(isset($change_data) && is_array($change_data)) { 760 if(!isset($changes[$definition][$item][$change_data_name])) { 761 $changes[$definition][$item][$change_data_name] = array(); 762 } 763 foreach($change_data as $change_part_name => $change_part) { 764 $changes[$definition][$item][$change_data_name][$change_part_name] = $change_part; 765 } 766 } else { 767 $changes[$definition][$item][$change_data_name] = $change_data; 768 } 769 } 770 return(MDB_OK); 771 } 772 773 // }}} 774 // {{{ _compareDefinitions() 775 776 /** 777 * compare a previous definition with the currenlty parsed definition 778 * 779 * @param array multi dimensional array that contains the previous definition 780 * @return mixed array of changes on success, or a MDB error object 781 * @access private 782 */ 783 function _compareDefinitions($previous_definition) 784 { 785 $defined_tables = $changes = array(); 786 if(isset($this->database_definition['TABLES']) && is_array($this->database_definition['TABLES'])) { 787 foreach($this->database_definition['TABLES'] as $table_name => $table) { 788 $was_table_name = $table['was']; 789 if(isset($previous_definition['TABLES'][$table_name]) 790 && isset($previous_definition['TABLES'][$table_name]['was']) 791 && !strcmp($previous_definition['TABLES'][$table_name]['was'], $was_table_name) 792 ) { 793 $was_table_name = $table_name; 794 } 795 if(isset($previous_definition['TABLES'][$was_table_name])) { 796 if(strcmp($was_table_name, $table_name)) { 797 $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, array('name' => $table_name)); 798 $this->database->debug("Renamed table '$was_table_name' to '$table_name'"); 799 } 800 if(isset($defined_tables[$was_table_name])) { 801 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 802 'the table "'.$was_table_name.'" was specified as base of more than of table of the database', 803 'MDB_Error', TRUE)); 804 } 805 $defined_tables[$was_table_name] = 1; 806 807 $previous_fields = $previous_definition['TABLES'][$was_table_name]['FIELDS']; 808 $defined_fields = array(); 809 if(isset($table['FIELDS']) && is_array($table['FIELDS'])) { 810 foreach($table['FIELDS'] as $field_name => $field) { 811 $was_field_name = $field['was']; 812 if(isset($previous_fields[$field_name]) 813 && isset($previous_fields[$field_name]['was']) 814 && !strcmp($previous_fields[$field_name]['was'], $was_field_name) 815 ) { 816 $was_field_name = $field_name; 817 } 818 if(isset($previous_fields[$was_field_name])) { 819 if(strcmp($was_field_name, $field_name)) { 820 $query = $this->database->getFieldDeclaration($field_name, $field); 821 if(MDB::isError($query)) { 822 return($query); 823 } 824 $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, 825 array( 826 'RenamedFields' => array( 827 $was_field_name => array( 828 'name' => $field_name, 829 'Declaration' => $query 830 ) 831 ) 832 ) 833 ); 834 $this->database->debug("Renamed field '$was_field_name' to '$field_name' in table '$table_name'"); 835 } 836 if(isset($defined_fields[$was_field_name])) { 837 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 838 'the field "'.$was_table_name.'" was specified as base of more than one field of table', 839 'MDB_Error', TRUE)); 840 } 841 $defined_fields[$was_field_name] = 1; 842 $change = array(); 843 if($field['type'] == $previous_fields[$was_field_name]['type']) { 844 switch($field['type']) { 845 case 'integer': 846 $previous_unsigned = isset($previous_fields[$was_field_name]['unsigned']); 847 $unsigned = isset($fields[$field_name]['unsigned']); 848 if(strcmp($previous_unsigned, $unsigned)) { 849 $change['unsigned'] = $unsigned; 850 $this->database->debug("Changed field '$field_name' type from '".($previous_unsigned ? 'unsigned ' : '').$previous_fields[$was_field_name]['type']."' to '".($unsigned ? 'unsigned ' : '').$field['type']."' in table '$table_name'"); 851 } 852 break; 853 case 'text': 854 case 'clob': 855 case 'blob': 856 $previous_length = (isset($previous_fields[$was_field_name]['length']) ? $previous_fields[$was_field_name]['length'] : 0); 857 $length = (isset($field['length']) ? $field['length'] : 0); 858 if(strcmp($previous_length, $length)) { 859 $change['length'] = $length; 860 $this->database->debug("Changed field '$field_name' length from '".$previous_fields[$was_field_name]['type'].($previous_length == 0 ? ' no length' : "($previous_length)")."' to '".$field['type'].($length == 0 ? ' no length' : "($length)")."' in table '$table_name'"); 861 } 862 break; 863 case 'date': 864 case 'timestamp': 865 case 'time': 866 case 'boolean': 867 case 'float': 868 case 'decimal': 869 break; 870 default: 871 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 872 'type "'.$field['type'].'" is not yet supported', 873 'MDB_Error', TRUE)); 874 } 875 876 $previous_notnull = isset($previous_fields[$was_field_name]['notnull']); 877 $notnull = isset($field['notnull']); 878 if($previous_notnull != $notnull) { 879 $change['ChangedNotNull'] = 1; 880 if($notnull) { 881 $change['notnull'] = isset($field['notnull']); 882 } 883 $this->database->debug("Changed field '$field_name' notnull from $previous_notnull to $notnull in table '$table_name'"); 884 } 885 886 $previous_default = isset($previous_fields[$was_field_name]['default']); 887 $default = isset($field['default']); 888 if(strcmp($previous_default, $default)) { 889 $change['ChangedDefault'] = 1; 890 if($default) { 891 $change['default'] = $field['default']; 892 } 893 $this->database->debug("Changed field '$field_name' default from ".($previous_default ? "'".$previous_fields[$was_field_name]['default']."'" : 'NULL').' TO '.($default ? "'".$fields[$field_name]['default']."'" : 'NULL')." IN TABLE '$table_name'"); 894 } else { 895 if($default 896 && strcmp($previous_fields[$was_field_name]['default'], $field['default']) 897 ) { 898 $change['ChangedDefault'] = 1; 899 $change['default'] = $field['default']; 900 $this->database->debug("Changed field '$field_name' default from '".$previous_fields[$was_field_name]['default']."' to '".$fields[$field_name]['default']."' in table '$table_name'"); 901 } 902 } 903 } else { 904 $change['type'] = $field['type']; 905 $this->database->debug("Changed field '$field_name' type from '".$previous_fields[$was_field_name]['type']."' to '".$fields[$field_name]['type']."' in table '$table_name'"); 906 } 907 if(count($change)) { 908 $query = $this->database->getFieldDeclaration($field_name, $field); 909 if(MDB::isError($query)) { 910 return($query); 911 } 912 $change['Declaration'] = $query; 913 $change['Definition'] = $field; 914 $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, array('ChangedFields' => array($field_name => $change))); 915 } 916 } else { 917 if(strcmp($field_name, $was_field_name)) { 918 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 919 'it was specified a previous field name ("' 920 .$was_field_name.'") for field "'.$field_name.'" of table "' 921 .$table_name.'" that does not exist', 922 'MDB_Error', TRUE)); 923 } 924 $query = $this->database->getFieldDeclaration($field_name, $field); 925 if(MDB::isError($query)) { 926 return($query); 927 } 928 $change['Declaration'] = $query; 929 $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('AddedFields' => array($field_name => $change))); 930 $this->database->debug("Added field '$field_name' to table '$table_name'"); 931 } 932 } 933 } 934 if(isset($previous_fields) && is_array($previous_fields)) { 935 foreach ($previous_fields as $field_previous_name => $field_previous) { 936 if(!isset($defined_fields[$field_previous_name])) { 937 $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('RemovedFields' => array($field_previous_name => array()))); 938 $this->database->debug("Removed field '$field_name' from table '$table_name'"); 939 } 940 } 941 } 942 $indexes = array(); 943 if(isset($this->database_definition['TABLES'][$table_name]['INDEXES']) 944 && is_array($this->database_definition['TABLES'][$table_name]['INDEXES']) 945 ) { 946 $indexes = $this->database_definition['TABLES'][$table_name]['INDEXES']; 947 } 948 $previous_indexes = array(); 949 if(isset($previous_definition['TABLES'][$was_table_name]['INDEXES']) 950 && is_array($previous_definition['TABLES'][$was_table_name]['INDEXES']) 951 ) { 952 $previous_indexes = $previous_definition['TABLES'][$was_table_name]['INDEXES']; 953 } 954 $defined_indexes = array(); 955 foreach($indexes as $index_name => $index) { 956 $was_index_name = $index['was']; 957 if(isset($previous_indexes[$index_name]) 958 && isset($previous_indexes[$index_name]['was']) 959 && !strcmp($previous_indexes[$index_name]['was'], $was_index_name) 960 ) { 961 $was_index_name = $index_name; 962 } 963 if(isset($previous_indexes[$was_index_name])) { 964 $change = array(); 965 966 if(strcmp($was_index_name, $index_name)) { 967 $change['name'] = $was_index_name; 968 $this->database->debug("Changed index '$was_index_name' name to '$index_name' in table '$table_name'"); 969 } 970 if(isset($defined_indexes[$was_index_name])) { 971 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 972 'the index "'.$was_index_name.'" was specified as base of' 973 .' more than one index of table "'.$table_name.'"', 974 'MDB_Error', TRUE)); 975 } 976 $defined_indexes[$was_index_name] = 1; 977 978 $previous_unique = isset($previous_indexes[$was_index_name]['unique']); 979 $unique = isset($index['unique']); 980 if($previous_unique != $unique) { 981 $change['ChangedUnique'] = 1; 982 if($unique) { 983 $change['unique'] = $unique; 984 } 985 $this->database->debug("Changed index '$index_name' unique from $previous_unique to $unique in table '$table_name'"); 986 } 987 $defined_fields = array(); 988 $previous_fields = $previous_indexes[$was_index_name]['FIELDS']; 989 if(isset($index['FIELDS']) && is_array($index['FIELDS'])) { 990 foreach($index['FIELDS'] as $field_name => $field) { 991 if(isset($previous_fields[$field_name])) { 992 $defined_fields[$field_name] = 1; 993 $sorting = (isset($field['sorting']) ? $field['sorting'] : ''); 994 $previous_sorting = (isset($previous_fields[$field_name]['sorting']) ? $previous_fields[$field_name]['sorting'] : ''); 995 if(strcmp($sorting, $previous_sorting)) { 996 $this->database->debug("Changed index field '$field_name' sorting default from '$previous_sorting' to '$sorting' in table '$table_name'"); 997 $change['ChangedFields'] = 1; 998 } 999 } else { 1000 $change['ChangedFields'] = 1; 1001 $this->database->debug("Added field '$field_name' to index '$index_name' of table '$table_name'"); 1002 } 1003 } 1004 } 1005 if(isset($previous_fields) && is_array($previous_fields)) { 1006 foreach($previous_fields as $field_name => $field) { 1007 if(!isset($defined_fields[$field_name])) { 1008 $change['ChangedFields'] = 1; 1009 $this->database->debug("Removed field '$field_name' from index '$index_name' of table '$table_name'"); 1010 } 1011 } 1012 } 1013 1014 if(count($change)) { 1015 $this->_addDefinitionChange($changes, 'INDEXES', $table_name,array('ChangedIndexes' => array($index_name => $change))); 1016 } 1017 } else { 1018 if(strcmp($index_name, $was_index_name)) { 1019 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 1020 'it was specified a previous index name ("'.$was_index_name 1021 .') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist', 1022 'MDB_Error', TRUE)); 1023 } 1024 $this->_addDefinitionChange($changes, 'INDEXES', $table_name,array('AddedIndexes' => array($index_name => $indexes[$index_name]))); 1025 $this->database->debug("Added index '$index_name' to table '$table_name'"); 1026 } 1027 } 1028 foreach($previous_indexes as $index_previous_name => $index_previous) { 1029 if(!isset($defined_indexes[$index_previous_name])) { 1030 $this->_addDefinitionChange($changes, 'INDEXES', $table_name, array('RemovedIndexes' => array($index_previous_name => $was_table_name))); 1031 $this->database->debug("Removed index '$index_name' from table '$table_name'"); 1032 } 1033 } 1034 } else { 1035 if(strcmp($table_name, $was_table_name)) { 1036 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 1037 'it was specified a previous table name ("' 1038 .$was_table_name.'") for table "'.$table_name.'" that does not exist', 1039 'MDB_Error', TRUE)); 1040 } 1041 $this->_addDefinitionChange($changes, 'TABLES', $table_name,array('Add' => 1)); 1042 $this->database->debug("Added table '$table_name'"); 1043 } 1044 } 1045 if(isset($previous_definition['TABLES']) && is_array($previous_definition['TABLES'])) { 1046 foreach ($previous_definition['TABLES'] as $table_name => $table) { 1047 if(!isset($defined_tables[$table_name])) { 1048 $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('Remove' => 1)); 1049 $this->database->debug("Removed table '$table_name'"); 1050 } 1051 } 1052 } 1053 if(isset($this->database_definition['SEQUENCES']) && is_array($this->database_definition['SEQUENCES'])) { 1054 foreach ($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) { 1055 $was_sequence_name = $sequence['was']; 1056 if(isset($previous_definition['SEQUENCES'][$sequence_name]) 1057 && isset($previous_definition['SEQUENCES'][$sequence_name]['was']) 1058 && !strcmp($previous_definition['SEQUENCES'][$sequence_name]['was'], $was_sequence_name) 1059 ) { 1060 $was_sequence_name = $sequence_name; 1061 } 1062 if(isset($previous_definition['SEQUENCES'][$was_sequence_name])) { 1063 if(strcmp($was_sequence_name, $sequence_name)) { 1064 $this->_addDefinitionChange($changes, 'SEQUENCES', $was_sequence_name,array('name' => $sequence_name)); 1065 $this->database->debug("Renamed sequence '$was_sequence_name' to '$sequence_name'"); 1066 } 1067 if(isset($defined_sequences[$was_sequence_name])) { 1068 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 1069 'the sequence "'.$was_sequence_name.'" was specified as base' 1070 .' of more than of sequence of the database', 1071 'MDB_Error', TRUE)); 1072 } 1073 $defined_sequences[$was_sequence_name] = 1; 1074 $change = array(); 1075 if(strcmp($sequence['start'], $previous_definition['SEQUENCES'][$was_sequence_name]['start'])) { 1076 $change['start'] = $this->database_definition['SEQUENCES'][$sequence_name]['start']; 1077 $this->database->debug("Changed sequence '$sequence_name' start from '".$previous_definition['SEQUENCES'][$was_sequence_name]['start']."' to '".$this->database_definition['SEQUENCES'][$sequence_name]['start']."'"); 1078 } 1079 if(strcmp($sequence['on']['table'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['table']) 1080 || strcmp($sequence['on']['field'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['field']) 1081 ) { 1082 $change['on'] = $sequence['on']; 1083 $this->database->debug("Changed sequence '$sequence_name' on table field from '".$previous_definition['SEQUENCES'][$was_sequence_name]['on']['table'].'.'.$previous_definition['SEQUENCES'][$was_sequence_name]['on']['field']."' to '".$this->database_definition['SEQUENCES'][$sequence_name]['on']['table'].'.'.$this->database_definition['SEQUENCES'][$sequence_name]['on']['field']."'"); 1084 } 1085 if(count($change)) { 1086 $this->_addDefinitionChange($changes, 'SEQUENCES', $was_sequence_name,array('Change' => array($sequence_name => array($change)))); 1087 } 1088 } else { 1089 if(strcmp($sequence_name, $was_sequence_name)) { 1090 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL, 1091 'it was specified a previous sequence name ("'.$was_sequence_name 1092 .'") for sequence "'.$sequence_name.'" that does not exist', 1093 'MDB_Error', TRUE)); 1094 } 1095 $this->_addDefinitionChange($changes, 'SEQUENCES', $sequence_name, array('Add' => 1)); 1096 $this->database->debug("Added sequence '$sequence_name'"); 1097 } 1098 } 1099 } 1100 if(isset($previous_definition['SEQUENCES']) && is_array($previous_definition['SEQUENCES'])) { 1101 foreach ($previous_definition['SEQUENCES'] as $sequence_name => $sequence) { 1102 if(!isset($defined_sequences[$sequence_name])) { 1103 $this->_addDefinitionChange($changes, 'SEQUENCES', $sequence_name, array('Remove' => 1)); 1104 $this->database->debug("Removed sequence '$sequence_name'"); 1105 } 1106 } 1107 } 1108 } 1109 return($changes); 1110 } 1111 1112 // }}} 1113 // {{{ _alterDatabase() 1114 1115 /** 1116 * Execute the necessary actions to implement the requested changes 1117 * in a database structure. 1118 * 1119 * @param array $previous_definition an associative array that contains 1120 * the definition of the database structure before applying the requested 1121 * changes. The definition of this array may be built separately, but 1122 * usually it is built by the Parse method the Metabase parser class. 1123 * @param array $changes an associative array that contains the definition of 1124 * the changes that are meant to be applied to the database structure. 1125 * @return mixed MDB_OK on success, or a MDB error object 1126 * @access private 1127 */ 1128 function _alterDatabase($previous_definition, $changes) 1129 { 1130 $result = ''; 1131 if(isset($changes['TABLES']) && is_array($changes['TABLES'])) { 1132 foreach($changes['TABLES'] as $table_name => $table) { 1133 if(isset($table['Add']) || isset($table['Remove'])) { 1134 continue; 1135 } 1136 $result = $this->database->alterTable($table_name, $table, 1); 1137 if(MDB::isError($result)) { 1138 return($result); 1139 } 1140 } 1141 } 1142 if(isset($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) { 1143 if(!$this->database->support('Sequences')) { 1144 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 1145 'sequences are not supported')); 1146 } 1147 foreach($changes['SEQUENCES'] as $sequence) { 1148 if(isset($sequence['Add']) 1149 || isset($sequence['Remove']) 1150 || isset($sequence['Change']) 1151 ) { 1152 continue; 1153 } 1154 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 1155 'some sequences changes are not yet supported')); 1156 } 1157 } 1158 if(isset($changes['INDEXES']) && is_array($changes['INDEXES'])) { 1159 if(!$this->database->support('Indexes')) { 1160 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 1161 'indexes are not supported')); 1162 } 1163 foreach($changes['INDEXES'] as $index) { 1164 $table_changes = count($index); 1165 if(isset($index['AddedIndexes'])) { 1166 $table_changes--; 1167 } 1168 if(isset($index['RemovedIndexes'])) { 1169 $table_changes--; 1170 } 1171 if(isset($index['ChangedIndexes'])) { 1172 $table_changes--; 1173 } 1174 if($table_changes) { 1175 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 1176 'index alteration not yet supported')); 1177 } 1178 } 1179 } 1180 1181 $previous_database_name = $this->database->setDatabase($this->database_definition['name']); 1182 if(($support_transactions = $this->database->support('Transactions')) 1183 && MDB::isError($result = $this->database->autoCommit(FALSE)) 1184 ) { 1185 return($result); 1186 } 1187 $error = ''; 1188 $alterations = 0; 1189 if(isset($changes['INDEXES']) && is_array($changes['INDEXES'])) { 1190 foreach($changes['INDEXES'] as $index_name => $index) { 1191 if(isset($index['RemovedIndexes']) && is_array($index['RemovedIndexes'])) { 1192 foreach($index['RemovedIndexes'] as $index_remove_name => $index_remove) { 1193 $result = $this->database->dropIndex($index_name,$index_remove_name); 1194 if(MDB::isError($result)) { 1195 break; 1196 } 1197 $alterations++; 1198 } 1199 } 1200 if(!MDB::isError($result) 1201 && is_array($index['ChangedIndexes']) 1202 ) { 1203 foreach($index['ChangedIndexes'] as $index_changed_name => $index_changed) { 1204 $was_name = (isset($indexes[$name]['name']) ? $indexes[$index_changed_name]['name'] : $index_changed_name); 1205 $result = $this->database->dropIndex($index_name, $was_name); 1206 if(MDB::isError($result)) { 1207 break; 1208 } 1209 $alterations++; 1210 } 1211 } 1212 if(MDB::isError($result)) { 1213 break; 1214 } 1215 } 1216 } 1217 if(!MDB::isError($result) && isset($changes['TABLES']) 1218 && is_array($changes['TABLES']) 1219 ) { 1220 foreach($changes['TABLES'] as $table_name => $table) { 1221 if(isset($table['Remove'])) { 1222 $result = $this->_dropTable($table_name); 1223 if(!MDB::isError($result)) { 1224 $alterations++; 1225 } 1226 } else { 1227 if(!isset($table['Add'])) { 1228 $result = $this->database->alterTable($table_name, $changes['TABLES'][$table_name], 0); 1229 if(!MDB::isError($result)) { 1230 $alterations++; 1231 } 1232 } 1233 } 1234 if(MDB::isError($result)) { 1235 break; 1236 } 1237 } 1238 foreach($changes['TABLES'] as $table_name => $table) { 1239 if(isset($table['Add'])) { 1240 $result = $this->_createTable($table_name, $this->database_definition['TABLES'][$table_name]); 1241 if(!MDB::isError($result)) { 1242 $alterations++; 1243 } 1244 } 1245 if(MDB::isError($result)) { 1246 break; 1247 } 1248 } 1249 } 1250 if(!MDB::isError($result) && isset($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) { 1251 foreach($changes['SEQUENCES'] as $sequence_name => $sequence) { 1252 if(isset($sequence['Add'])) { 1253 $created_on_table = 0; 1254 if(isset($this->database_definition['SEQUENCES'][$sequence_name]['on'])) { 1255 $table = $this->database_definition['SEQUENCES'][$sequence_name]['on']['table']; 1256 if(isset($changes['TABLES']) 1257 && isset($changes['TABLES'][$table_name]) 1258 && isset($changes['TABLES'][$table_name]['Add']) 1259 ) { 1260 $created_on_table = 1; 1261 } 1262 } 1263 1264 $result = $this->_createSequence($sequence_name, 1265 $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table); 1266 if(!MDB::isError($result)) { 1267 $alterations++; 1268 } 1269 } else { 1270 if(isset($sequence['Remove'])) { 1271 if(!strcmp($error = $this->_dropSequence($sequence_name), '')) { 1272 $alterations++; 1273 } 1274 } else { 1275 if(isset($sequence['Change'])) { 1276 $created_on_table = 0; 1277 if(isset($this->database_definition['SEQUENCES'][$sequence_name]['on'])) { 1278 $table = $this->database_definition['SEQUENCES'][$sequence_name]['on']['table']; 1279 if(isset($changes['TABLES']) 1280 && isset($changes['TABLES'][$table_name]) 1281 && isset($changes['TABLES'][$table_name]['Add']) 1282 ) { 1283 $created_on_table = 1; 1284 } 1285 } 1286 if(!MDB::isError($result = $this->_dropSequence( 1287 $this->database_definition['SEQUENCES'][$sequence_name]['was']), '') 1288 && !MDB::isError($result = $this->_createSequence( 1289 $sequence_name, $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table), '') 1290 ) { 1291 $alterations++; 1292 } 1293 } else { 1294 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, 1295 'changing sequences is not yet supported')); 1296 } 1297 } 1298 } 1299 if(MDB::isError($result)) { 1300 break; 1301 } 1302 } 1303 } 1304 if(!MDB::isError($result) && isset($changes['INDEXES']) && is_array($changes['INDEXES'])) { 1305 foreach($changes['INDEXES'] as $table_name => $indexes) { 1306 if(isset($indexes['ChangedIndexes'])) { 1307 $changedindexes = $indexes['ChangedIndexes']; 1308 foreach($changedindexes as $index_name => $index) { 1309 $result = $this->database->createIndex($table_name, $index_name, 1310 $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name]); 1311 if(MDB::isError($result)) { 1312 break; 1313 } 1314 $alterations++; 1315 } 1316 } 1317 if(!MDB::isError($result) 1318 && isset($indexes['AddedIndexes']) 1319 ) { 1320 $addedindexes = $indexes['AddedIndexes']; 1321 foreach($addedindexes as $index_name => $index) { 1322 $result = $this->database->createIndex($table_name, $index_name, 1323 $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name]); 1324 if(MDB::isError($result)) { 1325 break; 1326 } 1327 $alterations++; 1328 } 1329 } 1330 if(MDB::isError($result)) { 1331 break; 1332 } 1333 } 1334 } 1335 if($alterations && MDB::isError($result)) { 1336 if($support_transactions) { 1337 $res = $this->database->rollback(); 1338 if(MDB::isError($res)) 1339 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 1340 'Could not rollback the partially created database alterations (' 1341 .$result->getMessage().' ('.$result->getUserinfo(),'))', 1342 'MDB_Error', TRUE); 1343 } else { 1344 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 1345 'the requested database alterations were only partially implemented (' 1346 .$result->getMessage().' ('.$result->getUserinfo(),'))', 1347 'MDB_Error', TRUE); 1348 } 1349 } 1350 if($support_transactions) { 1351 $result = $this->database->autoCommit(TRUE); 1352 if(MDB::isError($result)) { 1353 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 1354 'Could not end transaction after successfully implemented the requested database alterations (' 1355 .$result->getMessage().' ('.$result->getUserinfo(),'))', 1356 'MDB_Error', TRUE); 1357 } 1358 } 1359 $this->database->setDatabase($previous_database_name); 1360 return($result); 1361 } 1362 1363 // }}} 1364 // {{{ _escapeSpecialCharacters() 1365 1366 /** 1367 * add escapecharacters to all special characters in a string 1368 * 1369 * @param string $string string that should be escaped 1370 * @return string escaped string 1371 * @access private 1372 */ 1373 function _escapeSpecialCharacters($string) 1374 { 1375 if(gettype($string) != 'string') { 1376 $string = strval($string); 1377 } 1378 for($escaped = '', $character = 0; 1379 $character < strlen($string); 1380 $character++) 1381 { 1382 switch($string[$character]) { 1383 case '\"': 1384 case '>': 1385 case '<': 1386 case '&': 1387 $code = ord($string[$character]); 1388 break; 1389 default: 1390 $code = ord($string[$character]); 1391 if($code < 32 || $code>127) { 1392 break; 1393 } 1394 $escaped .= $string[$character]; 1395 continue 2; 1396 } 1397 $escaped .= "&#$code;"; 1398 } 1399 return($escaped); 1400 } 1401 1402 // }}} 1403 // {{{ _dumpSequence() 1404 1405 /** 1406 * dump the structure of a sequence 1407 * 1408 * @param string $sequence_name 1409 * @param string $eol 1410 * @return mixed string with xml seqeunce definition on success, or a MDB error object 1411 * @access private 1412 */ 1413 function _dumpSequence($sequence_name, $eol, $dump = MDB_MANAGER_DUMP_ALL) 1414 { 1415 $sequence_definition = $this->database_definition['SEQUENCES'][$sequence_name]; 1416 $buffer = "$eol <sequence>$eol <name>$sequence_name</name>$eol"; 1417 if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_CONTENT) { 1418 if(isset($sequence_definition['start'])) { 1419 $start = $sequence_definition['start']; 1420 $buffer .= " <start>$start</start>$eol"; 1421 } 1422 } 1423 if(isset($sequence_definition['on'])) { 1424 $buffer .= " <on>$eol <table>".$sequence_definition['on']['table']."</table>$eol <field>".$sequence_definition['on']['field']."</field>$eol </on>$eol"; 1425 } 1426 $buffer .= " </sequence>$eol"; 1427 return($buffer); 1428 } 1429 1430 // }}} 1431 // {{{ parseDatabaseDefinitionFile() 1432 1433 /** 1434 * Parse a database definition file by creating a Metabase schema format 1435 * parser object and passing the file contents as parser input data stream. 1436 * 1437 * @param string $input_file the path of the database schema file. 1438 * @param array $variables an associative array that the defines the text 1439 * string values that are meant to be used to replace the variables that are 1440 * used in the schema description. 1441 * @param bool $fail_on_invalid_names (optional) make function fail on invalid 1442 * names 1443 * @return mixed MDB_OK on success, or a MDB error object 1444 * @access public 1445 */ 1446 function parseDatabaseDefinitionFile($input_file, $variables, $fail_on_invalid_names = 1) 1447 { 1448 $parser =& new MDB_Parser($variables, $fail_on_invalid_names); 1449 $result = $parser->setInputFile($input_file); 1450 if(MDB::isError($result)) { 1451 return($result); 1452 }; 1453 $result = $parser->parse(); 1454 if(MDB::isError($result)) { 1455 return($result); 1456 }; 1457 if(MDB::isError($parser->error)) { 1458 return($parser->error); 1459 } 1460 return($parser->database_definition); 1461 } 1462 1463 // }}} 1464 // {{{ _debugDatabaseChanges() 1465 1466 /** 1467 * Dump the changes between two database definitions. 1468 * 1469 * @param array $changes an associative array that specifies the list 1470 * of database definitions changes as returned by the _compareDefinitions 1471 * manager class function. 1472 * @return mixed MDB_OK on success, or a MDB error object 1473 * @access private 1474 */ 1475 function _debugDatabaseChanges($changes) 1476 { 1477 if(isset($changes['TABLES'])) { 1478 foreach($changes['TABLES'] as $table_name => $table) 1479 { 1480 $this->database->debug("$table_name:"); 1481 if(isset($table['Add'])) { 1482 $this->database->debug("\tAdded table '$table_name'"); 1483 } elseif(isset($table['Remove'])) { 1484 $this->database->debug("\tRemoved table '$table_name'"); 1485 } else { 1486 if(isset($table['name'])) { 1487 $this->database->debug("\tRenamed table '$table_name' to '".$table['name']."'"); 1488 } 1489 if(isset($table['AddedFields'])) { 1490 foreach($table['AddedFields'] as $field_name => $field) { 1491 $this->database->debug("\tAdded field '".$field_name."'"); 1492 } 1493 } 1494 if(isset($table['RemovedFields'])) { 1495 foreach($table['RemovedFields'] as $field_name => $field) { 1496 $this->database->debug("\tRemoved field '".$field_name."'"); 1497 } 1498 } 1499 if(isset($table['RenamedFields'])) { 1500 foreach($table['RenamedFields'] as $field_name => $field) { 1501 $this->database->debug("\tRenamed field '".$field_name."' to '".$field['name']."'"); 1502 } 1503 } 1504 if(isset($table['ChangedFields'])) { 1505 foreach($table['ChangedFields'] as $field_name => $field) { 1506 if(isset($field['type'])) { 1507 $this->database->debug( 1508 "\tChanged field '$field_name' type to '".$field['type']."'"); 1509 } 1510 if(isset($field['unsigned'])) { 1511 $this->database->debug( 1512 "\tChanged field '$field_name' type to '". 1513 ($field['unsigned'] ? '' : 'not ')."unsigned'"); 1514 } 1515 if(isset($field['length'])) { 1516 $this->database->debug( 1517 "\tChanged field '$field_name' length to '". 1518 ($field['length'] == 0 ? 'no length' : $field['length'])."'"); 1519 } 1520 if(isset($field['ChangedDefault'])) { 1521 $this->database->debug( 1522 "\tChanged field '$field_name' default to ". 1523 (isset($field['default']) ? "'".$field['default']."'" : 'NULL')); 1524 } 1525 if(isset($field['ChangedNotNull'])) { 1526 $this->database->debug( 1527 "\tChanged field '$field_name' notnull to ".(isset($field['notnull']) ? "'1'" : '0')); 1528 } 1529 } 1530 } 1531 } 1532 } 1533 } 1534 if(isset($changes['SEQUENCES'])) { 1535 foreach($changes['SEQUENCES'] as $sequence_name => $sequence) 1536 { 1537 $this->database->debug("$sequence_name:"); 1538 if(isset($sequence['Add'])) { 1539 $this->database->debug("\tAdded sequence '$sequence_name'"); 1540 } elseif(isset($sequence['Remove'])) { 1541 $this->database->debug("\tRemoved sequence '$sequence_name'"); 1542 } else { 1543 if(isset($sequence['name'])) { 1544 $this->database->debug("\tRenamed sequence '$sequence_name' to '".$sequence['name']."'"); 1545 } 1546 if(isset($sequence['Change'])) { 1547 foreach($sequence['Change'] as $sequence_name => $sequence) { 1548 if(isset($sequence['start'])) { 1549 $this->database->debug( 1550 "\tChanged sequence '$sequence_name' start to '".$sequence['start']."'"); 1551 } 1552 } 1553 } 1554 } 1555 } 1556 } 1557 if(isset($changes['INDEXES'])) { 1558 foreach($changes['INDEXES'] as $table_name => $table) 1559 { 1560 $this->database->debug("$table_name:"); 1561 if(isset($table['AddedIndexes'])) { 1562 foreach($table['AddedIndexes'] as $index_name => $index) { 1563 $this->database->debug("\tAdded index '".$index_name."' of table '$table_name'"); 1564 } 1565 } 1566 if(isset($table['RemovedIndexes'])) { 1567 foreach($table['RemovedIndexes'] as $index_name => $index) { 1568 $this->database->debug("\tRemoved index '".$index_name."' of table '$table_name'"); 1569 } 1570 } 1571 if(isset($table['ChangedIndexes'])) { 1572 foreach($table['ChangedIndexes'] as $index_name => $index) { 1573 if(isset($index['name'])) { 1574 $this->database->debug( 1575 "\tRenamed index '".$index_name."' to '".$index['name']."' on table '$table_name'"); 1576 } 1577 if(isset($index['ChangedUnique'])) { 1578 $this->database->debug( 1579 "\tChanged index '".$index_name."' unique to '". 1580 isset($index['unique'])."' on table '$table_name'"); 1581 } 1582 if(isset($index['ChangedFields'])) { 1583 $this->database->debug("\tChanged index '".$index_name."' on table '$table_name'"); 1584 } 1585 } 1586 } 1587 } 1588 } 1589 return(MDB_OK); 1590 } 1591 1592 // }}} 1593 // {{{ _dumpDatabaseContents() 1594 1595 /** 1596 * Parse a database schema definition file and dump the respective structure 1597 * and contents. 1598 * 1599 * @param string $schema_file path of the database schema file. 1600 * @param mixed $setup_arguments an associative array that takes pairs of tag names and values 1601 * that define the setup arguments that are passed to the 1602 * MDB_Manager::connect function. 1603 * @param array $dump_arguments an associative array that takes pairs of tag names and values 1604 * that define dump options as defined for the MDB_Manager::DumpDatabase 1605 * function. 1606 * @param array $variables an associative array that the defines the text string values 1607 * that are meant to be used to replace the variables that are used in the 1608 * schema description as defined for the 1609 * MDB_Manager::parseDatabaseDefinitionFile function. 1610 * @return mixed MDB_OK on success, or a MDB error object 1611 * @access private 1612 */ 1613 function _dumpDatabaseContents($schema_file, $setup_arguments, $dump_arguments, $variables) 1614 { 1615 $database_definition = $this->parseDatabaseDefinitionFile($schema_file, 1616 $variables, $this->options['fail_on_invalid_names']); 1617 if(MDB::isError($database_definition)) { 1618 return($database_definition); 1619 } 1620 1621 $this->database_definition = $database_definition; 1622 1623 $result = $this->connect($setup_arguments); 1624 if(MDB::isError($result)) { 1625 return($result); 1626 } 1627 1628 return($this->dumpDatabase($dump_arguments)); 1629 } 1630 1631 // }}} 1632 // {{{ getDefinitionFromDatabase() 1633 1634 /** 1635 * Attempt to reverse engineer a schema structure from an existing MDB 1636 * This method can be used if no xml schema file exists yet. 1637 * The resulting xml schema file may need some manual adjustments. 1638 * 1639 * @return mixed MDB_OK or array with all ambiguities on success, or a MDB error object 1640 * @access public 1641 */ 1642 function getDefinitionFromDatabase() 1643 { 1644 $database = $this->database->database_name; 1645 if(strlen($database) == 0) { 1646 return('it was not specified a valid database name'); 1647 } 1648 $this->database_definition = array( 1649 'name' => $database, 1650 'create' => 1, 1651 'TABLES' => array() 1652 ); 1653 $tables = $this->database->listTables(); 1654 if(MDB::isError($tables)) { 1655 return($tables); 1656 } 1657 for($table = 0; $table < count($tables); $table++) { 1658 $table_name = $tables[$table]; 1659 $fields = $this->database->listTableFields($table_name); 1660 if(MDB::isError($fields)) { 1661 return($fields); 1662 } 1663 $this->database_definition['TABLES'][$table_name] = array('FIELDS' => array()); 1664 for($field = 0; $field < count($fields); $field++) 1665 { 1666 $field_name = $fields[$field]; 1667 $definition = $this->database->getTableFieldDefinition($table_name, $field_name); 1668 if(MDB::isError($definition)) { 1669 return($definition); 1670 } 1671 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name] = $definition[0][0]; 1672 $field_choices = count($definition[0]); 1673 if($field_choices > 1) { 1674 $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): "; 1675 $field_choice_cnt = 1; 1676 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'] = array(); 1677 foreach($definition[0] as $field_choice) { 1678 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'][] = $field_choice; 1679 $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice); 1680 $field_choice_cnt++; 1681 } 1682 $this->warnings[] = $warning; 1683 } 1684 if(isset($definition[1])) { 1685 $sequence = $definition[1]['definition']; 1686 $sequence_name = $definition[1]['name']; 1687 $this->database->debug('Implicitly defining sequence: '.$sequence_name); 1688 if(!isset($this->database_definition['SEQUENCES'])) { 1689 $this->database_definition['SEQUENCES'] = array(); 1690 } 1691 $this->database_definition['SEQUENCES'][$sequence_name] = $sequence; 1692 } 1693 if(isset($definition[2])) { 1694 $index = $definition[2]['definition']; 1695 $index_name = $definition[2]['name']; 1696 $this->database->debug('Implicitly defining index: '.$index_name); 1697 if(!isset($this->database_definition['TABLES'][$table_name]['INDEXES'])) { 1698 $this->database_definition['TABLES'][$table_name]['INDEXES'] = array(); 1699 } 1700 $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name] = $index; 1701 } 1702 } 1703 $indexes = $this->database->listTableIndexes($table_name); 1704 if(MDB::isError($indexes)) { 1705 return($indexes); 1706 } 1707 if(is_array($indexes) && count($indexes) > 0 && !isset($this->database_definition['TABLES'][$table_name]['INDEXES'])) { 1708 $this->database_definition['TABLES'][$table_name]['INDEXES'] = array(); 1709 } 1710 for($index = 0, $index_cnt = count($indexes); $index < $index_cnt; $index++) 1711 { 1712 $index_name = $indexes[$index]; 1713 $definition = $this->database->getTableIndexDefinition($table_name, $index_name); 1714 if(MDB::isError($definition)) { 1715 return($definition); 1716 } 1717 $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name] = $definition; 1718 } 1719 // ensure that all fields that have an index on them are set to not null 1720 if(isset($this->database_definition['TABLES'][$table_name]['INDEXES']) 1721 && is_array($this->database_definition['TABLES'][$table_name]['INDEXES']) 1722 && count($this->database_definition['TABLES'][$table_name]['INDEXES']) > 0 1723 ) { 1724 foreach($this->database_definition['TABLES'][$table_name]['INDEXES'] as $index_check_null) { 1725 foreach($index_check_null['FIELDS'] as $field_name_check_null => $field_check_null) { 1726 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name_check_null]['notnull'] = 1; 1727 } 1728 } 1729 } 1730 // ensure that all fields that are set to not null also have a default value 1731 if(is_array($this->database_definition['TABLES'][$table_name]['FIELDS']) 1732 && count($this->database_definition['TABLES'][$table_name]['FIELDS']) > 0 1733 ) { 1734 foreach($this->database_definition['TABLES'][$table_name]['FIELDS'] as $field_set_default_name => $field_set_default) { 1735 if(isset($field_set_default['notnull']) && $field_set_default['notnull'] 1736 && !isset($field_set_default['default']) 1737 ) { 1738 if(isset($this->default_values[$field_set_default['type']])) { 1739 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = $this->default_values[$field_set_default['type']]; 1740 } else { 1741 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = 0; 1742 } 1743 } 1744 if(isset($field_set_default['CHOICES']) && is_array($field_set_default['CHOICES'])) { 1745 foreach($field_set_default['CHOICES'] as $field_choices_set_default_name => $field_choices_set_default) { 1746 if(isset($field_choices_set_default['notnull']) 1747 && $field_choices_set_default['notnull'] 1748 && !isset($field_choices_set_default['default']) 1749 ) { 1750 if(isset($this->default_values[$field_choices_set_default['type']])) { 1751 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES'] 1752 [$field_choices_set_default_name]['default'] = $this->default_values[$field_choices_set_default['type']]; 1753 } else { 1754 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES'] 1755 [$field_choices_set_default_name]['default'] = 0; 1756 } 1757 } 1758 } 1759 } 1760 } 1761 } 1762 } 1763 $sequences = $this->database->listSequences(); 1764 if(MDB::isError($sequences)) { 1765 return($sequences); 1766 } 1767 if(is_array($sequences) && count($sequences) > 0 && !isset($this->database_definition['SEQUENCES'])) { 1768 $this->database_definition['SEQUENCES'] = array(); 1769 } 1770 for($sequence = 0; $sequence < count($sequences); $sequence++) { 1771 $sequence_name = $sequences[$sequence]; 1772 $definition = $this->database->getSequenceDefinition($sequence_name); 1773 if(MDB::isError($definition)) { 1774 return($definition); 1775 } 1776 $this->database_definition['SEQUENCES'][$sequence_name] = $definition; 1777 } 1778 return(MDB_OK); 1779 } 1780 1781 // }}} 1782 // {{{ dumpDatabase() 1783 1784 /** 1785 * Dump a previously parsed database structure in the Metabase schema 1786 * XML based format suitable for the Metabase parser. This function 1787 * may optionally dump the database definition with initialization 1788 * commands that specify the data that is currently present in the tables. 1789 * 1790 * @param array $arguments an associative array that takes pairs of tag 1791 * names and values that define dump options. 1792 * array ( 1793 * 'Definition' => Boolean 1794 * TRUE : dump currently parsed definition 1795 * default: dump currently connected database 1796 * 'Output_Mode' => String 1797 * 'file' : dump into a file 1798 * default: dump using a function 1799 * 'Output' => String 1800 * depending on the 'Output_Mode' 1801 * name of the file 1802 * name of the function 1803 * 'EndOfLine' => String 1804 * end of line delimiter that should be used 1805 * default: "\n" 1806 * ); 1807 * @param integer $dump constant that determines what data to dump 1808 * MDB_MANAGER_DUMP_ALL : the entire db 1809 * MDB_MANAGER_DUMP_STRUCTURE : only the structure of the db 1810 * MDB_MANAGER_DUMP_CONTENT : only the content of the db 1811 * @return mixed MDB_OK on success, or a MDB error object 1812 * @access public 1813 */ 1814 function dumpDatabase($arguments, $dump = MDB_MANAGER_DUMP_ALL) 1815 { 1816 if(isset($arguments['Definition']) && $arguments['Definition']) { 1817 $dump_definition = TRUE; 1818 } else { 1819 if(!$this->database) { 1820 return($this->raiseError(MDB_ERROR_NODBSELECTED, 1821 NULL, NULL, 'please connect to a RDBMS first')); 1822 } 1823 $error = $this->getDefinitionFromDatabase(); 1824 if(MDB::isError($error)) { 1825 return($error); 1826 } 1827 $dump_definition = FALSE; 1828 } 1829 if(isset($arguments['Output'])) { 1830 if(isset($arguments['Output_Mode']) && $arguments['Output_Mode'] == 'file') { 1831 $fp = fopen($arguments['Output'], 'w'); 1832 $output = FALSE; 1833 } elseif(function_exists($arguments['Output'])) { 1834 $output = $arguments['Output']; 1835 } else { 1836 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 1837 'no valid output function specified')); 1838 } 1839 } else { 1840 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 1841 'no output method specified')); 1842 } 1843 if(isset($arguments['EndOfLine'])) { 1844 $eol = $arguments['EndOfLine']; 1845 } else { 1846 $eol = "\n"; 1847 } 1848 1849 $sequences = array(); 1850 if(isset($this->database_definition['SEQUENCES']) 1851 && is_array($this->database_definition['SEQUENCES']) 1852 ) { 1853 foreach($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) { 1854 if(isset($sequence['on'])) { 1855 $table = $sequence['on']['table']; 1856 } else { 1857 $table = ''; 1858 } 1859 $sequences[$table][] = $sequence_name; 1860 } 1861 } 1862 $previous_database_name = (strcmp($this->database_definition['name'], '') ? $this->database->setDatabase($this->database_definition['name']) : ''); 1863 $buffer = ('<?xml version="1.0" encoding="ISO-8859-1" ?>'.$eol); 1864 $buffer .= ("<database>$eol$eol <name>".$this->database_definition['name']."</name>$eol <create>".$this->database_definition['create']."</create>$eol"); 1865 1866 if($output) { 1867 $output($buffer); 1868 } else { 1869 fwrite($fp, $buffer); 1870 } 1871 $buffer = ''; 1872 if(isset($this->database_definition['TABLES']) && is_array($this->database_definition['TABLES'])) { 1873 foreach($this->database_definition['TABLES'] as $table_name => $table) { 1874 $buffer = ("$eol <table>$eol$eol <name>$table_name</name>$eol"); 1875 if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_STRUCTURE) { 1876 $buffer .= ("$eol <declaration>$eol"); 1877 if(isset($table['FIELDS']) && is_array($table['FIELDS'])) { 1878 foreach($table['FIELDS'] as $field_name => $field) { 1879 if(!isset($field['type'])) { 1880 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 1881 'it was not specified the type of the field "'.$field_name.'" of the table "'.$table_name)); 1882 } 1883 $buffer .=("$eol <field>$eol <name>$field_name</name>$eol <type>".$field['type']."</type>$eol"); 1884 if(in_array($field_name, array_keys($this->invalid_names))) { 1885 $this->warnings[] = "invalid field name: $field_name. You will need to set the class var \$fail_on_invalid_names to FALSE or change the field name."; 1886 } 1887 switch($field['type']) { 1888 case 'integer': 1889 if(isset($field['unsigned'])) { 1890 $buffer .=(" <unsigned>1</unsigned>$eol"); 1891 } 1892 break; 1893 case 'text': 1894 case 'clob': 1895 case 'blob': 1896 if(isset($field['length'])) { 1897 $buffer .=(' <length>'.$field['length']."</length>$eol"); 1898 } 1899 break; 1900 case 'boolean': 1901 case 'date': 1902 case 'timestamp': 1903 case 'time': 1904 case 'float': 1905 case 'decimal': 1906 break; 1907 default: 1908 return('type "'.$field['type'].'" is not yet supported'); 1909 } 1910 if(isset($field['notnull'])) { 1911 $buffer .=(" <notnull>1</notnull>$eol"); 1912 } 1913 if(isset($field['default'])) { 1914 $buffer .=(' <default>'.$this->_escapeSpecialCharacters($field['default'])."</default>$eol"); 1915 } 1916 $buffer .=(" </field>$eol"); 1917 } 1918 } 1919 if(isset($table['INDEXES']) && is_array($table['INDEXES'])) { 1920 foreach($table['INDEXES'] as $index_name => $index) { 1921 $buffer .=("$eol <index>$eol <name>$index_name</name>$eol"); 1922 if(isset($index['unique'])) { 1923 $buffer .=(" <unique>1</unique>$eol"); 1924 } 1925 foreach($index['FIELDS'] as $field_name => $field) { 1926 $buffer .=(" <field>$eol <name>$field_name</name>$eol"); 1927 if(is_array($field) && isset($field['sorting'])) { 1928 $buffer .=(' <sorting>'.$field['sorting']."</sorting>$eol"); 1929 } 1930 $buffer .=(" </field>$eol"); 1931 } 1932 $buffer .=(" </index>$eol"); 1933 } 1934 } 1935 $buffer .= ("$eol </declaration>$eol"); 1936 } 1937 if($output) { 1938 $output($buffer); 1939 } else { 1940 fwrite($fp, $buffer); 1941 } 1942 $buffer = ''; 1943 if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_CONTENT) { 1944 if($dump_definition) { 1945 if(isset($table['initialization']) && is_array($table['initialization'])) { 1946 $buffer = ("$eol <initialization>$eol"); 1947 foreach($table['initialization'] as $instruction_name => $instruction) { 1948 switch($instruction['type']) { 1949 case 'insert': 1950 $buffer .= ("$eol <insert>$eol"); 1951 foreach($instruction['FIELDS'] as $field_name => $field) { 1952 $buffer .= ("$eol <field>$eol <name>$field_name</name>$eol <value>".$this->_escapeSpecialCharacters($field)."</value>$eol </field>$eol"); 1953 } 1954 $buffer .= ("$eol </insert>$eol"); 1955 break; 1956 } 1957 } 1958 $buffer .= ("$eol </initialization>$eol"); 1959 } 1960 } else { 1961 $types = array(); 1962 foreach($table['FIELDS'] as $field) { 1963 $types[] = $field['type']; 1964 } 1965 $query = 'SELECT '.implode(',',array_keys($table['FIELDS']))." FROM $table_name"; 1966 $result = $this->database->queryAll($query, $types, MDB_FETCHMODE_ASSOC); 1967 if(MDB::isError($result)) { 1968 return($result); 1969 } 1970 $rows = count($result); 1971 if($rows > 0) { 1972 $buffer = ("$eol <initialization>$eol"); 1973 if($output) { 1974 $output($buffer); 1975 } else { 1976 fwrite($fp, $buffer); 1977 } 1978 1979 for($row = 0; $row < $rows; $row++) { 1980 $buffer = ("$eol <insert>$eol"); 1981 $values = $result[$row]; 1982 if(!is_array($values)) { 1983 break; 1984 } else { 1985 foreach($values as $field_name => $field) { 1986 $buffer .= ("$eol <field>$eol <name>$field_name</name>$eol <value>"); 1987 $buffer .= $this->_escapeSpecialCharacters($values[$field_name]); 1988 $buffer .= ("</value>$eol </field>$eol"); 1989 } 1990 } 1991 $buffer .= ("$eol </insert>$eol"); 1992 if($output) { 1993 $output($buffer); 1994 } else { 1995 fwrite($fp, $buffer); 1996 } 1997 $buffer = ''; 1998 } 1999 $buffer = ("$eol </initialization>$eol"); 2000 if($output) { 2001 $output($buffer); 2002 } else { 2003 fwrite($fp, $buffer); 2004 } 2005 $buffer = ''; 2006 } 2007 } 2008 } 2009 $buffer .= ("$eol </table>$eol"); 2010 if($output) { 2011 $output($buffer); 2012 } else { 2013 fwrite($fp, $buffer); 2014 } 2015 if(isset($sequences[$table_name])) { 2016 for($sequence = 0, $j = count($sequences[$table_name]); 2017 $sequence < $j; 2018 $sequence++) 2019 { 2020 $result = $this->_dumpSequence($sequences[$table_name][$sequence], $eol, $dump); 2021 if(MDB::isError($result)) { 2022 return($result); 2023 } 2024 if($output) { 2025 $output($result); 2026 } else { 2027 fwrite($fp, $result); 2028 } 2029 } 2030 } 2031 } 2032 } 2033 if(isset($sequences[''])) { 2034 for($sequence = 0; 2035 $sequence < count($sequences['']); 2036 $sequence++) 2037 { 2038 $result = $this->_dumpSequence($sequences[''][$sequence], $eol, $dump); 2039 if(MDB::isError($result)) { 2040 return($result); 2041 } 2042 if($output) { 2043 $output($result); 2044 } else { 2045 fwrite($fp, $result); 2046 } 2047 } 2048 } 2049 2050 $buffer = ("$eol</database>$eol"); 2051 if($output) { 2052 $output($buffer); 2053 } else { 2054 fwrite($fp, $buffer); 2055 fclose($fp); 2056 } 2057 2058 if(strcmp($previous_database_name, '')) { 2059 $this->database->setDatabase($previous_database_name); 2060 } 2061 return(MDB_OK); 2062 } 2063 2064 // }}} 2065 // {{{ updateDatabase() 2066 2067 /** 2068 * Compare the correspondent files of two versions of a database schema 2069 * definition: the previously installed and the one that defines the schema 2070 * that is meant to update the database. 2071 * If the specified previous definition file does not exist, this function 2072 * will create the database from the definition specified in the current 2073 * schema file. 2074 * If both files exist, the function assumes that the database was previously 2075 * installed based on the previous schema file and will update it by just 2076 * applying the changes. 2077 * If this function succeeds, the contents of the current schema file are 2078 * copied to replace the previous schema file contents. Any subsequent schema 2079 * changes should only be done on the file specified by the $current_schema_file 2080 * to let this function make a consistent evaluation of the exact changes that 2081 * need to be applied. 2082 * 2083 * @param string $current_schema_file name of the updated database schema 2084 * definition file. 2085 * @param string $previous_schema_file name the previously installed database 2086 * schema definition file. 2087 * @param array $variables an associative array that is passed to the argument 2088 * of the same name to the parseDatabaseDefinitionFile function. (there third 2089 * param) 2090 * @return mixed MDB_OK on success, or a MDB error object 2091 * @access public 2092 */ 2093 function updateDatabase($current_schema_file, $previous_schema_file = FALSE, $variables = array()) 2094 { 2095 $database_definition = $this->parseDatabaseDefinitionFile($current_schema_file, 2096 $variables, $this->options['fail_on_invalid_names']); 2097 if(MDB::isError($database_definition)) { 2098 return($database_definition); 2099 } 2100 $this->database_definition = $database_definition; 2101 $copy = 0; 2102/* 2103 $this->expectError(MDB_ERROR_UNSUPPORTED); 2104 $databases = $this->database->listDatabases(); 2105 $this->popExpect(); 2106 if((MDB::isError($databases) || (is_array($databases) && in_array($this->database_definition['name'], $databases))) 2107 && $previous_schema_file && file_exists($previous_schema_file)) 2108 { 2109*/ 2110 if($previous_schema_file && file_exists($previous_schema_file)) { 2111 $previous_definition = $this->parseDatabaseDefinitionFile($previous_schema_file, $variables, 0); 2112 if(MDB::isError($previous_definition)) { 2113 return($previous_definition); 2114 } 2115 $changes = $this->_compareDefinitions($previous_definition); 2116 if(MDB::isError($changes)) { 2117 return($changes); 2118 } 2119 if(isset($changes) && is_array($changes)) { 2120 $result = $this->_alterDatabase($previous_definition, $changes); 2121 if(MDB::isError($result)) { 2122 return($result); 2123 } 2124 $copy = 1; 2125 if($this->options['debug']) { 2126 $result = $this->_debugDatabaseChanges($changes); 2127 if(MDB::isError($result)) { 2128 return($result); 2129 } 2130 } 2131 } 2132 } else { 2133 $result = $this->_createDatabase(); 2134 if(MDB::isError($result)) { 2135 return($result); 2136 } 2137 $copy = 1; 2138 } 2139 if($copy && $previous_schema_file && !copy($current_schema_file, $previous_schema_file)) { 2140 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL, 2141 'Could not copy the new database definition file to the current file')); 2142 } 2143 return(MDB_OK); 2144 } 2145 2146 // }}} 2147} 2148?> 2149