Passed
Pull Request — master (#535)
by
unknown
02:55
created

IndexHints   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 61
c 0
b 0
f 0
dl 0
loc 126
rs 10
ccs 56
cts 56
cp 1
wmc 25

2 Methods

Rating   Name   Duplication   Size   Complexity  
D parse() 0 111 24
A buildAll() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Components\Parsers;
6
7
use PhpMyAdmin\SqlParser\Components\IndexHint;
8
use PhpMyAdmin\SqlParser\Parseable;
9
use PhpMyAdmin\SqlParser\Parser;
10
use PhpMyAdmin\SqlParser\TokensList;
11
use PhpMyAdmin\SqlParser\TokenType;
12
13
use function implode;
14
15
/**
16
 * Parses an Index hint.
17
 */
18
final class IndexHints implements Parseable
19
{
20
    /**
21
     * @param Parser               $parser  the parser that serves as context
22
     * @param TokensList           $list    the list of tokens that are being parsed
23
     * @param array<string, mixed> $options parameters for parsing
24
     *
25
     * @return IndexHint[]
26
     */
27 14
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
28
    {
29 14
        $ret = [];
30 14
        $expr = new IndexHint();
31 14
        $expr->type = $options['type'] ?? null;
32
        /**
33
         * The state of the parser.
34
         *
35
         * Below are the states of the parser.
36
         *      0 ----------------- [ USE/IGNORE/FORCE ]-----------------> 1
37
         *      1 -------------------- [ INDEX/KEY ] --------------------> 2
38
         *      2 ----------------------- [ FOR ] -----------------------> 3
39
         *      2 -------------------- [ expr_list ] --------------------> 0
40
         *      3 -------------- [ JOIN/GROUP BY/ORDER BY ] -------------> 4
41
         *      4 -------------------- [ expr_list ] --------------------> 0
42
         *
43
         * @var int
44
         */
45 14
        $state = 0;
46
47
        // By design, the parser will parse first token after the keyword. So, the keyword
48
        // must be analyzed too, in order to determine the type of this index hint.
49 14
        if ($list->idx > 0) {
50 14
            --$list->idx;
51
        }
52
53 14
        for (; $list->idx < $list->count; ++$list->idx) {
54
            /**
55
             * Token parsed at this moment.
56
             */
57 14
            $token = $list->tokens[$list->idx];
58
59
            // End of statement.
60 14
            if ($token->type === TokenType::Delimiter) {
61 8
                break;
62
            }
63
64
            // Skipping whitespaces and comments.
65 14
            if (($token->type === TokenType::Whitespace) || ($token->type === TokenType::Comment)) {
66 14
                continue;
67
            }
68
69
            switch ($state) {
70 14
                case 0:
71 14
                    if ($token->type === TokenType::Keyword) {
72 14
                        if ($token->keyword !== 'USE' && $token->keyword !== 'IGNORE' && $token->keyword !== 'FORCE') {
73 6
                            break 2;
74
                        }
75
76 14
                        $expr->type = $token->keyword;
77 14
                        $state = 1;
78
                    }
79
80 14
                    break;
81 14
                case 1:
82 14
                    if ($token->type === TokenType::Keyword) {
83 12
                        if ($token->keyword === 'INDEX' || $token->keyword === 'KEY') {
84 10
                            $expr->indexOrKey = $token->keyword;
85
                        } else {
86 2
                            $parser->error('Unexpected keyword.', $token);
87
                        }
88
89 12
                        $state = 2;
90
                    } else {
91
                        // we expect the token to be a keyword
92 2
                        $parser->error('Unexpected token.', $token);
93
                    }
94
95 14
                    break;
96 12
                case 2:
97 12
                    if ($token->type === TokenType::Keyword && $token->keyword === 'FOR') {
98 10
                        $state = 3;
99
                    } else {
100 8
                        $expr->indexes = ExpressionArray::parse($parser, $list);
101 8
                        $state = 0;
102 8
                        $ret[] = $expr;
103 8
                        $expr = new IndexHint();
104
                    }
105
106 12
                    break;
107 10
                case 3:
108 10
                    if ($token->type === TokenType::Keyword) {
109
                        if (
110 8
                            $token->keyword === 'JOIN'
111 8
                            || $token->keyword === 'GROUP BY'
112 8
                            || $token->keyword === 'ORDER BY'
113
                        ) {
114 6
                            $expr->for = $token->keyword;
115
                        } else {
116 2
                            $parser->error('Unexpected keyword.', $token);
117
                        }
118
119 8
                        $state = 4;
120
                    } else {
121
                        // we expect the token to be a keyword
122 2
                        $parser->error('Unexpected token.', $token);
123
                    }
124
125 10
                    break;
126 8
                case 4:
127 8
                    $expr->indexes = ExpressionArray::parse($parser, $list);
128 8
                    $state = 0;
129 8
                    $ret[] = $expr;
130 8
                    $expr = new IndexHint();
131 8
                    break;
132
            }
133
        }
134
135 14
        --$list->idx;
136
137 14
        return $ret;
138
    }
139
140
    /** @param IndexHint[] $component the component to be built */
141 2
    public static function buildAll(array $component): string
142
    {
143 2
        return implode(' ', $component);
144
    }
145
}
146