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