Driver::getViewColumns()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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