Passed
Push — master ( 6c3f09...c54108 )
by William
03:43
created

IndexHint   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 75
dl 0
loc 189
ccs 68
cts 68
cp 1
rs 10
c 0
b 0
f 0
wmc 29

4 Methods

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