Completed
Push — master ( fe4c2e...b05117 )
by Michael
12s
created

Tables::addColumn()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 28
Code Lines 17

Duplication

Lines 5
Ratio 17.86 %

Code Coverage

Tests 12
CRAP Score 6.105

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 5
loc 28
rs 8.439
ccs 12
cts 14
cp 0.8571
cc 6
eloc 17
nc 4
nop 3
crap 6.105
1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 */
11
12
namespace Xmf\Database;
13
14
use Doctrine\DBAL\Driver\Statement;
15
use Xmf\Language;
16
use Xoops\Core\Database\Connection;
17
use Xoops\Core\Database\Factory;
18
19
/**
20
 * Xmf\Database\Tables
21
 *
22
 * inspired by Yii CDbMigration
23
 *
24
 * Build a work queue of database changes needed to implement new and
25
 * changed tables. Define table(s) you are dealing with and any desired
26
 * change(s). If the changes are already in place (i.e. the new column
27
 * already exists) no work is added. Then executeQueue() to process the
28
 * whole set.
29
 *
30
 * @category  Xmf\Database\Tables
31
 * @package   Xmf
32
 * @author    Richard Griffith <[email protected]>
33
 * @copyright 2011-2016 XOOPS Project (http://xoops.org)
34
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
35
 * @version   Release: 1.0
36
 * @link      http://xoops.org
37
 * @since     1.0
38
 */
39
class Tables
40
{
41
    /**
42
     * @var Connection
43
     */
44
    protected $db;
45
46
    /**
47
     * @var string
48
     */
49
    protected $databaseName;
50
51
    /**
52
     * @var array Tables
53
     */
54
    protected $tables;
55
56
    /**
57
     * @var array Work queue
58
     */
59
    protected $queue;
60
61
    /**
62
     * @var string last error message
63
     */
64
    protected $lastError;
65
66
    /**
67
     * @var int last error number
68
     */
69
    protected $lastErrNo;
70
71
    /**
72
     * Constructor
73
     *
74
     */
75
    public function __construct()
76
    {
77
        Language::load('database', 'xmf');
78
79
        $this->db = Factory::getConnection();
80
        $this->databaseName = \XoopsBaseConfig::get('db-name');
81
        $this->resetQueue();
82
    }
83
84
    /**
85
     * Return a table name, prefixed with site table prefix
86
     *
87
     * @param string $table table name to contain prefix
88
     *
89
     * @return string table name with prefix
90
     */
91 1
    protected function name($table)
92
    {
93 1
        return $this->db->prefix($table);
94
    }
95
96
    /**
97
     * Add new column for table to the work queue
98
     *
99
     * @param string $table      table to contain the column
100
     * @param string $column     name of column to add
101
     * @param string $attributes column_definition
102
     *
103
     * @return bool true if no errors, false if errors encountered
104
     */
105 1
    public function addColumn($table, $column, $attributes)
106
    {
107
        $columnDef = array(
108 1
            'name' => $column,
109 1
            'attributes' => $attributes
110
        );
111
112
        // Find table def.
113 1
        if (isset($this->tables[$table])) {
114 1
            $tableDef = &$this->tables[$table];
115
            // Is this on a table we are adding?
116 1
            if (isset($tableDef['create']) && $tableDef['create']) {
117
                array_push($tableDef['columns'], $columnDef);
118
            } else {
119 1 View Code Duplication
                foreach ($tableDef['columns'] as $col) {
120 1
                    if (strcasecmp($col['name'], $column) == 0) {
121 1
                        return true;
122
                    }
123
                }
124 1
                $this->queue[] = "ALTER TABLE `{$tableDef['name']}`"
125 1
                    . " ADD COLUMN `{$column}` {$columnDef['attributes']}";
126
            }
127
        } else {
128
            return $this->tableNotEstablished();
129
        }
130
131 1
        return true; // exists or is added to queue
132
    }
133
134
    /**
135
     * Add new primary key definition for table to work queue
136
     *
137
     * @param string $table  table
138
     * @param string $column column or comma separated list of columns
139
     *                       to use as primary key
140
     *
141
     * @return bool true if no errors, false if errors encountered
142
     */
143 1
    public function addPrimaryKey($table, $column)
144
    {
145 1
        $columns = str_getcsv(str_replace(' ', '', $column));
146 1
        $columnList = '';
147 1
        $firstComma = '';
148 1
        foreach ($columns as $col) {
149 1
            $columnList .= "{$firstComma}`{$col}`";
150 1
            $firstComma = ', ';
151
        }
152 1
        if (isset($this->tables[$table])) {
153 1
            if (isset($this->tables[$table]['create']) && $this->tables[$table]['create']) {
154
                $this->tables[$table]['keys']['PRIMARY']['columns'] = $columnList;
155
            } else {
156 1
                $this->queue[] = "ALTER TABLE `{$this->tables[$table]['name']}` ADD PRIMARY KEY({$columnList})";
157
            }
158
        } else {
159
            return $this->tableNotEstablished();
160
        }
161
162 1
        return true;
163
    }
164
165
    /**
166
     * Add new index definition for index to work queue
167
     *
168
     * @param string $name   name of index to add
169
     * @param string $table  table indexed
170
     * @param string $column column or a comma separated list of columns
171
     *                        to use as the key
172
     * @param bool   $unique true if index is to be unique
173
     *
174
     * @return bool true if no errors, false if errors encountered
175
     */
176 1
    public function addIndex($name, $table, $column, $unique = false)
177
    {
178 1
        $columns = str_getcsv(str_replace(' ', '', $column));
179 1
        $columnList = '';
180 1
        $firstComma = '';
181 1
        foreach ($columns as $col) {
182 1
            $columnList .= "{$firstComma}`{$col}`";
183 1
            $firstComma = ', ';
184
        }
185 1
        if (isset($this->tables[$table])) {
186 1
            if (isset($this->tables[$table]['create']) && $this->tables[$table]['create']) {
187
                $this->tables[$table]['keys'][$name]['columns'] = $columnList;
188
                $this->tables[$table]['keys'][$name]['unique'] = (bool) $unique;
189
            } else {
190 1
                $add = ($unique ? 'ADD UNIQUE INDEX' : 'ADD INDEX');
191 1
                $this->queue[] = "ALTER TABLE `{$this->tables[$table]['name']}` {$add} `{$name}` ({$columnList})";
192
            }
193
        } else {
194
            return $this->tableNotEstablished();
195
        }
196
197 1
        return true;
198
    }
199
200
    /**
201
     * Load table schema from database, or starts new empty schema if
202
     * table does not exist
203
     *
204
     * @param string $table table
205
     *
206
     * @return bool true if no errors, false if errors encountered
207
     */
208
    public function addTable($table)
209
    {
210
        if (isset($this->tables[$table])) {
211
            return true;
212
        }
213
        $tableDef = $this->getTable($table);
214
        if (is_array($tableDef)) {
215
            $this->tables[$table] = $tableDef;
216
217
            return true;
218
        } else {
219
            if ($tableDef === true) {
220
                $tableDef = array(
221
                    'name' => $this->name($table),
222
                    'options' => 'ENGINE=InnoDB',
223
                    'columns' => array(),
224
                    'keys' => array(),
225
                    'create' => true,
226
                );
227
                $this->tables[$table] = $tableDef;
228
229
                $this->queue[] = array('createtable' => $table);
230
231
                return true;
232
            } else {
233
                return false;
234
            }
235
        }
236
    }
237
238
    /**
239
     * AddTable only if it exists
240
     *
241
     * @param string $table table
242
     *
243
     * @return bool true if table exists, false otherwise
244
     */
245 1
    public function useTable($table)
246
    {
247 1
        if (isset($this->tables[$table])) {
248
            return true;
249
        }
250 1
        $tableDef = $this->getTable($table);
251 1
        if (is_array($tableDef)) {
252 1
            $this->tables[$table] = $tableDef;
253 1
            return true;
254
        }
255 1
        return false;
256
    }
257
258
    /**
259
     * Get column attributes
260
     *
261
     * @param string $table  table containing the column
262
     * @param string $column column to alter
263
     *
264
     * @return string|bool attribute string, or false if error encountered
265
     */
266 1
    public function getColumnAttributes($table, $column)
267
    {
268
        // Find table def.
269 1
        if (isset($this->tables[$table])) {
270 1
            $tableDef = $this->tables[$table];
271
            // loop thru and find the column
272 1 View Code Duplication
            foreach ($tableDef['columns'] as $col) {
273 1
                if (strcasecmp($col['name'], $column) === 0) {
274 1
                    return $col['attributes'];
275
                }
276
            }
277
        }
278
279
        return false;
280
    }
281
282
    /**
283
     * Get indexes for a table
284
     *
285
     * @param string $table get indexes for this named table
286
     *
287
     * @return array|bool array of indexes, or false if error encountered
288
     */
289 1
    public function getTableIndexes($table)
290
    {
291
        // Find table def.
292 1
        if (isset($this->tables[$table]) && isset($this->tables[$table]['keys'])) {
293 1
            return $this->tables[$table]['keys'];
294
        }
295
296 1
        return false;
297
    }
298
299
    /**
300
     * Add alter column operation to the work queue
301
     *
302
     * @param string $table      table containing the column
303
     * @param string $column     column to alter
304
     * @param string $attributes new column_definition
305
     * @param string $newName    new name for column, blank to keep same
306
     * @param mixed  $position   FIRST, string of column name to add new
0 ignored issues
show
Bug introduced by
There is no parameter named $position. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
307
     *                           column after, or null for no change
308
     *
309
     * @return bool true if no errors, false if errors encountered
310
     */
311 1
    public function alterColumn($table, $column, $attributes, $newName = '')
312
    {
313 1
        if (empty($newName)) {
314
            $newName = $column;
315
        }
316
        // Find table def.
317 1
        if (isset($this->tables[$table])) {
318 1
            $tableDef = &$this->tables[$table];
319
            // Is this on a table we are adding?
320 1
            if (isset($tableDef['create']) && $tableDef['create']) {
321
                // loop thru and find the column
322
                foreach ($tableDef['columns'] as &$col) {
323
                    if (strcasecmp($col['name'], $column) == 0) {
324
                        $col['name'] = $newName;
325
                        $col['attributes'] = $attributes;
326
                        break;
327
                    }
328
                }
329
330
                return true;
331
            } else {
332 1
                $this->queue[] = "ALTER TABLE `{$tableDef['name']}` " .
333 1
                    "CHANGE COLUMN `{$column}` `{$newName}` {$attributes} ";
334
            }
335
        } else {
336
            return $this->tableNotEstablished();
337
        }
338
339 1
        return true;
340
    }
341
342
    /**
343
     * Loads table schema from database, and adds newTable with that
344
     * schema to the queue
345
     *
346
     * @param string $table    existing table
347
     * @param string $newTable new table
348
     * @param bool   $withData true to copy data, false for schema only
349
     *
350
     * @return bool true if no errors, false if errors encountered
351
     */
352 1
    public function copyTable($table, $newTable, $withData = false)
353
    {
354 1
        if (isset($this->tables[$newTable])) {
355
            return true;
356
        }
357 1
        $tableDef = $this->getTable($table);
358 1
        $copy = $this->name($newTable);
359 1
        $original = $this->name($table);
360
361 1
        if (is_array($tableDef)) {
362 1
            $tableDef['name'] = $copy;
363 1
            if ($withData) {
364
                $this->queue[] = "CREATE TABLE `{$copy}` LIKE `{$original}` ;";
365
                $this->queue[] = "INSERT INTO `{$copy}` SELECT * FROM `{$original}` ;";
366
            } else {
367 1
                $tableDef['create'] = true;
368 1
                $this->queue[] = array('createtable' => $newTable);
369
            }
370 1
            $this->tables[$newTable] = $tableDef;
371
372 1
            return true;
373
        } else {
374
            return false;
375
        }
376
    }
377
378
    /**
379
     * Add new index definition for index to work queue
380
     *
381
     * @param string $name   name of index to add
382
     * @param string $table  table indexed
383
     * @param string $column column or comma separated list of columns
384
     *                       to use as the key
385
     * @param bool   $unique true if index is to be unique
386
     *
387
     * @return bool true if no errors, false if errors encountered
388
     */
389
    public function createIndex($name, $table, $column, $unique = false)
390
    {
391
        if (isset($this->tables[$table])) {
392
            //ALTER TABLE `table` ADD INDEX `product_id` (`product_id`)
393
            $add = ($unique?'ADD UNIQUE INDEX':'ADD INDEX');
394
            $this->queue[]
395
                = "ALTER TABLE `{$table}` {$add} {$name} ({$column})";
396
        } else { // no table established
397
            $this->lastError = _DB_XMF_TABLE_IS_NOT_DEFINED;
398
            $this->lastErrNo = -1;
399
400
            return false;
401
        }
402
403
        return true;
404
    }
405
406
    /**
407
     * Add drop column operation to the work queue
408
     *
409
     * @param string $table  table containing the column
410
     * @param string $column column to drop
411
     *
412
     * @return bool true if no errors, false if errors encountered
413
     */
414 1 View Code Duplication
    public function dropColumn($table, $column)
415
    {
416
        // Find table def.
417 1
        if (isset($this->tables[$table])) {
418 1
            $tableDef = $this->tables[$table];
419 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP COLUMN `{$column}`";
420
        } else {
421
            return $this->tableNotEstablished();
422
        }
423
424 1
        return true;
425
    }
426
427
    /**
428
     * Add drop index operation to the work queue
429
     *
430
     * @param string $name  name of index to drop
431
     * @param string $table table indexed
432
     *
433
     * @return bool true if no errors, false if errors encountered
434
     */
435 1 View Code Duplication
    public function dropIndex($name, $table)
436
    {
437 1
        if (isset($this->tables[$table])) {
438 1
            $tableDef = $this->tables[$table];
439 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP INDEX `{$name}`";
440
        } else {
441
            return $this->tableNotEstablished();
442
        }
443
444 1
        return true;
445
    }
446
447
    /**
448
     * Add drop for all (non-PRIMARY) keys for a table to the work
449
     * queue. This can be used to clean up indexes with automatic names.
450
     *
451
     * @param string $table table indexed
452
     *
453
     * @return bool true if no errors, false if errors encountered
454
     */
455
    public function dropIndexes($table)
456
    {
457
        // Find table def.
458
        if (isset($this->tables[$table])) {
459
            $tableDef = &$this->tables[$table];
460
            // Is this on a table we are adding?
461
            if (isset($tableDef['create']) && $tableDef['create']) {
462
                // strip everything but the PRIMARY from definition
463 View Code Duplication
                foreach ($tableDef['keys'] as $keyName => $key) {
464
                    if ($keyName !== 'PRIMARY') {
465
                        unset($tableDef['keys'][$keyName]);
466
                    }
467
                }
468
            } else {
469
                // build drops to strip everything but the PRIMARY
470 View Code Duplication
                foreach ($tableDef['keys'] as $keyName => $key) {
471
                    if ($keyName !== 'PRIMARY') {
472
                        $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP INDEX {$keyName}";
473
                    }
474
                }
475
            }
476
        } else {
477
            return $this->tableNotEstablished();
478
        }
479
480
        return true;
481
    }
482
483
    /**
484
     * Add drop of PRIMARY key for a table to the work queue
485
     *
486
     * @param string $table table
487
     *
488
     * @return bool true if no errors, false if errors encountered
489
     */
490 1 View Code Duplication
    public function dropPrimaryKey($table)
491
    {
492 1
        if (isset($this->tables[$table])) {
493 1
            $tableDef = $this->tables[$table];
494 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP PRIMARY KEY ";
495
        } else {
496
            return $this->tableNotEstablished();
497
        }
498
499 1
        return true;
500
    }
501
502
    /**
503
     * Add drop of table to the work queue
504
     *
505
     * @param string $table table
506
     *
507
     * @return bool true if no errors, false if errors encountered
508
     */
509 1 View Code Duplication
    public function dropTable($table)
510
    {
511 1
        if (isset($this->tables[$table])) {
512 1
            $tableDef = $this->tables[$table];
513 1
            $this->queue[] = "DROP TABLE `{$tableDef['name']}` ";
514 1
            unset($this->tables[$table]);
515
        }
516
        // no table is not an error since we are dropping it anyway
517 1
        return true;
518
    }
519
520
521
    /**
522
     * Add rename table operation to the work queue
523
     *
524
     * @param string $table   table
525
     * @param string $newName new table name
526
     *
527
     * @return bool true if no errors, false if errors encountered
528
     */
529 1
    public function renameTable($table, $newName)
530
    {
531 1
        if (isset($this->tables[$table])) {
532 1
            $tableDef = $this->tables[$table];
533 1
            $newTable = $this->name($newName);
534 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` RENAME TO `{$newTable}`";
535 1
            $tableDef['name'] = $newTable;
536 1
            $this->tables[$newName] = $tableDef;
537 1
            unset($this->tables[$table]);
538
        } else {
539
            return $this->tableNotEstablished();
540
        }
541
542 1
        return true;
543
    }
544
545
    /**
546
     * Add alter table table_options (ENGINE, DEFAULT CHARSET, etc.)
547
     * to work queue
548
     *
549
     * @param string $table   table
550
     * @param array  $options table_options
551
     *
552
     * @return bool true if no errors, false if errors encountered
553
     */
554 1
    public function setTableOptions($table, $options)
555
    {
556
        // ENGINE=MEMORY DEFAULT CHARSET=utf8;
557 1
        if (isset($this->tables[$table])) {
558 1
            $tableDef = &$this->tables[$table];
559
            // Is this on a table we are adding?
560 1
            if (isset($tableDef['create']) && $tableDef['create']) {
561
                $tableDef['options'] = $options;
562
                return true;
563
            } else {
564 1
                $this->queue[] = "ALTER TABLE `{$tableDef['name']}` {$options} ";
565 1
                $tableDef['options'] = $options;
566 1
                return true;
567
            }
568
            $tableDef = $this->tables[$table];
0 ignored issues
show
Unused Code introduced by
$tableDef = $this->tables[$table]; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
569
        } else {
570
            return $this->tableNotEstablished();
571
        }
572
573
        return true;
574
    }
575
576
577
    /**
578
     * Clear the work queue
579
     *
580
     * @return void
581
     */
582
    public function resetQueue()
583
    {
584
        $this->tables = array();
585
        $this->queue  = array();
586
    }
587
588
    /**
589
     * Executes the work queue
590
     *
591
     * @param bool $force true to force updates even if this is a 'GET' request
592
     *
593
     * @return bool true if no errors, false if errors encountered
594
     */
595
    public function executeQueue($force = false)
596
    {
597
        $this->expandQueue();
598
        foreach ($this->queue as &$ddl) {
599 View Code Duplication
            if (is_array($ddl)) {
600
                if (isset($ddl['createtable'])) {
601
                    $ddl = $this->renderTableCreate($ddl['createtable']);
602
                }
603
            }
604
            $result = $this->execSql($ddl, $force);
605
            if (!$result) {
606
                $this->lastError = $this->db->errorInfo();
607
                $this->lastErrNo = $this->db->errorCode();
608
609
                return false;
610
            }
611
        }
612
613
        return true;
614
    }
615
616
617
    /**
618
     * Create a DELETE statement and add it to the work queue
619
     *
620
     * @param string                 $table    table
621
     * @param string|CriteriaElement $criteria string where clause or object criteria
622
     *
623
     * @return bool true if no errors, false if errors encountered
624
     */
625
    public function delete($table, $criteria)
626
    {
627
        if (isset($this->tables[$table])) {
628
            $tableDef = $this->tables[$table];
629
            $where = '';
630
            if (is_scalar($criteria)) {
631
                $where = $criteria;
632
            } elseif (is_object($criteria)) {
633
                $where = $criteria->renderWhere();
634
            }
635
            $this->queue[] = "DELETE FROM `{$tableDef['name']}` {$where}";
636
        } else {
637
            return $this->tableNotEstablished();
638
        }
639
640
        return true;
641
    }
642
643
    /** Create an INSERT SQL statement and add it to the work queue.
644
     *
645
     * @param string $table   table
646
     * @param array  $columns array of 'column'=>'value' entries
647
     *
648
     * @return boolean true if no errors, false if errors encountered
649
     */
650
    public function insert($table, $columns)
651
    {
652
        if (isset($this->tables[$table])) {
653
            $tableDef = $this->tables[$table];
654
            $colSql = '';
655
            $valSql = '';
656 View Code Duplication
            foreach ($tableDef['columns'] as $col) {
657
                $comma = empty($colSql) ? '' : ', ';
658
                if (isset($columns[$col['name']])) {
659
                    $colSql .= $comma . $col['name'];
660
                    $valSql .= $comma . $this->db->quote($columns[$col['name']]);
661
                }
662
            }
663
            $sql = "INSERT INTO `{$tableDef['name']}` ({$colSql}) VALUES({$valSql})";
664
            $this->queue[] = $sql;
665
666
            return true;
667
        } else {
668
            return $this->tableNotEstablished();
669
        }
670
    }
671
672
    /**
673
     * Create an UPDATE SQL statement and add it to the work queue
674
     *
675
     * @param string                 $table    table
676
     * @param array                  $columns  array of 'column'=>'value' entries
677
     * @param string|CriteriaElement $criteria string where clause or object criteria
678
     *
679
     * @return boolean true if no errors, false if errors encountered
680
     */
681
    public function update($table, $columns, $criteria)
682
    {
683
        if (isset($this->tables[$table])) {
684
            $tableDef = $this->tables[$table];
685
            $where = '';
686
            if (is_scalar($criteria)) {
687
                $where = $criteria;
688
            } elseif (is_object($criteria)) {
689
                $where = $criteria->renderWhere();
690
            }
691
            $colSql = '';
692 View Code Duplication
            foreach ($tableDef['columns'] as $col) {
693
                $comma = empty($colSql) ? '' : ', ';
694
                if (isset($columns[$col['name']])) {
695
                    $colSql .= $comma . $col['name'] . ' = '
696
                        . $this->db->quote($columns[$col['name']]);
697
                }
698
            }
699
            $sql = "UPDATE `{$tableDef['name']}` SET {$colSql} {$where}";
700
            $this->queue[] = $sql;
701
702
            return true;
703
        } else {
704
            return $this->tableNotEstablished();
705
        }
706
    }
707
708
    /**
709
     * Add statement to remove all rows from a table to the work queue
710
     *
711
     * @param string $table table
712
     *
713
     * @return bool true if no errors, false if errors encountered
714
     */
715 View Code Duplication
    public function truncate($table)
716
    {
717
        if (isset($this->tables[$table])) {
718
            $tableDef = $this->tables[$table];
719
            $this->queue[] = "TRUNCATE TABLE `{$tableDef['name']}`";
720
        } else {
721
            return $this->tableNotEstablished();
722
        }
723
724
        return true;
725
    }
726
727
728
729
    /**
730
     * return SQL to create the table
731
     *
732
     * This method does NOT modify the work queue
733
     *
734
     * @param string $table    table
735
     * @param bool   $prefixed true to return with table name prefixed
736
     *
737
     * @return string|false string SQL to create table, or false if errors encountered
738
     */
739
    protected function renderTableCreate($table, $prefixed = false)
740
    {
741
        if (isset($this->tables[$table])) {
742
            $tableDef = $this->tables[$table];
743
            $tableName = ($prefixed ? $tableDef['name'] : $table);
744
            $sql = "CREATE TABLE `{$tableName}` (";
745
            $firstComma = '';
746
            foreach ($tableDef['columns'] as $col) {
747
                $sql .= "{$firstComma}\n    `{$col['name']}`  {$col['attributes']}";
748
                $firstComma = ',';
749
            }
750
            $keySql = '';
751
            foreach ($tableDef['keys'] as $keyName => $key) {
752
                if ($keyName === 'PRIMARY') {
753
                    $keySql .= ",\n  PRIMARY KEY ({$key['columns']})";
754
                } else {
755
                    $unique = $key['unique'] ? 'UNIQUE ' : '';
756
                    $keySql .= ",\n  {$unique}KEY {$keyName} ({$key['columns']})";
757
                }
758
            }
759
            $sql .= $keySql;
760
            $sql .= "\n) {$tableDef['options']};\n";
761
762
            return $sql;
763
        } else {
764
            return $this->tableNotEstablished();
765
        }
766
    }
767
768
    /**
769
     * execute an SQL statement
770
     *
771
     * @param string $sql   SQL statement to execute
772
     * @param bool   $force true to use force updates even in safe requests
773
     *
774
     * @return mixed result Statement, or false on error
775
     *               Any error message is in $this->lastError;
776
     */
777
    protected function execSql($sql, $force = false)
778
    {
779
        if ($force) {
780
            $this->db->setForce(true);
781
        }
782
        $result = $this->db->query($sql);
783
784
        if (!$result) {
785
            $this->lastError = $this->db->errorInfo();
786
            $this->lastErrNo = $this->db->errorCode();
787
        }
788
789
        return $result;
790
    }
791
792
    /**
793
     * fetch the next row of a result set
794
     *
795
     * @param Statement $result as returned by query
796
     *
797
     * @return mixed false on error
798
     */
799
    protected function fetch($result)
800
    {
801
        return $result->fetch(\PDO::FETCH_ASSOC);
802
    }
803
804
    /**
805
     * get table definition from INFORMATION_SCHEMA
806
     *
807
     * @param string $table table
808
     *
809
     * @return array|bool table definition array if table exists, true if table not defined, or
810
     *                    false on error. Error message in $this->lastError;
811
     */
812
    protected function getTable($table)
813
    {
814
        $tableDef = array();
815
816
        $sql  = 'SELECT TABLE_NAME, ENGINE, CHARACTER_SET_NAME ';
817
        $sql .= ' FROM `INFORMATION_SCHEMA`.`TABLES` t, ';
818
        $sql .= ' `INFORMATION_SCHEMA`.`COLLATIONS` c ';
819
        $sql .= ' WHERE t.TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
820
        $sql .= ' AND t.TABLE_NAME = \'' . $this->name($table) . '\' ';
821
        $sql .= ' AND t.TABLE_COLLATION  = c.COLLATION_NAME ';
822
823
        $result = $this->execSql($sql);
824
        if (!$result) {
825
            return false;
826
        }
827
        $tableSchema = $this->fetch($result);
828
        if (empty($tableSchema)) {
829
            return true;
830
        }
831
        $tableDef['name'] = $tableSchema['TABLE_NAME'];
832
        $tableDef['options'] = 'ENGINE=' . $tableSchema['ENGINE'] . ' '
833
            . 'DEFAULT CHARSET=' . $tableSchema['CHARACTER_SET_NAME'];
834
835
        $sql  = 'SELECT * ';
836
        $sql .= ' FROM `INFORMATION_SCHEMA`.`COLUMNS` ';
837
        $sql .= ' WHERE TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
838
        $sql .= ' AND TABLE_NAME = \'' . $this->name($table) . '\' ';
839
        $sql .= ' ORDER BY `ORDINAL_POSITION` ';
840
841
        $result = $this->execSql($sql);
842
843
        while ($column = $this->fetch($result)) {
844
            $attributes = ' ' . $column['COLUMN_TYPE'] . ' '
845
                . (($column['IS_NULLABLE'] === 'NO') ? ' NOT NULL ' : '')
846
                . (($column['COLUMN_DEFAULT'] === null) ? '' : " DEFAULT '" . $column['COLUMN_DEFAULT'] . "' ")
847
                . $column['EXTRA'];
848
849
            $columnDef = array(
850
                'name' => $column['COLUMN_NAME'],
851
                'attributes' => $attributes
852
            );
853
854
            $tableDef['columns'][] = $columnDef;
855
        };
856
857
        $sql  = 'SELECT `INDEX_NAME`, `SEQ_IN_INDEX`, `NON_UNIQUE`, ';
858
        $sql .= ' `COLUMN_NAME`, `SUB_PART` ';
859
        $sql .= ' FROM `INFORMATION_SCHEMA`.`STATISTICS` ';
860
        $sql .= ' WHERE TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
861
        $sql .= ' AND TABLE_NAME = \'' . $this->name($table) . '\' ';
862
        $sql .= ' ORDER BY `INDEX_NAME`, `SEQ_IN_INDEX` ';
863
864
        $result = $this->execSql($sql);
865
866
        $lastKey = '';
867
        $keyCols = '';
868
        $keyUnique = false;
869
        while ($key = $this->fetch($result)) {
870
            if ($lastKey != $key['INDEX_NAME']) {
871 View Code Duplication
                if (!empty($lastKey)) {
872
                    $tableDef['keys'][$lastKey]['columns'] = $keyCols;
873
                    $tableDef['keys'][$lastKey]['unique'] = $keyUnique;
874
                }
875
                $lastKey = $key['INDEX_NAME'];
876
                $keyCols = $key['COLUMN_NAME'];
877
                if (!empty($key['SUB_PART'])) {
878
                    $keyCols .= ' (' . $key['SUB_PART'] . ')';
879
                }
880
                $keyUnique = !$key['NON_UNIQUE'];
881
            } else {
882
                $keyCols .= ', ' . $key['COLUMN_NAME'];
883
                if (!empty($key['SUB_PART'])) {
884
                    $keyCols .= ' (' . $key['SUB_PART'] . ')';
885
                }
886
            }
887
            //$tableDef['keys'][$key['INDEX_NAME']][$key['SEQ_IN_INDEX']] = $key;
888
        };
889 View Code Duplication
        if (!empty($lastKey)) {
890
            $tableDef['keys'][$lastKey]['columns'] = $keyCols;
891
            $tableDef['keys'][$lastKey]['unique'] = $keyUnique;
892
        }
893
894
        return $tableDef;
895
    }
896
897
    /**
898
     * During processing, tables to be created are put in the queue as
899
     * an array('createtable' => tablename) since the definition is not
900
     * complete. This method will expand those references to the full
901
     * ddl to create the table.
902
     *
903
     * @return void
904
     */
905
    protected function expandQueue()
906
    {
907
        foreach ($this->queue as &$ddl) {
908 View Code Duplication
            if (is_array($ddl)) {
909
                if (isset($ddl['createtable'])) {
910
                    $ddl = $this->renderTableCreate($ddl['createtable'], true);
911
                }
912
            }
913
        }
914
    }
915
916
    /**
917
     * Return message from last error encountered
918
     *
919
     * @return string last error message
920
     */
921
    public function getLastError()
922
    {
923
        return $this->lastError;
924
    }
925
926
    /**
927
     * Return code from last error encountered
928
     *
929
     * @return int last error number
930
     */
931
    public function getLastErrNo()
932
    {
933
        return $this->lastErrNo;
934
    }
935
936
    /**
937
     * dumpTables - development function to dump raw tables array
938
     *
939
     * @return array tables
940
     */
941
    public function dumpTables()
942
    {
943
        return $this->tables;
944
    }
945
946
    /**
947
     * dumpQueue - development function to dump the work queue
948
     *
949
     * @return array work queue
950
     */
951
    public function dumpQueue()
952
    {
953
        $this->expandQueue();
954
955
        return $this->queue;
956
    }
957
958
    /**
959
     * Set lastError as table not established
960
     *
961
     * @return false
962
     */
963
    protected function tableNotEstablished()
964
    {
965
        $this->lastError = _DB_XMF_TABLE_IS_NOT_DEFINED;
966
        $this->lastErrNo = -1;
967
        return false;
968
    }
969
}
970