Passed
Pull Request — master (#483)
by
unknown
03:01
created

IndexHint::buildAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Components;
6
7
use PhpMyAdmin\SqlParser\Component;
8
use PhpMyAdmin\SqlParser\Parser;
9
use PhpMyAdmin\SqlParser\Token;
10
use PhpMyAdmin\SqlParser\TokensList;
11
12
use function implode;
13
14
/**
15
 * Parses an Index hint.
16
 */
17
final class IndexHint implements Component
18
{
19
    /**
20
     * The type of hint (USE/FORCE/IGNORE)
21
     */
22
    public string|null $type;
23
24
    /**
25
     * What the hint is for (INDEX/KEY)
26
     */
27
    public string|null $indexOrKey;
28
29
    /**
30
     * The clause for which this hint is (JOIN/ORDER BY/GROUP BY)
31
     */
32
    public string|null $for;
33
34
    /**
35
     * List of indexes in this hint
36
     *
37
     * @var Expression[]
38
     */
39
    public array $indexes = [];
40
41
    /**
42
     * @param string       $type       the type of hint (USE/FORCE/IGNORE)
43
     * @param string       $indexOrKey What the hint is for (INDEX/KEY)
44
     * @param string       $for        the clause for which this hint is (JOIN/ORDER BY/GROUP BY)
45
     * @param Expression[] $indexes    List of indexes in this hint
46
     */
47 14
    public function __construct(
48
        string|null $type = null,
49
        string|null $indexOrKey = null,
50
        string|null $for = null,
51
        array $indexes = []
52
    ) {
53 14
        $this->type = $type;
54 14
        $this->indexOrKey = $indexOrKey;
55 14
        $this->for = $for;
56 14
        $this->indexes = $indexes;
57
    }
58
59
    /**
60
     * @param Parser               $parser  the parser that serves as context
61
     * @param TokensList           $list    the list of tokens that are being parsed
62
     * @param array<string, mixed> $options parameters for parsing
63
     *
64
     * @return IndexHint[]
65
     */
66 14
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
67
    {
68 14
        $ret = [];
69 14
        $expr = new static();
70 14
        $expr->type = $options['type'] ?? null;
71
        /**
72
         * The state of the parser.
73
         *
74
         * Below are the states of the parser.
75
         *      0 ----------------- [ USE/IGNORE/FORCE ]-----------------> 1
76
         *      1 -------------------- [ INDEX/KEY ] --------------------> 2
77
         *      2 ----------------------- [ FOR ] -----------------------> 3
78
         *      2 -------------------- [ expr_list ] --------------------> 0
79
         *      3 -------------- [ JOIN/GROUP BY/ORDER BY ] -------------> 4
80
         *      4 -------------------- [ expr_list ] --------------------> 0
81
         *
82
         * @var int
83
         */
84 14
        $state = 0;
85
86
        // By design, the parser will parse first token after the keyword. So, the keyword
87
        // must be analyzed too, in order to determine the type of this index hint.
88 14
        if ($list->idx > 0) {
89 14
            --$list->idx;
90
        }
91
92 14
        for (; $list->idx < $list->count; ++$list->idx) {
93
            /**
94
             * Token parsed at this moment.
95
             */
96 14
            $token = $list->tokens[$list->idx];
97
98
            // End of statement.
99 14
            if ($token->type === Token::TYPE_DELIMITER) {
100 8
                break;
101
            }
102
103
            // Skipping whitespaces and comments.
104 14
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
105 14
                continue;
106
            }
107
108
            switch ($state) {
109 14
                case 0:
110 14
                    if ($token->type === Token::TYPE_KEYWORD) {
111 14
                        if ($token->keyword !== 'USE' && $token->keyword !== 'IGNORE' && $token->keyword !== 'FORCE') {
112 6
                            break 2;
113
                        }
114
115 14
                        $expr->type = $token->keyword;
116 14
                        $state = 1;
117
                    }
118
119 14
                    break;
120 14
                case 1:
121 14
                    if ($token->type === Token::TYPE_KEYWORD) {
122 12
                        if ($token->keyword === 'INDEX' || $token->keyword === 'KEY') {
123 10
                            $expr->indexOrKey = $token->keyword;
124
                        } else {
125 2
                            $parser->error('Unexpected keyword.', $token);
126
                        }
127
128 12
                        $state = 2;
129
                    } else {
130
                        // we expect the token to be a keyword
131 2
                        $parser->error('Unexpected token.', $token);
132
                    }
133
134 14
                    break;
135 12
                case 2:
136 12
                    if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'FOR') {
137 10
                        $state = 3;
138
                    } else {
139 8
                        $expr->indexes = ExpressionArray::parse($parser, $list);
140 8
                        $state = 0;
141 8
                        $ret[] = $expr;
142 8
                        $expr = new static();
143
                    }
144
145 12
                    break;
146 10
                case 3:
147 10
                    if ($token->type === Token::TYPE_KEYWORD) {
148
                        if (
149 8
                            $token->keyword === 'JOIN'
150 8
                            || $token->keyword === 'GROUP BY'
151 8
                            || $token->keyword === 'ORDER BY'
152
                        ) {
153 6
                            $expr->for = $token->keyword;
154
                        } else {
155 2
                            $parser->error('Unexpected keyword.', $token);
156
                        }
157
158 8
                        $state = 4;
159
                    } else {
160
                        // we expect the token to be a keyword
161 2
                        $parser->error('Unexpected token.', $token);
162
                    }
163
164 10
                    break;
165 8
                case 4:
166 8
                    $expr->indexes = ExpressionArray::parse($parser, $list);
167 8
                    $state = 0;
168 8
                    $ret[] = $expr;
169 8
                    $expr = new static();
170 8
                    break;
171
            }
172
        }
173
174 14
        --$list->idx;
175
176 14
        return $ret;
177
    }
178
179
    /**
180
     * @param IndexHint $component the component to be built
181
     */
182 2
    public static function build($component): string
183
    {
184 2
        $ret = $component->type . ' ' . $component->indexOrKey . ' ';
185 2
        if ($component->for !== null) {
186 2
            $ret .= 'FOR ' . $component->for . ' ';
187
        }
188
189 2
        return $ret . Expression::buildAll($component->indexes);
190
    }
191
192
    /**
193
     * @param IndexHint[] $component the component to be built
194
     */
195 2
    public static function buildAll(array $component): string
196
    {
197 2
        return implode(' ', $component);
198
    }
199
200 2
    public function __toString(): string
201
    {
202 2
        return static::build($this);
203
    }
204
}
205