Passed
Pull Request — master (#389)
by
unknown
02:48
created

ExplainStatement   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 77
dl 0
loc 184
ccs 67
cts 67
cp 1
rs 10
c 0
b 0
f 0
wmc 28

2 Methods

Rating   Name   Duplication   Size   Complexity  
D parse() 0 109 23
A build() 0 18 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Statements;
6
7
use PhpMyAdmin\SqlParser\Components\OptionsArray;
8
use PhpMyAdmin\SqlParser\Exceptions\ParserException;
0 ignored issues
show
introduced by
Type PhpMyAdmin\SqlParser\Exceptions\ParserException is not used in this file.
Loading history...
9
use PhpMyAdmin\SqlParser\Parser;
10
use PhpMyAdmin\SqlParser\Statement;
11
use PhpMyAdmin\SqlParser\Token;
12
use PhpMyAdmin\SqlParser\TokensList;
13
14
use function array_slice;
15
use function count;
16
17
/**
18
 * `EXPLAIN` statement.
19
 */
20
class ExplainStatement extends Statement
21
{
22
    /**
23
     * Options for `EXPLAIN` statements.
24
     *
25
     * @var array<string, int|array<int, int|string>>
26
     * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
27
     */
28
    public static $OPTIONS = [
29
30
        'EXTENDED' => 1,
31
        'PARTITIONS' => 1,
32
        'FORMAT' => [
33
            1,
34
            'var',
35
        ],
36
    ];
37
38
    /**
39
     * The parser of the statement to be explained
40
     *
41
     * @var Parser|null
42
     */
43
    public $bodyParser = null;
44
45
    /**
46
     * The statement alias, could be any of the following:
47
     * - EXPLAIN/DESC/DESCRIBE
48
     * - EXPLAIN/DESC/DESCRIBE ANALYZE
49
     * - ANALYZE
50
     *
51
     * @var string
52
     */
53
    public $statemenetAlias;
54
55
    /**
56
     * The connection identifier, if used.
57
     *
58
     * @var number|null
59
     */
60
    public $connectionId = null;
61
62
    /**
63
     * The explained table's name, if used.
64
     *
65
     * @var string|null
66
     */
67
    public $explainedTable = null;
68
69
    /**
70
     * @param Parser     $parser the instance that requests parsing
71
     * @param TokensList $list   the list of tokens to be parsed
72
     */
73 44
    public function parse(Parser $parser, TokensList $list)
74
    {
75
        /**
76
         * The state of the parser.
77
         *
78
         * Below are the states of the parser.
79
         *
80
         *      0 -------------------[ EXPLAIN/EXPLAIN ANALYZE/ANALYZE ]-----------------------> 1
81
         *
82
         *      1 ------------------------------[ OPTIONS ]------------------------------------> 2
83
         *
84
         *      2 --------------[ tablename / STATEMENT / FOR CONNECTION ]---------------------> 2
85
         *
86
         * @var int
87
         */
88 44
        $state = 0;
89
90
        /**
91
         * To Differentiate between ANALYZE / EXPLAIN / EXPLAIN ANALYZE
92
         * 0 -> ANALYZE ( used by mariaDB https://mariadb.com/kb/en/analyze-statement)
93
         * 1 -> EXPLAIN / DESC / DESCRIBE
94
         * 2 -> EXPLAIN / DESC / DESCRIBE [ANALYZE]
95
         */
96 44
        $miniState = 0;
97
98 44
        for (; $list->idx < $list->count; ++$list->idx) {
99
            /**
100
             * Token parsed at this moment.
101
             */
102 44
            $token = $list->tokens[$list->idx];
103
104
            // Skipping whitespaces and comments.
105 44
            if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
106 36
                continue;
107
            }
108
109 44
            if ($state === 0) {
110 44
                if ($token->keyword === 'ANALYZE' && $miniState === 0) {
111 12
                    $state = 1;
112 12
                    $this->statemenetAlias = 'ANALYZE';
113
                } elseif (
114 36
                    $token->keyword === 'EXPLAIN'
115 12
                    || $token->keyword === 'DESC'
116 36
                    || $token->keyword === 'DESCRIBE'
117
                ) {
118 36
                    $miniState === 1;
119 36
                    $this->statemenetAlias = $token->keyword;
120
121 36
                    $lastIdx = $list->idx;
122 36
                    $nextKeyword = $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ANALYZE');
123 36
                    if ($nextKeyword && $nextKeyword->keyword !== null) {
124 12
                        $miniState = 2;
125 12
                        $this->statemenetAlias .= ' ANALYZE';
126
                    } else {
127 28
                        $list->idx = $lastIdx;
128
                    }
129
130 44
                    $state = 1;
131
                }
132 44
            } elseif ($state === 1) {
133
                // Parsing options.
134 44
                $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
135 44
                $state = 2;
136 44
            } elseif ($state === 2) {
137 44
                $currIdx = $list->idx;
138 44
                $currToken = $list->getNext();
0 ignored issues
show
Unused Code introduced by
The assignment to $currToken is dead and can be removed.
Loading history...
139 44
                $nextToken = $list->getNext();
140 44
                $list->idx = $currIdx;
141
142 44
                if ($token->keyword === 'FOR' && $nextToken->keyword === 'CONNECTION') {
143 4
                    $forToken = $list->getNext(); // FOR
0 ignored issues
show
Unused Code introduced by
The assignment to $forToken is dead and can be removed.
Loading history...
144 4
                    $connectionToken = $list->getNext(); // CONNECTION
0 ignored issues
show
Unused Code introduced by
The assignment to $connectionToken is dead and can be removed.
Loading history...
145 4
                    $nextToken = $list->getNext(); // Identifier
146 4
                    $this->connectionId = $nextToken->value;
147 4
                    break;
148
                }
149
150
                // To support EXPLAIN tablename
151 44
                if ($token->type === Token::TYPE_NONE) {
152 16
                    $this->explainedTable = $token->value;
153 16
                    break;
154
                }
155
156
                if (
157 32
                    $token->keyword !== 'SELECT'
158 32
                    && $token->keyword !== 'INSERT'
159 32
                    && $token->keyword !== 'UPDATE'
160 32
                    && $token->keyword !== 'DELETE'
161
                ) {
162 12
                    $parser->error('Unexpected token.', $token);
163 12
                    break;
164
                }
165
166
                // Index of the last parsed token by default would be the last token in the $list, because we're
167
                // assuming that all remaining tokens at state 2, are related to the to-be-explained statement.
168 20
                $idxOfLastParsedToken = $list->count - 1;
169 20
                $subList = new TokensList(array_slice($list->tokens, $list->idx));
170
171 20
                $this->bodyParser = new Parser($subList);
172 20
                if (count($this->bodyParser->errors)) {
173 4
                    foreach ($this->bodyParser->errors as $error) {
174 4
                        $parser->errors[] = $error;
175
                    }
176
177 4
                    break;
178
                }
179
180 16
                $list->idx = $idxOfLastParsedToken;
181 16
                break;
182
            }
183
        }
184
    }
185
186 4
    public function build(): string
187
    {
188 4
        $str = $this->statemenetAlias . ' ';
189
190 4
        $str .= OptionsArray::build($this->options);
191
192 4
        if ($this->bodyParser) {
193 4
            foreach ($this->bodyParser->statements as $statement) {
194 4
                $str .= $statement->build();
195
            }
196 4
        } elseif ($this->connectionId) {
197 4
            $str .= 'FOR CONNECTION ';
198 4
            $str .= $this->connectionId;
199 4
        } elseif ($this->explainedTable) {
200 4
            $str .= $this->explainedTable;
201
        }
202
203 4
        return $str;
204
    }
205
}
206