Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
 
48
require_once('MDB/Parser.php');
49
 
50
define('MDB_MANAGER_DUMP_ALL',          0);
51
define('MDB_MANAGER_DUMP_STRUCTURE',    1);
52
define('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
 */
63
class 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
?>