Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
214 / 214
100.00% covered (success)
100.00%
37 / 37
CRAP
100.00% covered (success)
100.00%
1 / 1
TableStatement
100.00% covered (success)
100.00%
214 / 214
100.00% covered (success)
100.00%
37 / 37
62
100.00% covered (success)
100.00%
1 / 1
 option
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 options
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 renderOptions
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
1 / 1
4
 makeEngine
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 makeAutoIncrement
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 makeAvgRowLength
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 makeCharset
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
1 / 1
1
 makeChecksum
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makeCollate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeConnection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeDataDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeDelayKeyWrite
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makeIetfQuotes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makeIndexDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeInsertMethod
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 makeKeyBlockSize
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 makeEncrypted
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makeEncryptionKeyId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 makeMaxRows
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 makeMinRows
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 makePackKeys
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makePageChecksum
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makePageCompressed
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 makePageCompressionLevel
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makePassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeStatsAutoRecalc
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 makeStatsPersistent
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 makeStatsSamplePages
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 makeTablespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeRowFormat
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 makeSequence
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 makeTransactional
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makeUnion
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getValue
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 invalidValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 quote
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Database Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Database\Definition\Table;
11
12use Framework\Database\Statement;
13use InvalidArgumentException;
14
15/**
16 * Class TableStatement.
17 *
18 * @see https://mariadb.com/kb/en/create-table/#table-options
19 *
20 * @package database
21 */
22abstract class TableStatement extends Statement
23{
24    /**
25     * @see https://mariadb.com/kb/en/create-table/#storage-engine
26     *
27     * @var string
28     */
29    public const OPT_ENGINE = 'ENGINE';
30    /**
31     * @see https://mariadb.com/kb/en/create-table/#auto_increment
32     *
33     * @var string
34     */
35    public const OPT_AUTO_INCREMENT = 'AUTO_INCREMENT';
36    /**
37     * @see https://mariadb.com/kb/en/create-table/#avg_row_length
38     *
39     * @var string
40     */
41    public const OPT_AVG_ROW_LENGTH = 'AVG_ROW_LENGTH';
42    /**
43     * @see https://mariadb.com/kb/en/create-table/#default-character-setcharset
44     *
45     * @var string
46     */
47    public const OPT_CHARSET = 'CHARSET';
48    /**
49     * @see https://mariadb.com/kb/en/create-table/#checksumtable_checksum
50     *
51     * @var string
52     */
53    public const OPT_CHECKSUM = 'CHECKSUM';
54    /**
55     * @see https://mariadb.com/kb/en/create-table/#default-collate
56     *
57     * @var string
58     */
59    public const OPT_COLLATE = 'COLLATE';
60    /**
61     * @see https://mariadb.com/kb/en/create-table/#comment
62     *
63     * @var string
64     */
65    public const OPT_COMMENT = 'COMMENT';
66    /**
67     * @see https://mariadb.com/kb/en/create-table/#connection
68     *
69     * @var string
70     */
71    public const OPT_CONNECTION = 'CONNECTION';
72    /**
73     * @see https://mariadb.com/kb/en/create-table/#data-directoryindex-directory
74     *
75     * @var string
76     */
77    public const OPT_DATA_DIRECTORY = 'DATA DIRECTORY';
78    /**
79     * @see https://mariadb.com/kb/en/create-table/#delay_key_write
80     *
81     * @var string
82     */
83    public const OPT_DELAY_KEY_WRITE = 'DELAY_KEY_WRITE';
84    /**
85     * @see https://mariadb.com/kb/en/create-table/#encrypted
86     *
87     * @var string
88     */
89    public const OPT_ENCRYPTED = 'ENCRYPTED';
90    /**
91     * @see https://mariadb.com/kb/en/create-table/#encryption_key_id
92     *
93     * @var string
94     */
95    public const OPT_ENCRYPTION_KEY_ID = 'ENCRYPTION_KEY_ID';
96    /**
97     * @see https://mariadb.com/kb/en/create-table/#ietf_quotes
98     *
99     * @var string
100     */
101    public const OPT_IETF_QUOTES = 'IETF_QUOTES';
102    /**
103     * @see https://mariadb.com/kb/en/create-table/#data-directoryindex-directory
104     *
105     * @var string
106     */
107    public const OPT_INDEX_DIRECTORY = 'INDEX DIRECTORY';
108    /**
109     * @see https://mariadb.com/kb/en/create-table/#insert_method
110     *
111     * @var string
112     */
113    public const OPT_INSERT_METHOD = 'INSERT_METHOD';
114    /**
115     * @see https://mariadb.com/kb/en/create-table/#key_block_size
116     *
117     * @var string
118     */
119    public const OPT_KEY_BLOCK_SIZE = 'KEY_BLOCK_SIZE';
120    /**
121     * @see https://mariadb.com/kb/en/create-table/#min_rowsmax_rows
122     *
123     * @var string
124     */
125    public const OPT_MAX_ROWS = 'MAX_ROWS';
126    /**
127     * @see https://mariadb.com/kb/en/create-table/#min_rowsmax_rows
128     *
129     * @var string
130     */
131    public const OPT_MIN_ROWS = 'MIN_ROWS';
132    /**
133     * @see https://mariadb.com/kb/en/create-table/#pack_keys
134     *
135     * @var string
136     */
137    public const OPT_PACK_KEYS = 'PACK_KEYS';
138    /**
139     * @see https://mariadb.com/kb/en/create-table/#page_checksum
140     *
141     * @var string
142     */
143    public const OPT_PAGE_CHECKSUM = 'PAGE_CHECKSUM';
144    /**
145     * @see https://mariadb.com/kb/en/create-table/#page_compressed
146     *
147     * @var string
148     */
149    public const OPT_PAGE_COMPRESSED = 'PAGE_COMPRESSED';
150    /**
151     * @see https://mariadb.com/kb/en/create-table/#page_compression_level
152     *
153     * @var string
154     */
155    public const OPT_PAGE_COMPRESSION_LEVEL = 'PAGE_COMPRESSION_LEVEL';
156    /**
157     * @see https://mariadb.com/kb/en/create-table/#password
158     *
159     * @var string
160     */
161    public const OPT_PASSWORD = 'PASSWORD';
162    /**
163     * @see https://mariadb.com/kb/en/create-table/#row_format
164     *
165     * @var string
166     */
167    public const OPT_ROW_FORMAT = 'ROW_FORMAT';
168    /**
169     * @see https://mariadb.com/kb/en/create-table/#sequence
170     *
171     * @var string
172     */
173    public const OPT_SEQUENCE = 'SEQUENCE';
174    /**
175     * @see https://mariadb.com/kb/en/create-table/#stats_auto_recalc
176     *
177     * @var string
178     */
179    public const OPT_STATS_AUTO_RECALC = 'STATS_AUTO_RECALC';
180    /**
181     * @see https://mariadb.com/kb/en/create-table/#stats_persistent
182     *
183     * @var string
184     */
185    public const OPT_STATS_PERSISTENT = 'STATS_PERSISTENT';
186    /**
187     * @see https://mariadb.com/kb/en/create-table/#stats_sample_pages
188     *
189     * @var string
190     */
191    public const OPT_STATS_SAMPLE_PAGES = 'STATS_SAMPLE_PAGES';
192    /**
193     * @see https://mariadb.com/kb/en/create-tablespace/
194     *
195     * @var string
196     */
197    public const OPT_TABLESPACE = 'TABLESPACE';
198    /**
199     * @see https://mariadb.com/kb/en/create-table/#transactional
200     *
201     * @var string
202     */
203    public const OPT_TRANSACTIONAL = 'TRANSACTIONAL';
204    /**
205     * @see https://mariadb.com/kb/en/create-table/#union
206     *
207     * @var string
208     */
209    public const OPT_UNION = 'UNION';
210    /**
211     * @see https://mariadb.com/kb/en/create-table/#with-system-versioning
212     *
213     * @var string
214     */
215    public const OPT_WITH_SYSTEM_VERSIONING = 'WITH SYSTEM VERSIONING';
216
217    /**
218     * Adds a table option.
219     *
220     * @param string $name
221     * @param int|string|null $value
222     *
223     * @return static
224     */
225    public function option(string $name, int | string $value = null) : static
226    {
227        $this->sql['options'][$name] = $value;
228        return $this;
229    }
230
231    /**
232     * Adds table options.
233     *
234     * @param array<string,int|string> $options
235     *
236     * @return static
237     */
238    public function options(array $options) : static
239    {
240        foreach ($options as $name => $value) {
241            $this->option($name, $value);
242        }
243        return $this;
244    }
245
246    protected function renderOptions() : ?string
247    {
248        if ( ! isset($this->sql['options'])) {
249            return null;
250        }
251        $options = [];
252        foreach ($this->sql['options'] as $name => $value) {
253            $nameUpper = \strtoupper($name);
254            $value = (string) $value;
255            $value = match ($nameUpper) {
256                static::OPT_ENGINE => $this->makeEngine($value),
257                static::OPT_AUTO_INCREMENT => $this->makeAutoIncrement($value),
258                static::OPT_AVG_ROW_LENGTH => $this->makeAvgRowLength($value),
259                static::OPT_CHARSET => $this->makeCharset($value),
260                static::OPT_CHECKSUM => $this->makeChecksum($value),
261                static::OPT_COLLATE => $this->makeCollate($value),
262                static::OPT_COMMENT => $this->makeComment($value),
263                static::OPT_CONNECTION => $this->makeConnection($value),
264                static::OPT_DATA_DIRECTORY => $this->makeDataDirectory($value),
265                static::OPT_DELAY_KEY_WRITE => $this->makeDelayKeyWrite($value),
266                static::OPT_ENCRYPTED => $this->makeEncrypted($value),
267                static::OPT_ENCRYPTION_KEY_ID => $this->makeEncryptionKeyId($value),
268                static::OPT_IETF_QUOTES => $this->makeIetfQuotes($value),
269                static::OPT_INDEX_DIRECTORY => $this->makeIndexDirectory($value),
270                static::OPT_INSERT_METHOD => $this->makeInsertMethod($value),
271                static::OPT_KEY_BLOCK_SIZE => $this->makeKeyBlockSize($value),
272                static::OPT_MAX_ROWS => $this->makeMaxRows($value),
273                static::OPT_MIN_ROWS => $this->makeMinRows($value),
274                static::OPT_PACK_KEYS => $this->makePackKeys($value),
275                static::OPT_PAGE_CHECKSUM => $this->makePageChecksum($value),
276                static::OPT_PAGE_COMPRESSED => $this->makePageCompressed($value),
277                static::OPT_PAGE_COMPRESSION_LEVEL => $this->makePageCompressionLevel($value),
278                static::OPT_PASSWORD => $this->makePassword($value),
279                static::OPT_ROW_FORMAT => $this->makeRowFormat($value),
280                static::OPT_SEQUENCE => $this->makeSequence($value),
281                static::OPT_STATS_AUTO_RECALC => $this->makeStatsAutoRecalc($value),
282                static::OPT_STATS_PERSISTENT => $this->makeStatsPersistent($value),
283                static::OPT_STATS_SAMPLE_PAGES => $this->makeStatsSamplePages($value),
284                static::OPT_TABLESPACE => $this->makeTablespace($value),
285                static::OPT_TRANSACTIONAL => $this->makeTransactional($value),
286                static::OPT_UNION => $this->makeUnion($value),
287                static::OPT_WITH_SYSTEM_VERSIONING => '',
288                default => throw new InvalidArgumentException('Invalid option: ' . $name)
289            };
290            $option = $nameUpper;
291            if ($value !== '') {
292                $option .= ' = ' . $value;
293            }
294            $options[] = $option;
295        }
296        return ' ' . \implode(', ', $options);
297    }
298
299    private function makeEngine(string $value) : string
300    {
301        return $this->getValue(static::OPT_ENGINE, $value, [
302            'aria' => 'Aria',
303            'csv' => 'CSV',
304            'innodb' => 'InnoDB',
305            'memory' => 'MEMORY',
306            'mrg_myisam' => 'MRG_MyISAM',
307            'myisam' => 'MyISAM',
308            'sequence' => 'SEQUENCE',
309        ], \strtolower($value), true);
310    }
311
312    private function makeAutoIncrement(string $value) : string
313    {
314        return \is_numeric($value)
315            ? $value
316            : throw $this->invalidValue(static::OPT_AUTO_INCREMENT, $value);
317    }
318
319    private function makeAvgRowLength(string $value) : string
320    {
321        if (\is_numeric($value) && $value >= 0) {
322            return $value;
323        }
324        throw $this->invalidValue(static::OPT_AVG_ROW_LENGTH, $value);
325    }
326
327    private function makeCharset(string $value) : string
328    {
329        return $this->getValue(static::OPT_CHARSET, $value, [
330            'armscii8',
331            'ascii',
332            'big5',
333            'binary',
334            'cp1250',
335            'cp1251',
336            'cp1256',
337            'cp1257',
338            'cp850',
339            'cp852',
340            'cp866',
341            'cp932',
342            'dec8',
343            'eucjpms',
344            'euckr',
345            'gb2312',
346            'gbk',
347            'geostd8',
348            'greek',
349            'hebrew',
350            'hp8',
351            'keybcs2',
352            'koi8r',
353            'koi8u',
354            'latin1',
355            'latin2',
356            'latin5',
357            'latin7',
358            'macce',
359            'macroman',
360            'sjis',
361            'swe7',
362            'tis620',
363            'ucs2',
364            'ujis',
365            'utf16',
366            'utf16le',
367            'utf32',
368            'utf8',
369            'utf8mb3',
370            'utf8mb4',
371        ], \strtolower($value));
372    }
373
374    private function makeChecksum(string $value) : string
375    {
376        return $this->getValue(static::OPT_CHECKSUM, $value, [
377            '0',
378            '1',
379        ]);
380    }
381
382    private function makeCollate(string $value) : string
383    {
384        return $this->quote($value);
385    }
386
387    private function makeComment(string $value) : string
388    {
389        return $this->quote($value);
390    }
391
392    private function makeConnection(string $value) : string
393    {
394        return $this->quote($value);
395    }
396
397    private function makeDataDirectory(string $value) : string
398    {
399        return $this->quote($value);
400    }
401
402    private function makeDelayKeyWrite(string $value) : string
403    {
404        return $this->getValue(static::OPT_DELAY_KEY_WRITE, $value, [
405            '0',
406            '1',
407        ]);
408    }
409
410    private function makeIetfQuotes(string $value) : string
411    {
412        return $this->getValue(static::OPT_IETF_QUOTES, $value, [
413            'NO',
414            'YES',
415        ], \strtoupper($value));
416    }
417
418    private function makeIndexDirectory(string $value) : string
419    {
420        return $this->quote($value);
421    }
422
423    private function makeInsertMethod(string $value) : string
424    {
425        return $this->getValue(static::OPT_INSERT_METHOD, $value, [
426            'FIRST',
427            'LAST',
428            'NO',
429        ], \strtoupper($value));
430    }
431
432    private function makeKeyBlockSize(string $value) : string
433    {
434        if (\is_numeric($value) && $value >= 0) {
435            return $value;
436        }
437        throw $this->invalidValue(static::OPT_KEY_BLOCK_SIZE, $value);
438    }
439
440    private function makeEncrypted(string $value) : string
441    {
442        return $this->getValue(static::OPT_ENCRYPTED, $value, [
443            'NO',
444            'YES',
445        ], \strtoupper($value));
446    }
447
448    private function makeEncryptionKeyId(string $value) : string
449    {
450        if (\is_numeric($value) && $value >= 1) {
451            return $value;
452        }
453        throw $this->invalidValue(static::OPT_ENCRYPTION_KEY_ID, $value);
454    }
455
456    private function makeMaxRows(string $value) : string
457    {
458        if (\is_numeric($value) && $value >= 0) {
459            return $value;
460        }
461        throw $this->invalidValue(static::OPT_MAX_ROWS, $value);
462    }
463
464    private function makeMinRows(string $value) : string
465    {
466        if (\is_numeric($value) && $value >= 0) {
467            return $value;
468        }
469        throw $this->invalidValue(static::OPT_MIN_ROWS, $value);
470    }
471
472    private function makePackKeys(string $value) : string
473    {
474        return $this->getValue(static::OPT_PACK_KEYS, $value, [
475            '0',
476            '1',
477        ]);
478    }
479
480    private function makePageChecksum(string $value) : string
481    {
482        return $this->getValue(static::OPT_PAGE_CHECKSUM, $value, [
483            '0',
484            '1',
485        ]);
486    }
487
488    private function makePageCompressed(string $value) : string
489    {
490        return $this->getValue(
491            static::OPT_PAGE_COMPRESSED,
492            $value,
493            \array_map('strval', \range(0, 9))
494        );
495    }
496
497    private function makePageCompressionLevel(string $value) : string
498    {
499        return $this->getValue(static::OPT_PAGE_COMPRESSION_LEVEL, $value, [
500            '0',
501            '1',
502        ]);
503    }
504
505    private function makePassword(string $value) : string
506    {
507        return $this->quote($value);
508    }
509
510    private function makeStatsAutoRecalc(string $value) : string
511    {
512        return $this->getValue(static::OPT_STATS_AUTO_RECALC, $value, [
513            '0',
514            '1',
515            'DEFAULT',
516        ], \strtoupper($value));
517    }
518
519    private function makeStatsPersistent(string $value) : string
520    {
521        return $this->getValue(static::OPT_STATS_PERSISTENT, $value, [
522            '0',
523            '1',
524            'DEFAULT',
525        ], \strtoupper($value));
526    }
527
528    private function makeStatsSamplePages(string $value) : string
529    {
530        if (\strtoupper($value) === 'DEFAULT') {
531            return 'DEFAULT';
532        }
533        if (\is_numeric($value) && $value > 0) {
534            return $value;
535        }
536        throw $this->invalidValue(static::OPT_STATS_SAMPLE_PAGES, $value);
537    }
538
539    private function makeTablespace(string $value) : string
540    {
541        return $this->quote($value);
542    }
543
544    private function makeRowFormat(string $value) : string
545    {
546        return $this->getValue(static::OPT_ROW_FORMAT, $value, [
547            'COMPACT',
548            'COMPRESSED',
549            'DEFAULT',
550            'DYNAMIC',
551            'FIXED',
552            'PAGE',
553            'REDUNDANT',
554        ], \strtoupper($value));
555    }
556
557    private function makeSequence(string $value) : string
558    {
559        if (\is_numeric($value) && $value >= 0) {
560            return $value;
561        }
562        throw $this->invalidValue(static::OPT_SEQUENCE, $value);
563    }
564
565    private function makeTransactional(string $value) : string
566    {
567        return $this->getValue(static::OPT_TRANSACTIONAL, $value, [
568            '0',
569            '1',
570        ]);
571    }
572
573    private function makeUnion(string $value) : string
574    {
575        if ($value === '') {
576            throw $this->invalidValue(static::OPT_UNION, $value);
577        }
578        $tables = \array_map('trim', \explode(',', $value));
579        foreach ($tables as &$table) {
580            $table = $this->database->protectIdentifier($table);
581        }
582        unset($table);
583        $tables = \implode(', ', $tables);
584        return '(' . $tables . ')';
585    }
586
587    /**
588     * @param string $optionName
589     * @param string $originalValue
590     * @param array<string> $options
591     * @param string|null $value
592     * @param bool $getByKey
593     *
594     * @return string
595     */
596    private function getValue(
597        string $optionName,
598        string $originalValue,
599        array $options,
600        string $value = null,
601        bool $getByKey = false
602    ) : string {
603        $value ??= $originalValue;
604        if ($getByKey) {
605            if (isset($options[$value])) {
606                return $options[$value];
607            }
608        } elseif (\in_array($value, $options, true)) {
609            return $value;
610        }
611        throw $this->invalidValue($optionName, $originalValue);
612    }
613
614    private function invalidValue(string $option, string $value) : InvalidArgumentException
615    {
616        return new InvalidArgumentException("Invalid {$option} option value: {$value}");
617    }
618
619    private function quote(string $value) : string
620    {
621        return (string) $this->database->quote($value);
622    }
623}