Passed
Push — develop ( cd2c29...4f7ba8 )
by nguereza
02:28
created

Driver::getViews()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 1
dl 0
loc 14
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine Database
5
 *
6
 * Platine Database is the abstraction layer using PDO with support of query and schema builder
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Database
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file Driver.php
33
 *
34
 *  The Database Query Driver class
35
 *
36
 *  Each driver like MySQL, SQLite, Oracle, etc. need extend this class
37
 *
38
 *  @package    Platine\Database\Driver
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   http://www.iacademy.cf
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Database\Driver;
50
51
use DateTime;
52
use Platine\Database\Connection;
53
use Platine\Database\Query\Expression;
54
use Platine\Database\Query\QueryStatement;
55
use Platine\Database\Schema\AlterTable;
56
use Platine\Database\Schema\BaseColumn;
57
use Platine\Database\Schema\CreateTable;
58
use Platine\Database\Schema\ForeignKey;
59
60
61
/**
62
 * Class Driver
63
 *
64
 * @package Platine\Database\Driver
65
 */
66
class Driver
67
{
68
    /**
69
     * The driver default date format
70
     * @var string
71
     */
72
    protected string $dateFormat = 'Y-m-d H:i:s';
73
74
    /**
75
     * The quote identifier for a table and columns
76
     * @var string
77
     */
78
    protected string $identifier = '"%s"';
79
80
    /**
81
     * Each query separator
82
     * @var string
83
     */
84
    protected string $separator = ';';
85
86
    /**
87
     * The columns modifiers
88
     * @var array<string>
89
     */
90
    protected array $modifiers = [
91
        'unsigned',
92
        'nullable',
93
        'default',
94
        'autoincrement',
95
        'description',
96
        'after',
97
    ];
98
99
    /**
100
     * Columns serial
101
     * @var array<string>
102
     */
103
    protected array $serials = [
104
        'tiny',
105
        'small',
106
        'normal',
107
        'medium',
108
        'big'
109
    ];
110
111
    /**
112
     * Auto increment value modifier
113
     * @var string
114
     */
115
    protected string $autoincrement = 'AUTO_INCREMENT';
116
117
    /**
118
     * The query parameters
119
     * @var array<mixed>
120
     */
121
    protected array $params = [];
122
123
    /**
124
     * The Connection instance
125
     * @var Connection
126
     */
127
    protected Connection $connection;
128
129
    /**
130
     * Class constructor
131
     * @param Connection $connection
132
     */
133
    public function __construct(Connection $connection)
134
    {
135
        $this->connection = $connection;
136
    }
137
138
    /**
139
     * Returns the SQL for SELECT statement
140
     * @param QueryStatement $select
141
     *
142
     * @return string
143
     */
144
    public function select(QueryStatement $select): string
145
    {
146
        $sql = $select->hasDistinct() ? 'SELECT DISTINCT ' : 'SELECT ';
147
        $sql .= $this->getColumnList($select->getColumns());
148
        $sql .= $this->getInto($select->getIntoTable());
149
        $sql .= ' FROM ';
150
        $sql .= $this->getTableList($select->getTables());
151
        $sql .= $this->getJoins($select->getJoins());
152
        $sql .= $this->getWheres($select->getWheres());
153
        $sql .= $this->getGroupBy($select->getGroupBy());
154
        $sql .= $this->getHaving($select->getHaving());
155
        $sql .= $this->getOrders($select->getOrder());
156
        $sql .= $this->getLimit($select->getLimit());
157
        $sql .= $this->getOffset($select->getOffset());
158
159
        return $sql;
160
    }
161
162
    /**
163
     * Return SQL for INSERT statement
164
     * @param QueryStatement $insert
165
     *
166
     * @return string
167
     */
168
    public function insert(QueryStatement $insert): string
169
    {
170
        $columns = $this->getColumnList($insert->getColumns());
171
172
        $sql = 'INSERT INTO ';
173
        $sql .= $this->getTableList($insert->getTables());
174
        $sql .= ($columns == '*') ? '' : '(' . $columns . ')';
175
        $sql .= $this->getInsertValues($insert->getValues());
176
177
        return $sql;
178
    }
179
180
    /**
181
     * Return the SQL for UPDATE statement
182
     * @param QueryStatement $update
183
     *
184
     * @return string
185
     */
186
    public function update(QueryStatement $update): string
187
    {
188
        $sql = 'UPDATE ';
189
        $sql .= $this->getTableList($update->getTables());
190
        $sql .= $this->getJoins($update->getJoins());
191
        $sql .= $this->getSetColumns($update->getColumns());
192
        $sql .= $this->getWheres($update->getWheres());
193
194
        return $sql;
195
    }
196
197
    /**
198
     * Return the SQL for DELETE statement
199
     * @param QueryStatement $delete
200
     *
201
     * @return string
202
     */
203
    public function delete(QueryStatement $delete): string
204
    {
205
        $sql = 'DELETE ' . $this->getTableList($delete->getTables());
206
        $sql .= $sql === 'DELETE ' ? 'FROM ' : ' FROM ';
207
        $sql .= $this->getTableList($delete->getFrom());
208
        $sql .= $this->getJoins($delete->getJoins());
209
        $sql .= $this->getWheres($delete->getWheres());
210
211
        return $sql;
212
    }
213
214
    /**
215
     * Return the date format
216
     * @return string
217
     */
218
    public function getDateFormat(): string
219
    {
220
        return $this->dateFormat;
221
    }
222
223
    /**
224
     *
225
     * @param string $format
226
     * @return $this
227
     */
228
    public function setDateFormat(string $format): self
229
    {
230
        $this->dateFormat = $format;
231
232
        return $this;
233
    }
234
235
    /**
236
     * Set the drive options
237
     * @param array<string, mixed> $options
238
     */
239
    public function setOptions(array $options): void
240
    {
241
        foreach ($options as $name => $value) {
242
            $this->{$name} = $value;
243
        }
244
    }
245
246
    /**
247
     * @param array<mixed> $params
248
     *
249
     * @return string
250
     */
251
    public function params(array $params): string
252
    {
253
        return implode(', ', array_map([$this, 'param'], $params));
254
    }
255
256
    /**
257
     * @return array<mixed>
258
     */
259
    public function getParams(): array
260
    {
261
        $params = $this->params;
262
        $this->params = [];
263
264
        return $params;
265
    }
266
267
    /**
268
     * @param Expression[]|string[] $columns
269
     *
270
     * @return string
271
     */
272
    public function columns(array $columns): string
273
    {
274
        return implode(', ', array_map([$this, 'quoteIdentifier'], $columns));
275
    }
276
277
    /**
278
     * @param string $value
279
     *
280
     * @return string
281
     */
282
    public function quote(string $value): string
283
    {
284
        return "'" . str_replace("'", "''", $value) . "'";
285
    }
286
287
    /**
288
     * Return the SQL for the current database
289
     * @return array<string, mixed>
290
     */
291
    public function getDatabaseName(): array
292
    {
293
        return [
294
            'sql' => 'SELECT database()',
295
            'params' => []
296
        ];
297
    }
298
299
    /**
300
     *
301
     * @param string $current
302
     * @param string $new
303
     * @return array<string, mixed>
304
     */
305
    public function renameTable(string $current, string $new): array
306
    {
307
        return [
308
            'sql' => 'RENAME TABLE ' . $this->quoteIdentifier($current)
309
            . ' TO ' . $this->quoteIdentifier($new),
310
            'params' => []
311
        ];
312
    }
313
314
    /**
315
     *
316
     * @param string $database
317
     * @return array<string, mixed>
318
     */
319
    public function getTables(string $database): array
320
    {
321
        $sql = sprintf(
322
            'SELECT %s FROM %s.%s WHERE table_type = ? '
323
                . 'AND table_schema = ? ORDER BY %s ASC',
324
            $this->quoteIdentifier('table_name'),
325
            $this->quoteIdentifier('information_schema'),
326
            $this->quoteIdentifier('tables'),
327
            $this->quoteIdentifier('table_name'),
328
        );
329
330
        return [
331
            'sql' => $sql,
332
            'params' => ['BASE TABLE', $database]
333
        ];
334
    }
335
336
    /**
337
     *
338
     * @param string $database
339
     * @return array<string, mixed>
340
     */
341
    public function getViews(string $database): array
342
    {
343
        $sql = sprintf(
344
            'SELECT %s FROM %s.%s WHERE table_type = ? '
345
                . 'AND table_schema = ? ORDER BY %s ASC',
346
            $this->quoteIdentifier('table_name'),
347
            $this->quoteIdentifier('information_schema'),
348
            $this->quoteIdentifier('tables'),
349
            $this->quoteIdentifier('table_name'),
350
        );
351
352
        return [
353
            'sql' => $sql,
354
            'params' => ['VIEW', $database]
355
        ];
356
    }
357
358
    /**
359
     *
360
     * @param string $database
361
     * @param string $table
362
     * @return array<string, mixed>
363
     */
364
    public function getColumns(string $database, string $table): array
365
    {
366
        $sql = sprintf(
367
            'SELECT %s AS %s, %s AS %s FROM %s.%s WHERE %s = ? '
368
                . 'AND %s = ? ORDER BY %s ASC',
369
            $this->quoteIdentifier('column_name'),
370
            $this->quoteIdentifier('name'),
371
            $this->quoteIdentifier('column_type'),
372
            $this->quoteIdentifier('type'),
373
            $this->quoteIdentifier('information_schema'),
374
            $this->quoteIdentifier('columns'),
375
            $this->quoteIdentifier('table_schema'),
376
            $this->quoteIdentifier('table_name'),
377
            $this->quoteIdentifier('ordinal_position'),
378
        );
379
380
        return [
381
            'sql' => $sql,
382
            'params' => [$database, $table]
383
        ];
384
    }
385
386
    /**
387
     *
388
     * @param string $database
389
     * @param string $view
390
     * @return array<string, mixed>
391
     */
392
    public function getViewColumns(string $database, string $view): array
393
    {
394
        $sql = sprintf(
395
            'SELECT %s AS %s, %s AS %s FROM %s.%s WHERE %s = ? '
396
                . 'AND %s = ? ORDER BY %s ASC',
397
            $this->quoteIdentifier('column_name'),
398
            $this->quoteIdentifier('name'),
399
            $this->quoteIdentifier('column_type'),
400
            $this->quoteIdentifier('type'),
401
            $this->quoteIdentifier('information_schema'),
402
            $this->quoteIdentifier('columns'),
403
            $this->quoteIdentifier('table_schema'),
404
            $this->quoteIdentifier('table_name'),
405
            $this->quoteIdentifier('ordinal_position'),
406
        );
407
408
        return [
409
            'sql' => $sql,
410
            'params' => [$database, $view]
411
        ];
412
    }
413
414
    /**
415
     *
416
     * @param CreateTable $schema
417
     * @return array<int, array<string, mixed>>
418
     */
419
    public function create(CreateTable $schema): array
420
    {
421
        $sql = 'CREATE TABLE ' . $this->quoteIdentifier($schema->getTableName());
422
        $sql .= "(\n";
423
        $sql .= $this->getSchemaColumns($schema->getColumns());
424
        $sql .= $this->getPrimaryKey($schema);
425
        $sql .= $this->getUniqueKeys($schema);
426
        $sql .= $this->getForeignKeys($schema);
427
        $sql .= ")\n";
428
        $sql .= $this->getEngine($schema);
429
430
        $commands = [];
431
432
        $commands[] = [
433
            'sql' => $sql,
434
            'params' => []
435
        ];
436
437
        foreach ($this->getIndexKeys($schema) as $index) {
438
            $commands[] = [
439
                'sql' => $index,
440
                'params' => []
441
            ];
442
        }
443
444
        return $commands;
445
    }
446
447
    /**
448
     *
449
     * @param AlterTable $schema
450
     * @return array<int, array<string, mixed>>
451
     */
452
    public function alter(AlterTable $schema): array
453
    {
454
        $commands = [];
455
456
        foreach ($schema->getCommands() as $command) {
457
            $callback = 'get' . ucfirst($command['type']);
458
            $sql = $this->{$callback}($schema, $command['data']);
459
460
            if ($sql === '') {
461
                continue;
462
            }
463
464
            $commands[] = [
465
                'sql' => $sql,
466
                'params' => $this->getParams()
467
            ];
468
        }
469
470
        return $commands;
471
    }
472
473
    /**
474
     *
475
     * @param string $table
476
     * @return array<string, mixed>
477
     */
478
    public function drop(string $table): array
479
    {
480
        return [
481
            'sql' => 'DROP TABLE ' . $this->quoteIdentifier($table),
482
            'params' => []
483
        ];
484
    }
485
486
    /**
487
     *
488
     * @param string $table
489
     * @return array<string, mixed>
490
     */
491
    public function truncate(string $table): array
492
    {
493
        return [
494
            'sql' => 'TRUNCATE TABLE ' . $this->quoteIdentifier($table),
495
            'params' => []
496
        ];
497
    }
498
499
    /**
500
     * @param mixed $value
501
     *
502
     * @return string
503
     */
504
    protected function param($value): string
505
    {
506
        if ($value instanceof Expression) {
507
            return $this->getExpressions($value->getExpressions());
508
        } elseif ($value instanceof DateTime) {
509
            $this->params[] = $value->format($this->dateFormat);
510
        } else {
511
            $this->params[] = $value;
512
        }
513
514
        return '?';
515
    }
516
517
    /**
518
     * Get the value by convert it to the type
519
     * @param mixed $value
520
     * @return mixed
521
     *
522
     */
523
    protected function value($value)
524
    {
525
        if (is_numeric($value)) {
526
            return $value;
527
        }
528
529
        if (is_bool($value)) {
530
            return $value ? 1 : 0;
531
        }
532
533
        if (is_string($value)) {
534
            return "'" . str_replace("'", "''", $value) . "'";
535
        }
536
537
        return 'NULL';
538
    }
539
540
    /**
541
     * Add quote identifier like "", ``
542
     * @param mixed $value
543
     *
544
     * @return string
545
     */
546
    protected function quoteIdentifier($value): string
547
    {
548
        if ($value instanceof Expression) {
549
            return $this->getExpressions($value->getExpressions());
550
        }
551
552
        $identifiers = [];
553
554
        foreach (explode('.', $value) as $segment) {
555
            if ($segment === '*') {
556
                $identifiers[] = $segment;
557
            } else {
558
                $identifiers[] = sprintf($this->identifier, $segment);
559
            }
560
        }
561
562
        return implode('.', $identifiers);
563
    }
564
565
    /**
566
     *
567
     * @param array<mixed> $values
568
     * @param string $separator
569
     * @return string
570
     */
571
    protected function quoteIdentifiers(array $values, string $separator = ', '): string
572
    {
573
        return implode($separator, array_map([$this, 'quoteIdentifier'], $values));
574
    }
575
576
    /**
577
     * Handle expressions
578
     * @param array<int, array<string, mixed>> $expressions
579
     *
580
     * @return string
581
     */
582
    protected function getExpressions(array $expressions): string
583
    {
584
        $sql = [];
585
586
        foreach ($expressions as $expression) {
587
            switch ($expression['type']) {
588
                case 'column':
589
                    $sql[] = $this->quoteIdentifier($expression['value']);
590
                    break;
591
                case 'op':
592
                    $sql[] = $expression['value'];
593
                    break;
594
                case 'value':
595
                    $sql[] = $this->param($expression['value']);
596
                    break;
597
                case 'group':
598
                    $expr = $expression['value'];
599
                    $sql[] = '(' . $this->getExpressions($expr->getExpressions()) . ')';
600
                    break;
601
                case 'function':
602
                    $sql[] = $this->getSqlFunction($expression['value']);
603
                    break;
604
                case 'subquery':
605
                    $subQuery = $expression['value'];
606
                    $sql[] = '(' . $this->select($subQuery->getQueryStatement()) . ')';
607
                    break;
608
            }
609
        }
610
611
        return implode(' ', $sql);
612
    }
613
614
    /**
615
     * Handle SQL function
616
     * @param array<string, mixed> $functions
617
     *
618
     * @return string
619
     */
620
    protected function getSqlFunction(array $functions): string
621
    {
622
        $method = $functions['type'] . $functions['name'];
623
624
        return $this->{$method}($functions);
625
    }
626
627
    /**
628
     * Handle columns
629
     * @param array<int, array<string, mixed>> $columns
630
     *
631
     * @return string
632
     */
633
    protected function getColumnList(array $columns): string
634
    {
635
        if (empty($columns)) {
636
            return '*';
637
        }
638
        $sql = [];
639
640
        foreach ($columns as $column) {
641
            if (isset($column['alias'])) {
642
                $sql[] = $this->quoteIdentifier($column['name'])
643
                        . ' AS ' . $this->quoteIdentifier($column['alias']);
644
            } else {
645
                $sql[] = $this->quoteIdentifier($column['name']);
646
            }
647
        }
648
649
        return implode(', ', $sql);
650
    }
651
652
    /**
653
     * Handle schema columns
654
     * @param array<int|string, BaseColumn> $columns list of BaseColumn
655
     * @return string
656
     */
657
    protected function getSchemaColumns(array $columns): string
658
    {
659
        $sql = [];
660
661
        foreach ($columns as $column) {
662
            $line = $this->quoteIdentifier($column->getName());
663
            $line .= $this->getColumnType($column);
664
            $line .= $this->getColumnModifiers($column);
665
666
            $sql[] = $line;
667
        }
668
669
        return implode(",\n", $sql);
670
    }
671
672
    /**
673
     *
674
     * @param BaseColumn $column
675
     * @return string
676
     */
677
    protected function getColumnType(BaseColumn $column): string
678
    {
679
        $type = $column->getType();
680
        $result = '';
681
        if (is_string($type)) {
682
            $callback = 'getType' . ucfirst($type);
683
            $result = trim($this->{$callback}($column));
684
685
            if ($result !== '') {
686
                $result = ' ' . $result;
687
            }
688
        }
689
690
        return $result;
691
    }
692
693
    /**
694
     *
695
     * @param BaseColumn $column
696
     * @return string
697
     */
698
    protected function getColumnModifiers(BaseColumn $column): string
699
    {
700
        $line = '';
701
        foreach ($this->modifiers as $modifier) {
702
            $callback = 'getModifier' . ucfirst($modifier);
703
            $result = trim($this->{$callback}($column));
704
705
706
            if ($result !== '') {
707
                $result = ' ' . $result;
708
            }
709
710
            $line .= $result;
711
        }
712
713
        return $line;
714
    }
715
716
    /**
717
     *
718
     * @param BaseColumn $column
719
     * @return string
720
     */
721
    protected function getTypeInteger(BaseColumn $column): string
722
    {
723
        return 'INT';
724
    }
725
726
    /**
727
     *
728
     * @param BaseColumn $column
729
     * @return string
730
     */
731
    protected function getTypeFloat(BaseColumn $column): string
732
    {
733
        return 'FLOAT';
734
    }
735
736
    /**
737
     *
738
     * @param BaseColumn $column
739
     * @return string
740
     */
741
    protected function getTypeDouble(BaseColumn $column): string
742
    {
743
        return 'DOUBLE';
744
    }
745
746
    /**
747
     *
748
     * @param BaseColumn $column
749
     * @return string
750
     */
751
    protected function getTypeDecimal(BaseColumn $column): string
752
    {
753
        return 'DECIMAL';
754
    }
755
756
    /**
757
     *
758
     * @param BaseColumn $column
759
     * @return string
760
     */
761
    protected function getTypeEnum(BaseColumn $column): string
762
    {
763
        return 'ENUM';
764
    }
765
766
    /**
767
     *
768
     * @param BaseColumn $column
769
     * @return string
770
     */
771
    protected function getTypeBoolean(BaseColumn $column): string
772
    {
773
        return 'BOOLEAN';
774
    }
775
776
    /**
777
     *
778
     * @param BaseColumn $column
779
     * @return string
780
     */
781
    protected function getTypeBinary(BaseColumn $column): string
782
    {
783
        return 'BLOB';
784
    }
785
786
    /**
787
     *
788
     * @param BaseColumn $column
789
     * @return string
790
     */
791
    protected function getTypeText(BaseColumn $column): string
792
    {
793
        return 'TEXT';
794
    }
795
796
    /**
797
     *
798
     * @param BaseColumn $column
799
     * @return string
800
     */
801
    protected function getTypeString(BaseColumn $column): string
802
    {
803
        return 'VARCHAR(' . $this->value($column->get('length', 255)) . ')';
804
    }
805
806
    /**
807
     *
808
     * @param BaseColumn $column
809
     * @return string
810
     */
811
    protected function getTypeFixed(BaseColumn $column): string
812
    {
813
        return 'CHAR(' . $this->value($column->get('length', 255)) . ')';
814
    }
815
816
    /**
817
     *
818
     * @param BaseColumn $column
819
     * @return string
820
     */
821
    protected function getTypeTime(BaseColumn $column): string
822
    {
823
        return 'TIME';
824
    }
825
826
    /**
827
     *
828
     * @param BaseColumn $column
829
     * @return string
830
     */
831
    protected function getTypeTimestamp(BaseColumn $column): string
832
    {
833
        return 'TIMESTAMP';
834
    }
835
836
    /**
837
     *
838
     * @param BaseColumn $column
839
     * @return string
840
     */
841
    protected function getTypeDate(BaseColumn $column): string
842
    {
843
        return 'DATE';
844
    }
845
846
    /**
847
     *
848
     * @param BaseColumn $column
849
     * @return string
850
     */
851
    protected function getTypeDatetime(BaseColumn $column): string
852
    {
853
        return 'DATETIME';
854
    }
855
856
    /**
857
     *
858
     * @param BaseColumn $column
859
     * @return string
860
     */
861
    protected function getModifierUnsigned(BaseColumn $column): string
862
    {
863
        return $column->get('unsigned', false) ? 'UNSIGNED' : '';
864
    }
865
866
    /**
867
     *
868
     * @param BaseColumn $column
869
     * @return string
870
     */
871
    protected function getModifierNullable(BaseColumn $column): string
872
    {
873
        return $column->get('nullable', true) ? '' : 'NOT NULL';
874
    }
875
876
    /**
877
     *
878
     * @param BaseColumn $column
879
     * @return string
880
     */
881
    protected function getModifierDefault(BaseColumn $column): string
882
    {
883
        return $column->get('default', null) === null ? '' : 'DEFAULT ' . $this->value($column->get('default'));
884
    }
885
886
    /**
887
     *
888
     * @param BaseColumn $column
889
     * @return string
890
     */
891
    protected function getModifierDescription(BaseColumn $column): string
892
    {
893
        return $column->get('description', null) === null ? '' : 'COMMENT ' . $this->value($column->get('description'));
894
    }
895
896
    /**
897
     *
898
     * @param BaseColumn $column
899
     * @return string
900
     */
901
    protected function getModifierAfter(BaseColumn $column): string
902
    {
903
        return $column->get('after', null) === null ? '' : 'AFTER ' . $this->quoteIdentifier($column->get('after'));
904
    }
905
906
    /**
907
     *
908
     * @param BaseColumn $column
909
     * @return string
910
     */
911
    protected function getModifierAutoincrement(BaseColumn $column): string
912
    {
913
        if (
914
            $column->getType() !== 'integer'
915
            || !in_array($column->get('size', 'normal'), $this->serials)
916
        ) {
917
            return '';
918
        }
919
        return $column->get('autoincrement', false) ? $this->autoincrement : '';
920
    }
921
922
    /**
923
     *
924
     * @param CreateTable $schema
925
     * @return string
926
     */
927
    protected function getPrimaryKey(CreateTable $schema): string
928
    {
929
        $primaryKey = $schema->getPrimaryKey();
930
        if (empty($primaryKey)) {
931
            return '';
932
        }
933
934
        return ",\n" . 'CONSTRAINT ' . $this->quoteIdentifier($primaryKey['name'])
935
                . ' PRIMARY KEY (' . $this->quoteIdentifiers($primaryKey['columns']) . ')';
936
    }
937
938
    /**
939
     *
940
     * @param CreateTable $schema
941
     * @return string
942
     */
943
    protected function getUniqueKeys(CreateTable $schema): string
944
    {
945
        $indexes = $schema->getUniqueKeys();
946
947
        if (empty($indexes)) {
948
            return '';
949
        }
950
951
        $sql = [];
952
953
        foreach ($indexes as $name => $columns) {
954
            $sql[] = 'CONSTRAINT ' . $this->quoteIdentifier($name)
955
                    . ' UNIQUE (' . $this->quoteIdentifiers($columns) . ')';
956
        }
957
958
        return ",\n" . implode(",\n", $sql);
959
    }
960
961
    /**
962
     *
963
     * @param CreateTable $schema
964
     * @return array<int, string>
965
     */
966
    protected function getIndexKeys(CreateTable $schema): array
967
    {
968
        $indexes = $schema->getIndexes();
969
970
        if (empty($indexes)) {
971
            return [];
972
        }
973
974
        $sql = [];
975
        $table = $this->quoteIdentifier($schema->getTableName());
976
977
        foreach ($indexes as $name => $columns) {
978
            $sql[] = 'CREATE INDEX ' . $this->quoteIdentifier($name)
979
                    . ' ON ' . $table . '(' . $this->quoteIdentifiers($columns) . ')';
980
        }
981
982
        return $sql;
983
    }
984
985
    /**
986
     *
987
     * @param CreateTable $schema
988
     * @return string
989
     */
990
    protected function getForeignKeys(CreateTable $schema): string
991
    {
992
        $keys = $schema->getForeignKeys();
993
994
        if (empty($keys)) {
995
            return '';
996
        }
997
998
        $sql = [];
999
1000
        foreach ($keys as $name => $key) {
1001
            $cmd = 'CONSTRAINT ' . $this->quoteIdentifier($name)
1002
                    . ' FOREIGN KEY (' . $this->quoteIdentifiers($key->getColumns()) . ') ';
1003
            $cmd .= 'REFERENCES ' . $this->quoteIdentifier($key->getReferenceTable())
1004
                    . ' (' . $this->quoteIdentifiers($key->getReferenceColumns()) . ')';
1005
1006
            foreach ($key->getActions() as $actionName => $action) {
1007
                $cmd .= ' ' . $actionName . ' ' . $action;
1008
            }
1009
            $sql[] = $cmd;
1010
        }
1011
1012
        return ",\n" . implode(",\n", $sql);
1013
    }
1014
1015
    /**
1016
     *
1017
     * @param CreateTable $schema
1018
     * @return string
1019
     */
1020
    protected function getEngine(CreateTable $schema): string
1021
    {
1022
        $engine = $schema->getEngine();
1023
        if ($engine === null) {
1024
            return '';
1025
        }
1026
1027
        return ' ENGINE = ' . strtoupper($engine);
1028
    }
1029
1030
    /**
1031
     *
1032
     * @param AlterTable $schema
1033
     * @param mixed $data
1034
     * @return string
1035
     */
1036
    protected function getDropPrimaryKey(AlterTable $schema, $data): string
1037
    {
1038
        return sprintf(
1039
            'ALTER TABLE %s DROP CONSTRAINT %s',
1040
            $this->quoteIdentifier($schema->getTableName()),
1041
            $this->quoteIdentifier($data)
1042
        );
1043
    }
1044
1045
    /**
1046
     *
1047
     * @param AlterTable $schema
1048
     * @param mixed $data
1049
     * @return string
1050
     */
1051
    protected function getDropUniqueKey(AlterTable $schema, $data): string
1052
    {
1053
        return sprintf(
1054
            'ALTER TABLE %s DROP CONSTRAINT %s',
1055
            $this->quoteIdentifier($schema->getTableName()),
1056
            $this->quoteIdentifier($data)
1057
        );
1058
    }
1059
1060
    /**
1061
     *
1062
     * @param AlterTable $schema
1063
     * @param mixed $data
1064
     * @return string
1065
     */
1066
    protected function getDropIndex(AlterTable $schema, $data): string
1067
    {
1068
        return sprintf(
1069
            'DROP INDEX %s.%s',
1070
            $this->quoteIdentifier($schema->getTableName()),
1071
            $this->quoteIdentifier($data)
1072
        );
1073
    }
1074
1075
    /**
1076
     *
1077
     * @param AlterTable $schema
1078
     * @param mixed $data
1079
     * @return string
1080
     */
1081
    protected function getDropForeignKey(AlterTable $schema, $data): string
1082
    {
1083
        return sprintf(
1084
            'ALTER TABLE %s DROP CONSTRAINT %s',
1085
            $this->quoteIdentifier($schema->getTableName()),
1086
            $this->quoteIdentifier($data)
1087
        );
1088
    }
1089
1090
    /**
1091
     *
1092
     * @param AlterTable $schema
1093
     * @param mixed $data
1094
     * @return string
1095
     */
1096
    protected function getDropColumn(AlterTable $schema, $data): string
1097
    {
1098
        return sprintf(
1099
            'ALTER TABLE %s DROP COLUMN %s',
1100
            $this->quoteIdentifier($schema->getTableName()),
1101
            $this->quoteIdentifier($data)
1102
        );
1103
    }
1104
1105
    /**
1106
     *
1107
     * @param AlterTable $schema
1108
     * @param mixed $data
1109
     * @return string
1110
     */
1111
    protected function getRenameColumn(AlterTable $schema, $data): string
1112
    {
1113
        //TODO: please implement it in subclass
1114
        return '';
1115
    }
1116
1117
    /**
1118
     *
1119
     * @param AlterTable $schema
1120
     * @param mixed $data
1121
     * @return string
1122
     */
1123
    protected function getModifyColumn(AlterTable $schema, $data): string
1124
    {
1125
        return sprintf(
1126
            'ALTER TABLE %s MODIFY COLUMN %s',
1127
            $this->quoteIdentifier($schema->getTableName()),
1128
            $this->getSchemaColumns([$data])
1129
        );
1130
    }
1131
1132
    /**
1133
     *
1134
     * @param AlterTable $schema
1135
     * @param mixed $data
1136
     * @return string
1137
     */
1138
    protected function getAddColumn(AlterTable $schema, $data): string
1139
    {
1140
        return sprintf(
1141
            'ALTER TABLE %s ADD COLUMN %s',
1142
            $this->quoteIdentifier($schema->getTableName()),
1143
            $this->getSchemaColumns([$data])
1144
        );
1145
    }
1146
1147
    /**
1148
     *
1149
     * @param AlterTable $schema
1150
     * @param mixed $data
1151
     * @return string
1152
     */
1153
    protected function getAddPrimary(AlterTable $schema, $data): string
1154
    {
1155
        return sprintf(
1156
            'ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s)',
1157
            $this->quoteIdentifier($schema->getTableName()),
1158
            $this->quoteIdentifier($data['name']),
1159
            $this->quoteIdentifiers($data['columns'])
1160
        );
1161
    }
1162
1163
    /**
1164
     *
1165
     * @param AlterTable $schema
1166
     * @param mixed $data
1167
     * @return string
1168
     */
1169
    protected function getAddUnique(AlterTable $schema, $data): string
1170
    {
1171
        return sprintf(
1172
            'ALTER TABLE %s ADD CONSTRAINT %s UNIQUE (%s)',
1173
            $this->quoteIdentifier($schema->getTableName()),
1174
            $this->quoteIdentifier($data['name']),
1175
            $this->quoteIdentifiers($data['columns'])
1176
        );
1177
    }
1178
1179
    /**
1180
     *
1181
     * @param AlterTable $schema
1182
     * @param mixed $data
1183
     * @return string
1184
     */
1185
    protected function getAddIndex(AlterTable $schema, $data): string
1186
    {
1187
        return sprintf(
1188
            'CREATE INDEX %s ON %s (%s)',
1189
            $this->quoteIdentifier($data['name']),
1190
            $this->quoteIdentifier($schema->getTableName()),
1191
            $this->quoteIdentifiers($data['columns'])
1192
        );
1193
    }
1194
1195
    /**
1196
     *
1197
     * @param AlterTable $schema
1198
     * @param mixed $data
1199
     * @return string
1200
     */
1201
    protected function getAddForeign(AlterTable $schema, $data): string
1202
    {
1203
        /** @var ForeignKey $key */
1204
        $key = $data['foreign'];
1205
        return sprintf(
1206
            'ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)',
1207
            $this->quoteIdentifier($schema->getTableName()),
1208
            $this->quoteIdentifier($data['name']),
1209
            $this->quoteIdentifiers($key->getColumns()),
1210
            $this->quoteIdentifier($key->getReferenceTable()),
1211
            $this->quoteIdentifiers($key->getReferenceColumns()),
1212
        );
1213
    }
1214
1215
    /**
1216
     *
1217
     * @param AlterTable $schema
1218
     * @param mixed $data
1219
     * @return string
1220
     */
1221
    protected function getSetDefaultValue(AlterTable $schema, $data): string
1222
    {
1223
        return sprintf(
1224
            'ALTER TABLE %s ALTER COLUMN %s SET DEFAULT (%s)',
1225
            $this->quoteIdentifier($schema->getTableName()),
1226
            $this->quoteIdentifier($data['column']),
1227
            $this->value($data['value'])
1228
        );
1229
    }
1230
1231
    /**
1232
     *
1233
     * @param AlterTable $schema
1234
     * @param mixed $data
1235
     * @return string
1236
     */
1237
    protected function getDropDefaultValue(AlterTable $schema, $data): string
1238
    {
1239
        return sprintf(
1240
            'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
1241
            $this->quoteIdentifier($schema->getTableName()),
1242
            $this->quoteIdentifier($data)
1243
        );
1244
    }
1245
1246
    /**
1247
     * Handle into the table
1248
     *
1249
     * @param string|null $table
1250
     *
1251
     * @return string
1252
     */
1253
    protected function getInto(?string $table): string
1254
    {
1255
        if ($table === null) {
1256
            return '';
1257
        }
1258
        return ' INTO ' . $this->quoteIdentifier($table);
1259
    }
1260
1261
    /**
1262
     * Handle tables
1263
     * @param array<mixed, string> $tables
1264
     *
1265
     * @return string
1266
     */
1267
    protected function getTableList(array $tables): string
1268
    {
1269
        if (empty($tables)) {
1270
            return '';
1271
        }
1272
        $sql = [];
1273
        foreach ($tables as $name => $alias) {
1274
            if (is_string($name)) {
1275
                $sql[] = $this->quoteIdentifier($name) . ' AS ' . $this->quoteIdentifier($alias);
1276
            } else {
1277
                $sql[] = $this->quoteIdentifier($alias);
1278
            }
1279
        }
1280
1281
        return implode(', ', $sql);
1282
    }
1283
1284
    /**
1285
     * Handle for joins
1286
     * @param array<int, mixed> $joins
1287
     *
1288
     * @return string
1289
     */
1290
    protected function getJoins(array $joins): string
1291
    {
1292
        if (empty($joins)) {
1293
            return '';
1294
        }
1295
        $sql = [];
1296
1297
        foreach ($joins as $join) {
1298
            $joinObject = $join['join'];
1299
1300
            $on = '';
1301
            if ($joinObject) {
1302
                $on = $this->getJoinConditions($joinObject->getJoinConditions());
1303
            }
1304
1305
            if ($on !== '') {
1306
                $on = ' ON ' . $on;
1307
            }
1308
1309
            $sql[] = $join['type'] . ' JOIN ' . $this->getTableList($join['table']) . $on;
1310
        }
1311
1312
        return ' ' . implode(' ', $sql);
1313
    }
1314
1315
    /**
1316
     * Handle for the join conditions
1317
     * @param array<int, mixed> $conditions
1318
     * @return string
1319
     */
1320
    protected function getJoinConditions(array $conditions): string
1321
    {
1322
        if (empty($conditions)) {
1323
            return '';
1324
        }
1325
1326
        $sql = [];
1327
1328
        $sql[] = $this->{$conditions[0]['type']}($conditions[0]);
1329
1330
        $count = count($conditions);
1331
        for ($i = 1; $i < $count; $i++) {
1332
            $sql[] = $conditions[$i]['separator'] . ' ' . $this->{$conditions[$i]['type']}($conditions[$i]);
1333
        }
1334
1335
        return implode(' ', $sql);
1336
    }
1337
1338
    /**
1339
     * Handle group by
1340
     * @param Expression[]|string[] $groupBy
1341
     *
1342
     * @return string
1343
     */
1344
    protected function getGroupBy(array $groupBy): string
1345
    {
1346
        return empty($groupBy) ? '' : ' GROUP BY ' . $this->columns($groupBy);
1347
    }
1348
1349
    /**
1350
     * Handle for Order
1351
     * @param array<int, array<string, mixed>> $orders
1352
     * @return string
1353
     */
1354
    protected function getOrders(array $orders): string
1355
    {
1356
        if (empty($orders)) {
1357
            return '';
1358
        }
1359
        $sql = [];
1360
        foreach ($orders as $order) {
1361
            $sql[] = $this->columns($order['columns']) . ' ' . $order['order'];
1362
        }
1363
1364
        return ' ORDER BY ' . implode(', ', $sql);
1365
    }
1366
1367
    /**
1368
     * Handle columns for set (UPDATE)
1369
     * @param array<int, array<string, mixed>> $columns
1370
     * @return string
1371
     */
1372
    protected function getSetColumns(array $columns): string
1373
    {
1374
        if (empty($columns)) {
1375
            return '';
1376
        }
1377
        $sql = [];
1378
1379
        foreach ($columns as $column) {
1380
            $sql[] = $this->quoteIdentifier($column['column']) . ' = ' . $this->param($column['value']);
1381
        }
1382
1383
        return ' SET ' . implode(', ', $sql);
1384
    }
1385
1386
    /**
1387
     * Handler where
1388
     * @param array<int, mixed> $wheres
1389
     * @param bool $prefix
1390
     *
1391
     * @return string
1392
     */
1393
    protected function getWheres(array $wheres, bool $prefix = true): string
1394
    {
1395
        $sql = $this->getWheresHaving($wheres);
1396
        if (empty($sql)) {
1397
            return '';
1398
        }
1399
        return ($prefix ? ' WHERE ' : '') . $sql;
1400
    }
1401
1402
    /**
1403
     * Handle for having
1404
     * @param array<int, mixed> $having
1405
     * @param bool $prefix
1406
     * @return string
1407
     */
1408
    protected function getHaving(array $having, bool $prefix = true): string
1409
    {
1410
        $sql = $this->getWheresHaving($having);
1411
        if (empty($sql)) {
1412
            return '';
1413
        }
1414
        return ($prefix ? ' HAVING ' : '') . $sql;
1415
    }
1416
1417
    /**
1418
     * Return the build part for where or having
1419
     * @param array<int, mixed> $values
1420
     *
1421
     * @return string
1422
     */
1423
    protected function getWheresHaving(array $values): string
1424
    {
1425
        if (empty($values)) {
1426
            return '';
1427
        }
1428
        $sql = [];
1429
        $sql[] = $this->{$values[0]['type']}($values[0]);
1430
        $count = count($values);
1431
1432
        for ($i = 1; $i < $count; $i++) {
1433
            $sql[] = $values[$i]['separator'] . ' ' . $this->{$values[$i]['type']}($values[$i]);
1434
        }
1435
        return implode(' ', $sql);
1436
    }
1437
1438
    /**
1439
     * Handle for insert values
1440
     * @param array<int, mixed> $values
1441
     * @return string
1442
     */
1443
    protected function getInsertValues(array $values): string
1444
    {
1445
        return ' VALUES (' . $this->params($values) . ')';
1446
    }
1447
1448
    /**
1449
     * Handle for limit
1450
     * @param int $limit
1451
     * @return string
1452
     */
1453
    protected function getLimit(int $limit): string
1454
    {
1455
        return ($limit === 0) ? '' : ' LIMIT ' . $this->param($limit);
1456
    }
1457
1458
    /**
1459
     * Handle for offset
1460
     * @param int $offset
1461
     * @return string
1462
     */
1463
    protected function getOffset(int $offset): string
1464
    {
1465
        return ($offset < 0) ? '' : ' OFFSET ' . $this->param($offset);
1466
    }
1467
1468
    /**
1469
     * @param array<string, mixed> $join
1470
     * @return string
1471
     */
1472
    protected function joinColumn(array $join): string
1473
    {
1474
        return sprintf(
1475
            '%s %s %s',
1476
            $this->quoteIdentifier($join['column1']),
1477
            $join['operator'],
1478
            $this->quoteIdentifier($join['column2'])
1479
        );
1480
    }
1481
1482
    /**
1483
     * @param array<string, mixed> $join
1484
     * @return string
1485
     */
1486
    protected function joinNested(array $join): string
1487
    {
1488
        return '(' . $this->getJoinConditions($join['join']->getJoinConditions()) . ')';
1489
    }
1490
1491
    /**
1492
     * @param array<string, mixed> $join
1493
     * @return string
1494
     */
1495
    protected function joinExpression(array $join): string
1496
    {
1497
        return $this->quoteIdentifier($join['expression']);
1498
    }
1499
1500
    /**
1501
     * @param array<string, mixed> $where
1502
     * @return string
1503
     */
1504
    protected function whereColumn(array $where): string
1505
    {
1506
        return sprintf(
1507
            '%s %s %s',
1508
            $this->quoteIdentifier($where['column']),
1509
            $where['operator'],
1510
            $this->param($where['value'])
1511
        );
1512
    }
1513
1514
    /**
1515
     * @param array<string, mixed> $where
1516
     * @return string
1517
     */
1518
    protected function whereIn(array $where): string
1519
    {
1520
        return sprintf(
1521
            '%s %s (%s)',
1522
            $this->quoteIdentifier($where['column']),
1523
            $where['not'] ? 'NOT IN' : 'IN',
1524
            $this->params($where['value'])
1525
        );
1526
    }
1527
1528
    /**
1529
     * @param array<string, mixed> $where
1530
     * @return string
1531
     */
1532
    protected function whereInSelect(array $where): string
1533
    {
1534
        return sprintf(
1535
            '%s %s (%s)',
1536
            $this->quoteIdentifier($where['column']),
1537
            $where['not'] ? 'NOT IN' : 'IN',
1538
            $this->select($where['subquery']->getQueryStatement())
1539
        );
1540
    }
1541
1542
    /**
1543
     * @param array<string, mixed> $where
1544
     * @return string
1545
     */
1546
    protected function whereNested(array $where): string
1547
    {
1548
        return '(' . $this->getWheres($where['clause'], false) . ')';
1549
    }
1550
1551
    /**
1552
     * @param array<string, mixed> $where
1553
     * @return string
1554
     */
1555
    public function whereExists(array $where): string
1556
    {
1557
        return sprintf(
1558
            '%s (%s)',
1559
            $where['not'] ? 'NOT EXISTS' : 'EXISTS',
1560
            $this->select($where['subquery']->getQueryStatement())
1561
        );
1562
    }
1563
1564
    /**
1565
     * @param array<string, mixed> $where
1566
     * @return string
1567
     */
1568
    protected function whereNull(array $where): string
1569
    {
1570
        return sprintf(
1571
            '%s %s',
1572
            $this->quoteIdentifier($where['column']),
1573
            $where['not'] ? 'IS NOT NULL' : 'IS NULL',
1574
        );
1575
    }
1576
1577
    /**
1578
     * @param array<string, mixed> $where
1579
     * @return string
1580
     */
1581
    protected function whereBetween(array $where): string
1582
    {
1583
        return sprintf(
1584
            '%s %s %s AND %s',
1585
            $this->quoteIdentifier($where['column']),
1586
            $where['not'] ? 'NOT BETWEEN' : 'BETWEEN',
1587
            $this->param($where['value1']),
1588
            $this->param($where['value2']),
1589
        );
1590
    }
1591
1592
    /**
1593
     * @param array<string, mixed> $where
1594
     * @return string
1595
     */
1596
    protected function whereLike(array $where): string
1597
    {
1598
        return sprintf(
1599
            '%s %s %s',
1600
            $this->quoteIdentifier($where['column']),
1601
            $where['not'] ? 'NOT LIKE' : 'LIKE',
1602
            $this->param($where['pattern']),
1603
        );
1604
    }
1605
1606
    /**
1607
     * @param array<string, mixed> $where
1608
     * @return string
1609
     */
1610
    protected function whereSubQuery(array $where): string
1611
    {
1612
        return sprintf(
1613
            '%s %s (%s)',
1614
            $this->quoteIdentifier($where['column']),
1615
            $where['operator'],
1616
            $this->select($where['subquery']->getQueryStatement())
1617
        );
1618
    }
1619
1620
    /**
1621
     * @param array<string, mixed> $where
1622
     * @return string
1623
     */
1624
    protected function whereNop(array $where): string
1625
    {
1626
        return $this->quoteIdentifier($where['column']);
1627
    }
1628
1629
    /**
1630
     * @param array<string, mixed> $having
1631
     * @return string
1632
     */
1633
    protected function havingCondition(array $having): string
1634
    {
1635
        return sprintf(
1636
            '%s %s %s',
1637
            $this->quoteIdentifier($having['aggregate']),
1638
            $having['operator'],
1639
            $having['value']
1640
        );
1641
    }
1642
1643
    /**
1644
     * @param array<string, mixed> $having
1645
     * @return string
1646
     */
1647
    protected function havingNested(array $having): string
1648
    {
1649
        return '(' . $this->getHaving($having['conditions'], false) . ')';
1650
    }
1651
1652
    /**
1653
     * @param array<string, mixed> $having
1654
     * @return string
1655
     */
1656
    protected function havingBetween(array $having): string
1657
    {
1658
        return sprintf(
1659
            '%s %s %s AND %s',
1660
            $this->quoteIdentifier($having['aggregate']),
1661
            $having['not'] ? 'NOT BETWEEN' : 'BETWEEN',
1662
            $this->param($having['value1']),
1663
            $this->param($having['value2']),
1664
        );
1665
    }
1666
1667
    /**
1668
     * @param array<string, mixed> $having
1669
     * @return string
1670
     */
1671
    protected function havingInSelect(array $having): string
1672
    {
1673
        return sprintf(
1674
            '%s %s (%s)',
1675
            $this->quoteIdentifier($having['aggregate']),
1676
            $having['not'] ? 'NOT IN' : 'IN',
1677
            $this->select($having['subquery']->getQueryStatement())
1678
        );
1679
    }
1680
1681
    /**
1682
     * @param array<string, mixed> $having
1683
     * @return string
1684
     */
1685
    protected function havingIn(array $having): string
1686
    {
1687
        return sprintf(
1688
            '%s %s (%s)',
1689
            $this->quoteIdentifier($having['aggregate']),
1690
            $having['not'] ? 'NOT IN' : 'IN',
1691
            $this->params($having['value'])
1692
        );
1693
    }
1694
1695
    /**
1696
     * Return aggregate function COUNT
1697
     * @param array<string, mixed> $function
1698
     * @return string
1699
     */
1700
    protected function aggregateFunctionCOUNT(array $function): string
1701
    {
1702
        return sprintf(
1703
            'COUNT(%s%s)',
1704
            $function['distinct'] ? 'DISTINCT ' : '',
1705
            $this->columns($function['column'])
1706
        );
1707
    }
1708
1709
    /**
1710
     * Return aggregate function AVG
1711
     * @param array<string, mixed> $function
1712
     * @return string
1713
     */
1714
    protected function aggregateFunctionAVG(array $function): string
1715
    {
1716
        return sprintf(
1717
            'AVG(%s%s)',
1718
            $function['distinct'] ? 'DISTINCT ' : '',
1719
            $this->quoteIdentifier($function['column'])
1720
        );
1721
    }
1722
1723
    /**
1724
     * Return aggregate function SUM
1725
     * @param array<string, mixed> $function
1726
     * @return string
1727
     */
1728
    protected function aggregateFunctionSUM(array $function): string
1729
    {
1730
        return sprintf(
1731
            'SUM(%s%s)',
1732
            $function['distinct'] ? 'DISTINCT ' : '',
1733
            $this->quoteIdentifier($function['column'])
1734
        );
1735
    }
1736
1737
    /**
1738
     * Return aggregate function MIN
1739
     * @param array<string, mixed> $function
1740
     * @return string
1741
     */
1742
    protected function aggregateFunctionMIN(array $function): string
1743
    {
1744
        return sprintf(
1745
            'MIN(%s%s)',
1746
            $function['distinct'] ? 'DISTINCT ' : '',
1747
            $this->quoteIdentifier($function['column'])
1748
        );
1749
    }
1750
1751
    /**
1752
     * Return aggregate function MAX
1753
     * @param array<string, mixed> $function
1754
     * @return string
1755
     */
1756
    protected function aggregateFunctionMAX(array $function): string
1757
    {
1758
        return sprintf(
1759
            'MAX(%s%s)',
1760
            $function['distinct'] ? 'DISTINCT ' : '',
1761
            $this->quoteIdentifier($function['column'])
1762
        );
1763
    }
1764
}
1765