Completed
Push — xmfsync ( c8fd7a )
by Richard
11:25 queued 02:44
created

Tables::queueReset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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