Completed
Pull Request — master (#563)
by Richard
08:33
created

Tables::dropIndexes()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 13
nc 7
nop 1
dl 0
loc 26
rs 5.3846
c 0
b 0
f 0
ccs 0
cts 12
cp 0
crap 72
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
 * @link      http://xoops.org
36
 */
37
class Tables
38
{
39
    /**
40
     * @var Connection
41
     */
42
    protected $db;
43
44
    /**
45
     * @var string
46
     */
47
    protected $databaseName;
48
49
    /**
50
     * @var array Tables
51
     */
52
    protected $tables;
53
54
    /**
55
     * @var array Work queue
56
     */
57
    protected $queue;
58
59
    /**
60
     * @var string last error message
61
     */
62
    protected $lastError;
63
64
    /**
65
     * @var int last error number
66
     */
67
    protected $lastErrNo;
68
69
    /**
70
     * Constructor
71
     *
72
     */
73 19
    public function __construct()
74
    {
75 19
        Language::load('xmf');
76
77 19
        $this->db = Factory::getConnection();
78 19
        $this->databaseName = \XoopsBaseConfig::get('db-name');
79 19
        $this->resetQueue();
80 19
    }
81
82
    /**
83
     * Return a table name, prefixed with site table prefix
84
     *
85
     * @param string $table table name to contain prefix
86
     *
87
     * @return string table name with prefix
88
     */
89 15
    protected function name($table)
90
    {
91 15
        return $this->db->prefix($table);
92
    }
93
94
    /**
95
     * Add new column for table to the work queue
96
     *
97
     * @param string $table      table to contain the column
98
     * @param string $column     name of column to add
99
     * @param string $attributes column_definition
100
     *
101
     * @return bool true if no errors, false if errors encountered
102
     */
103 1
    public function addColumn($table, $column, $attributes)
104
    {
105
        $columnDef = array(
106 1
            'name' => $column,
107 1
            'attributes' => $attributes
108
        );
109
110
        // Find table def.
111 1
        if (isset($this->tables[$table])) {
112 1
            $tableDef = &$this->tables[$table];
113
            // Is this on a table we are adding?
114 1
            if (isset($tableDef['create']) && $tableDef['create']) {
115
                array_push($tableDef['columns'], $columnDef);
116
            } else {
117 1
                foreach ($tableDef['columns'] as $col) {
118 1
                    if (strcasecmp($col['name'], $column) == 0) {
119 1
                        return true;
120
                    }
121
                }
122 1
                $this->queue[] = "ALTER TABLE `{$tableDef['name']}`"
123 1
                    . " ADD COLUMN `{$column}` {$columnDef['attributes']}";
124 1
                array_push($tableDef['columns'], $columnDef);
125
            }
126
        } else {
127
            return $this->tableNotEstablished();
128
        }
129
130 1
        return true; // exists or is added to queue
131
    }
132
133
    /**
134
     * Add new primary key definition for table to work queue
135
     *
136
     * @param string $table  table
137
     * @param string $column column or comma separated list of columns
138
     *                       to use as primary key
139
     *
140
     * @return bool true if no errors, false if errors encountered
141
     */
142 1
    public function addPrimaryKey($table, $column)
143
    {
144 1
        $columns = str_getcsv(str_replace(' ', '', $column));
145 1
        $columnList = '';
146 1
        $firstComma = '';
147 1
        foreach ($columns as $col) {
148 1
            $columnList .= "{$firstComma}`{$col}`";
149 1
            $firstComma = ', ';
150
        }
151 1
        if (isset($this->tables[$table])) {
152 1
            if (isset($this->tables[$table]['create']) && $this->tables[$table]['create']) {
153
                $this->tables[$table]['keys']['PRIMARY']['columns'] = $columnList;
154
            } else {
155 1
                $this->queue[] = "ALTER TABLE `{$this->tables[$table]['name']}` ADD PRIMARY KEY({$columnList})";
156
            }
157
        } else {
158
            return $this->tableNotEstablished();
159
        }
160
161 1
        return true;
162
    }
163
164
    /**
165
     * Add new index definition for index to work queue
166
     *
167
     * @param string $name   name of index to add
168
     * @param string $table  table indexed
169
     * @param string $column column or a comma separated list of columns
170
     *                        to use as the key
171
     * @param bool   $unique true if index is to be unique
172
     *
173
     * @return bool true if no errors, false if errors encountered
174
     */
175 1
    public function addIndex($name, $table, $column, $unique = false)
176
    {
177 1
        $columns = str_getcsv(str_replace(' ', '', $column));
178 1
        $columnList = '';
179 1
        $firstComma = '';
180 1
        foreach ($columns as $col) {
181 1
            $columnList .= "{$firstComma}`{$col}`";
182 1
            $firstComma = ', ';
183
        }
184 1
        if (isset($this->tables[$table])) {
185 1
            if (isset($this->tables[$table]['create']) && $this->tables[$table]['create']) {
186
                $this->tables[$table]['keys'][$name]['columns'] = $columnList;
187
                $this->tables[$table]['keys'][$name]['unique'] = (bool) $unique;
188
            } else {
189 1
                $add = ($unique ? 'ADD UNIQUE INDEX' : 'ADD INDEX');
190 1
                $this->queue[] = "ALTER TABLE `{$this->tables[$table]['name']}` {$add} `{$name}` ({$columnList})";
191
            }
192
        } else {
193
            return $this->tableNotEstablished();
194
        }
195
196 1
        return true;
197
    }
198
199
    /**
200
     * Load table schema from database, or starts new empty schema if
201
     * table does not exist
202
     *
203
     * @param string $table table
204
     *
205
     * @return bool true if no errors, false if errors encountered
206
     */
207
    public function addTable($table)
208
    {
209
        if (isset($this->tables[$table])) {
210
            return true;
211
        }
212
        $tableDef = $this->getTable($table);
213
        if (is_array($tableDef)) {
0 ignored issues
show
introduced by
The condition is_array($tableDef) can never be true.
Loading history...
214
            $this->tables[$table] = $tableDef;
215
216
            return true;
217
        } else {
218
            if ($tableDef === true) {
219
                $tableDef = array(
220
                    'name' => $this->name($table),
221
                    'options' => 'ENGINE=InnoDB',
222
                    'columns' => array(),
223
                    'keys' => array(),
224
                    'create' => true,
225
                );
226
                $this->tables[$table] = $tableDef;
227
228
                $this->queue[] = array('createtable' => $table);
229
230
                return true;
231
            } else {
232
                return false;
233
            }
234
        }
235
    }
236
237
    /**
238
     * AddTable only if it exists
239
     *
240
     * @param string $table table
241
     *
242
     * @return bool true if table exists, false otherwise
243
     */
244 15
    public function useTable($table)
245
    {
246 15
        if (isset($this->tables[$table])) {
247
            return true;
248
        }
249 15
        $tableDef = $this->getTable($table);
250 15
        if (is_array($tableDef)) {
0 ignored issues
show
introduced by
The condition is_array($tableDef) can never be true.
Loading history...
251 15
            $this->tables[$table] = $tableDef;
252 15
            return true;
253
        }
254 2
        return false;
255
    }
256
257
    /**
258
     * Get column attributes
259
     *
260
     * @param string $table  table containing the column
261
     * @param string $column column to alter
262
     *
263
     * @return string|bool attribute string, or false if error encountered
264
     */
265 1
    public function getColumnAttributes($table, $column)
266
    {
267
        // Find table def.
268 1
        if (isset($this->tables[$table])) {
269 1
            $tableDef = $this->tables[$table];
270
            // loop thru and find the column
271 1
            foreach ($tableDef['columns'] as $col) {
272 1
                if (strcasecmp($col['name'], $column) === 0) {
273 1
                    return $col['attributes'];
274
                }
275
            }
276
        }
277
278
        return false;
279
    }
280
281
    /**
282
     * Get indexes for a table
283
     *
284
     * @param string $table get indexes for this named table
285
     *
286
     * @return array|bool array of indexes, or false if error encountered
287
     */
288 1
    public function getTableIndexes($table)
289
    {
290
        // Find table def.
291 1
        if (isset($this->tables[$table]) && isset($this->tables[$table]['keys'])) {
292 1
            return $this->tables[$table]['keys'];
293
        }
294
295 1
        return false;
296
    }
297
298
    /**
299
     * Add alter column operation to the work queue
300
     *
301
     * @param string $table      table containing the column
302
     * @param string $column     column to alter
303
     * @param string $attributes new column_definition
304
     * @param string $newName    new name for column, blank to keep same
305
     *
306
     * @return bool true if no errors, false if errors encountered
307
     */
308 1
    public function alterColumn($table, $column, $attributes, $newName = '')
309
    {
310 1
        if (empty($newName)) {
311
            $newName = $column;
312
        }
313
        // Find table def.
314 1
        if (isset($this->tables[$table])) {
315 1
            $tableDef = &$this->tables[$table];
316
            // Is this on a table we are adding?
317 1
            if (isset($tableDef['create']) && $tableDef['create']) {
318
                // loop thru and find the column
319
                foreach ($tableDef['columns'] as &$col) {
320
                    if (strcasecmp($col['name'], $column) == 0) {
321
                        $col['name'] = $newName;
322
                        $col['attributes'] = $attributes;
323
                        break;
324
                    }
325
                }
326
327
                return true;
328
            } else {
329 1
                $this->queue[] = "ALTER TABLE `{$tableDef['name']}` " .
330 1
                    "CHANGE COLUMN `{$column}` `{$newName}` {$attributes} ";
331
                // loop thru and find the column
332 1
                foreach ($tableDef['columns'] as &$col) {
333 1
                    if (strcasecmp($col['name'], $column) == 0) {
334 1
                        $col['name'] = $newName;
335 1
                        $col['attributes'] = $attributes;
336 1
                        break;
337
                    }
338
                }
339
            }
340
        } else {
341
            return $this->tableNotEstablished();
342
        }
343
344 1
        return true;
345
    }
346
347
    /**
348
     * Loads table schema from database, and adds newTable with that
349
     * schema to the queue
350
     *
351
     * @param string $table    existing table
352
     * @param string $newTable new table
353
     * @param bool   $withData true to copy data, false for schema only
354
     *
355
     * @return bool true if no errors, false if errors encountered
356
     */
357 1
    public function copyTable($table, $newTable, $withData = false)
358
    {
359 1
        if (isset($this->tables[$newTable])) {
360
            return true;
361
        }
362 1
        $tableDef = $this->getTable($table);
363 1
        $copy = $this->name($newTable);
364 1
        $original = $this->name($table);
365
366 1
        if (is_array($tableDef)) {
0 ignored issues
show
introduced by
The condition is_array($tableDef) can never be true.
Loading history...
367 1
            $tableDef['name'] = $copy;
368 1
            if ($withData) {
369
                $this->queue[] = "CREATE TABLE `{$copy}` LIKE `{$original}` ;";
370
                $this->queue[] = "INSERT INTO `{$copy}` SELECT * FROM `{$original}` ;";
371
            } else {
372 1
                $tableDef['create'] = true;
373 1
                $this->queue[] = array('createtable' => $newTable);
374
            }
375 1
            $this->tables[$newTable] = $tableDef;
376
377 1
            return true;
378
        } else {
379
            return false;
380
        }
381
    }
382
383
    /**
384
     * Add drop column operation to the work queue
385
     *
386
     * @param string $table  table containing the column
387
     * @param string $column column to drop
388
     *
389
     * @return bool true if no errors, false if errors encountered
390
     */
391 1
    public function dropColumn($table, $column)
392
    {
393
        // Find table def.
394 1
        if (isset($this->tables[$table])) {
395 1
            $tableDef = $this->tables[$table];
396 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP COLUMN `{$column}`";
397
        } else {
398
            return $this->tableNotEstablished();
399
        }
400
401 1
        return true;
402
    }
403
404
    /**
405
     * Add drop index operation to the work queue
406
     *
407
     * @param string $name  name of index to drop
408
     * @param string $table table indexed
409
     *
410
     * @return bool true if no errors, false if errors encountered
411
     */
412 1
    public function dropIndex($name, $table)
413
    {
414 1
        if (isset($this->tables[$table])) {
415 1
            $tableDef = $this->tables[$table];
416 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP INDEX `{$name}`";
417
        } else {
418
            return $this->tableNotEstablished();
419
        }
420
421 1
        return true;
422
    }
423
424
    /**
425
     * Add drop for all (non-PRIMARY) keys for a table to the work
426
     * queue. This can be used to clean up indexes with automatic names.
427
     *
428
     * @param string $table table indexed
429
     *
430
     * @return bool true if no errors, false if errors encountered
431
     */
432
    public function dropIndexes($table)
433
    {
434
        // Find table def.
435
        if (isset($this->tables[$table])) {
436
            $tableDef = &$this->tables[$table];
437
            // Is this on a table we are adding?
438
            if (isset($tableDef['create']) && $tableDef['create']) {
439
                // strip everything but the PRIMARY from definition
440
                foreach ($tableDef['keys'] as $keyName => $key) {
441
                    if ($keyName !== 'PRIMARY') {
442
                        unset($tableDef['keys'][$keyName]);
443
                    }
444
                }
445
            } else {
446
                // build drops to strip everything but the PRIMARY
447
                foreach ($tableDef['keys'] as $keyName => $key) {
448
                    if ($keyName !== 'PRIMARY') {
449
                        $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP INDEX {$keyName}";
450
                    }
451
                }
452
            }
453
        } else {
454
            return $this->tableNotEstablished();
455
        }
456
457
        return true;
458
    }
459
460
    /**
461
     * Add drop of PRIMARY key for a table to the work queue
462
     *
463
     * @param string $table table
464
     *
465
     * @return bool true if no errors, false if errors encountered
466
     */
467 1
    public function dropPrimaryKey($table)
468
    {
469 1
        if (isset($this->tables[$table])) {
470 1
            $tableDef = $this->tables[$table];
471 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP PRIMARY KEY ";
472
        } else {
473
            return $this->tableNotEstablished();
474
        }
475
476 1
        return true;
477
    }
478
479
    /**
480
     * Add drop of table to the work queue
481
     *
482
     * @param string $table table
483
     *
484
     * @return bool true if no errors, false if errors encountered
485
     */
486 1
    public function dropTable($table)
487
    {
488 1
        if (isset($this->tables[$table])) {
489 1
            $tableDef = $this->tables[$table];
490 1
            $this->queue[] = "DROP TABLE `{$tableDef['name']}` ";
491 1
            unset($this->tables[$table]);
492
        }
493
        // no table is not an error since we are dropping it anyway
494 1
        return true;
495
    }
496
497
498
    /**
499
     * Add rename table operation to the work queue
500
     *
501
     * @param string $table   table
502
     * @param string $newName new table name
503
     *
504
     * @return bool true if no errors, false if errors encountered
505
     */
506 1
    public function renameTable($table, $newName)
507
    {
508 1
        if (isset($this->tables[$table])) {
509 1
            $tableDef = $this->tables[$table];
510 1
            $newTable = $this->name($newName);
511 1
            $this->queue[] = "ALTER TABLE `{$tableDef['name']}` RENAME TO `{$newTable}`";
512 1
            $tableDef['name'] = $newTable;
513 1
            $this->tables[$newName] = $tableDef;
514 1
            unset($this->tables[$table]);
515
        } else {
516
            return $this->tableNotEstablished();
517
        }
518
519 1
        return true;
520
    }
521
522
    /**
523
     * Add alter table table_options (ENGINE, DEFAULT CHARSET, etc.)
524
     * to work queue
525
     *
526
     * @param string $table   table
527
     * @param string $options table_options
528
     *
529
     * @return bool true if no errors, false if errors encountered
530
     */
531 1
    public function setTableOptions($table, $options)
532
    {
533
        // ENGINE=MEMORY DEFAULT CHARSET=utf8;
534 1
        if (isset($this->tables[$table])) {
535 1
            $tableDef = &$this->tables[$table];
536
            // Is this on a table we are adding?
537 1
            if (isset($tableDef['create']) && $tableDef['create']) {
538
                $tableDef['options'] = $options;
539
                return true;
540
            } else {
541 1
                $this->queue[] = "ALTER TABLE `{$tableDef['name']}` {$options} ";
542 1
                $tableDef['options'] = $options;
543 1
                return true;
544
            }
545
        } else {
546
            return $this->tableNotEstablished();
547
        }
548
    }
549
550
551
    /**
552
     * Clear the work queue
553
     *
554
     * @return void
555
     */
556 19
    public function resetQueue()
557
    {
558 19
        $this->tables = array();
559 19
        $this->queue  = array();
560 19
    }
561
562
    /**
563
     * Executes the work queue
564
     *
565
     * @param bool $force true to force updates even if this is a 'GET' request
566
     *
567
     * @return bool true if no errors, false if errors encountered
568
     */
569
    public function executeQueue($force = false)
570
    {
571
        $this->expandQueue();
572
        foreach ($this->queue as &$ddl) {
573
            if (is_array($ddl)) {
574
                if (isset($ddl['createtable'])) {
575
                    $ddl = $this->renderTableCreate($ddl['createtable']);
576
                }
577
            }
578
            $result = $this->execSql($ddl, $force);
579
            if (!$result) {
580
                $this->lastError = $this->db->errorInfo();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->errorInfo() of type array is incompatible with the declared type string of property $lastError.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
581
                $this->lastErrNo = $this->db->errorCode();
582
583
                return false;
584
            }
585
        }
586
587
        return true;
588
    }
589
590
591
    /**
592
     * Create a DELETE statement and add it to the work queue
593
     *
594
     * @param string                 $table    table
595
     * @param string|CriteriaElement $criteria string where clause or object criteria
0 ignored issues
show
Bug introduced by
The type Xmf\Database\CriteriaElement was not found. Did you mean CriteriaElement? If so, make sure to prefix the type with \.
Loading history...
596
     *
597
     * @return bool true if no errors, false if errors encountered
598
     */
599
    public function delete($table, $criteria)
600
    {
601
        if (isset($this->tables[$table])) {
602
            $tableDef = $this->tables[$table];
603
            $where = '';
604
            if (is_scalar($criteria)) {
605
                $where = $criteria;
606
            } elseif (is_object($criteria)) {
607
                $where = $criteria->renderWhere();
608
            }
609
            $this->queue[] = "DELETE FROM `{$tableDef['name']}` {$where}";
610
        } else {
611
            return $this->tableNotEstablished();
612
        }
613
614
        return true;
615
    }
616
617
    /**
618
     * Create an INSERT SQL statement and add it to the work queue.
619
     *
620
     * @param string  $table      table
621
     * @param array   $columns    array of 'column'=>'value' entries
622
     * @param boolean $quoteValue true to quote values, false if caller handles quoting
623
     *
624
     * @return boolean true if no errors, false if errors encountered
625
     */
626
    public function insert($table, $columns, $quoteValue = true)
627
    {
628
        if (isset($this->tables[$table])) {
629
            $tableDef = $this->tables[$table];
630
            $colSql = '';
631
            $valSql = '';
632
            foreach ($tableDef['columns'] as $col) {
633
                $comma = empty($colSql) ? '' : ', ';
634
                if (isset($columns[$col['name']])) {
635
                    $colSql .= "{$comma}`{$col['name']}`";
636
                    $valSql .= $comma
637
                        . ($quoteValue ? $this->db->quote($columns[$col['name']]) : $columns[$col['name']]);
638
                }
639
            }
640
            $sql = "INSERT INTO `{$tableDef['name']}` ({$colSql}) VALUES({$valSql})";
641
            $this->queue[] = $sql;
642
643
            return true;
644
        } else {
645
            return $this->tableNotEstablished();
646
        }
647
    }
648
649
    /**
650
     * Create an UPDATE SQL statement and add it to the work queue
651
     *
652
     * @param string                 $table      table
653
     * @param array                  $columns    array of 'column'=>'value' entries
654
     * @param string|CriteriaElement $criteria   string where clause or object criteria
655
     * @param boolean                $quoteValue true to quote values, false if caller handles quoting
656
     *
657
     * @return boolean true if no errors, false if errors encountered
658
     */
659
    public function update($table, $columns, $criteria, $quoteValue = true)
660
    {
661
        if (isset($this->tables[$table])) {
662
            $tableDef = $this->tables[$table];
663
            $where = '';
664
            if (is_scalar($criteria)) {
665
                $where = $criteria;
666
            } elseif (is_object($criteria)) {
667
                $where = $criteria->renderWhere();
668
            }
669
            $colSql = '';
670
            foreach ($tableDef['columns'] as $col) {
671
                $comma = empty($colSql) ? '' : ', ';
672
                if (isset($columns[$col['name']])) {
673
                    $colSql .= "{$comma}`{$col['name']}` = "
674
                        . ($quoteValue ? $this->db->quote($columns[$col['name']]) : $columns[$col['name']]);
675
                }
676
            }
677
            $sql = "UPDATE `{$tableDef['name']}` SET {$colSql} {$where}";
678
            $this->queue[] = $sql;
679
680
            return true;
681
        } else {
682
            return $this->tableNotEstablished();
683
        }
684
    }
685
686
    /**
687
     * Add statement to remove all rows from a table to the work queue
688
     *
689
     * @param string $table table
690
     *
691
     * @return bool true if no errors, false if errors encountered
692
     */
693
    public function truncate($table)
694
    {
695
        if (isset($this->tables[$table])) {
696
            $tableDef = $this->tables[$table];
697
            $this->queue[] = "TRUNCATE TABLE `{$tableDef['name']}`";
698
        } else {
699
            return $this->tableNotEstablished();
700
        }
701
702
        return true;
703
    }
704
705
706
707
    /**
708
     * return SQL to create the table
709
     *
710
     * This method does NOT modify the work queue
711
     *
712
     * @param string $table    table
713
     * @param bool   $prefixed true to return with table name prefixed
714
     *
715
     * @return string|false string SQL to create table, or false if errors encountered
716
     */
717
    protected function renderTableCreate($table, $prefixed = false)
718
    {
719
        if (isset($this->tables[$table])) {
720
            $tableDef = $this->tables[$table];
721
            $tableName = ($prefixed ? $tableDef['name'] : $table);
722
            $sql = "CREATE TABLE `{$tableName}` (";
723
            $firstComma = '';
724
            foreach ($tableDef['columns'] as $col) {
725
                $sql .= "{$firstComma}\n    `{$col['name']}`  {$col['attributes']}";
726
                $firstComma = ',';
727
            }
728
            $keySql = '';
729
            foreach ($tableDef['keys'] as $keyName => $key) {
730
                if ($keyName === 'PRIMARY') {
731
                    $keySql .= ",\n  PRIMARY KEY ({$key['columns']})";
732
                } else {
733
                    $unique = $key['unique'] ? 'UNIQUE ' : '';
734
                    $keySql .= ",\n  {$unique}KEY {$keyName} ({$key['columns']})";
735
                }
736
            }
737
            $sql .= $keySql;
738
            $sql .= "\n) {$tableDef['options']}";
739
740
            return $sql;
741
        } else {
742
            return $this->tableNotEstablished();
743
        }
744
    }
745
746
    /**
747
     * execute an SQL statement
748
     *
749
     * @param string $sql   SQL statement to execute
750
     * @param bool   $force true to use force updates even in safe requests
751
     *
752
     * @return mixed result Statement, or false on error
753
     *               Any error message is in $this->lastError;
754
     */
755 15
    protected function execSql($sql, $force = false)
756
    {
757 15
        if ($force) {
758
            $this->db->setForce(true);
759
        }
760 15
        $result = $this->db->query($sql);
761
762 15
        if (!$result) {
763
            $this->lastError = $this->db->errorInfo();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->errorInfo() of type array is incompatible with the declared type string of property $lastError.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
764
            $this->lastErrNo = $this->db->errorCode();
765
        }
766
767 15
        return $result;
768
    }
769
770
    /**
771
     * fetch the next row of a result set
772
     *
773
     * @param Statement $result as returned by query
774
     *
775
     * @return mixed false on error
776
     */
777 15
    protected function fetch($result)
778
    {
779 15
        return $result->fetch(\PDO::FETCH_ASSOC);
780
    }
781
782
    /**
783
     * get table definition from INFORMATION_SCHEMA
784
     *
785
     * @param string $table table
786
     *
787
     * @return array|bool table definition array if table exists, true if table not defined, or
788
     *                    false on error. Error message in $this->lastError;
789
     */
790 15
    protected function getTable($table)
791
    {
792 15
        $tableDef = array();
793
794 15
        $sql  = 'SELECT TABLE_NAME, ENGINE, CHARACTER_SET_NAME ';
795 15
        $sql .= ' FROM `INFORMATION_SCHEMA`.`TABLES` t, ';
796 15
        $sql .= ' `INFORMATION_SCHEMA`.`COLLATIONS` c ';
797 15
        $sql .= ' WHERE t.TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
798 15
        $sql .= ' AND t.TABLE_NAME = \'' . $this->name($table) . '\' ';
799 15
        $sql .= ' AND t.TABLE_COLLATION  = c.COLLATION_NAME ';
800
801 15
        $result = $this->execSql($sql);
802 15
        if (!$result) {
803
            return false;
804
        }
805 15
        $tableSchema = $this->fetch($result);
806 15
        if (empty($tableSchema)) {
807 2
            return true;
808
        }
809 15
        $tableDef['name'] = $tableSchema['TABLE_NAME'];
810 15
        $tableDef['options'] = 'ENGINE=' . $tableSchema['ENGINE'] . ' '
811 15
            . 'DEFAULT CHARSET=' . $tableSchema['CHARACTER_SET_NAME'];
812
813 15
        $sql  = 'SELECT * ';
814 15
        $sql .= ' FROM `INFORMATION_SCHEMA`.`COLUMNS` ';
815 15
        $sql .= ' WHERE TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
816 15
        $sql .= ' AND TABLE_NAME = \'' . $this->name($table) . '\' ';
817 15
        $sql .= ' ORDER BY `ORDINAL_POSITION` ';
818
819 15
        $result = $this->execSql($sql);
820
821 15
        while ($column = $this->fetch($result)) {
822 15
            $attributes = ' ' . $column['COLUMN_TYPE'] . ' '
823 15
                . (($column['IS_NULLABLE'] === 'NO') ? ' NOT NULL ' : '')
824 15
                . (($column['COLUMN_DEFAULT'] === null) ? '' : " DEFAULT '" . $column['COLUMN_DEFAULT'] . "' ")
825 15
                . $column['EXTRA'];
826
827
            $columnDef = array(
828 15
                'name' => $column['COLUMN_NAME'],
829 15
                'attributes' => $attributes
830
            );
831
832 15
            $tableDef['columns'][] = $columnDef;
833
        };
834
835 15
        $sql  = 'SELECT `INDEX_NAME`, `SEQ_IN_INDEX`, `NON_UNIQUE`, ';
836 15
        $sql .= ' `COLUMN_NAME`, `SUB_PART` ';
837 15
        $sql .= ' FROM `INFORMATION_SCHEMA`.`STATISTICS` ';
838 15
        $sql .= ' WHERE TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
839 15
        $sql .= ' AND TABLE_NAME = \'' . $this->name($table) . '\' ';
840 15
        $sql .= ' ORDER BY `INDEX_NAME`, `SEQ_IN_INDEX` ';
841
842 15
        $result = $this->execSql($sql);
843
844 15
        $lastKey = '';
845 15
        $keyCols = '';
846 15
        $keyUnique = false;
847 15
        while ($key = $this->fetch($result)) {
848 15
            if ($lastKey != $key['INDEX_NAME']) {
849 15
                if (!empty($lastKey)) {
850 15
                    $tableDef['keys'][$lastKey]['columns'] = $keyCols;
851 15
                    $tableDef['keys'][$lastKey]['unique'] = $keyUnique;
852
                }
853 15
                $lastKey = $key['INDEX_NAME'];
854 15
                $keyCols = $key['COLUMN_NAME'];
855 15
                if (!empty($key['SUB_PART'])) {
856
                    $keyCols .= ' (' . $key['SUB_PART'] . ')';
857
                }
858 15
                $keyUnique = !$key['NON_UNIQUE'];
859
            } else {
860 15
                $keyCols .= ', ' . $key['COLUMN_NAME'];
861 15
                if (!empty($key['SUB_PART'])) {
862 15
                    $keyCols .= ' (' . $key['SUB_PART'] . ')';
863
                }
864
            }
865
        };
866 15
        if (!empty($lastKey)) {
0 ignored issues
show
introduced by
The condition ! empty($lastKey) can never be true.
Loading history...
867 15
            $tableDef['keys'][$lastKey]['columns'] = $keyCols;
868 15
            $tableDef['keys'][$lastKey]['unique'] = $keyUnique;
869
        }
870
871 15
        return $tableDef;
872
    }
873
874
    /**
875
     * During processing, tables to be created are put in the queue as
876
     * an array('createtable' => tablename) since the definition is not
877
     * complete. This method will expand those references to the full
878
     * ddl to create the table.
879
     *
880
     * @return void
881
     */
882 11
    protected function expandQueue()
883
    {
884 11
        foreach ($this->queue as &$ddl) {
885 11
            if (is_array($ddl)) {
886
                if (isset($ddl['createtable'])) {
887 11
                    $ddl = $this->renderTableCreate($ddl['createtable'], true);
888
                }
889
            }
890
        }
891 11
    }
892
893
    /**
894
     * Return message from last error encountered
895
     *
896
     * @return string last error message
897
     */
898 1
    public function getLastError()
899
    {
900 1
        return $this->lastError;
901
    }
902
903
    /**
904
     * Return code from last error encountered
905
     *
906
     * @return int last error number
907
     */
908 1
    public function getLastErrNo()
909
    {
910 1
        return $this->lastErrNo;
911
    }
912
913
    /**
914
     * dumpTables - utility function to dump raw tables array
915
     *
916
     * @return array tables
917
     */
918 2
    public function dumpTables()
919
    {
920 2
        return $this->tables;
921
    }
922
923
    /**
924
     * dumpQueue - utility function to dump the work queue
925
     *
926
     * @return array work queue
927
     */
928 11
    public function dumpQueue()
929
    {
930 11
        $this->expandQueue();
931
932 11
        return $this->queue;
933
    }
934
935
    /**
936
     * addToQueue - utility function to add a statement to the work queue
937
     *
938
     * @param string $sql an SQL/DDL statement to add
939
     *
940
     * @return void
941
     */
942 1
    public function addToQueue($sql)
943
    {
944 1
        $this->queue[] = $sql;
945 1
    }
946
947
    /**
948
     * Set lastError as table not established
949
     *
950
     * @return false
951
     */
952
    protected function tableNotEstablished()
953
    {
954
        $this->lastError = _DB_XMF_TABLE_IS_NOT_DEFINED;
955
        $this->lastErrNo = -1;
956
        return false;
957
    }
958
}
959