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

ExplainStatement::parse()   D

Complexity

Conditions 24
Paths 15

Size

Total Lines 113
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 56
CRAP Score 24.0235

Importance

Changes 0
Metric Value
cc 24
eloc 60
nc 15
nop 2
dl 0
loc 113
ccs 56
cts 58
cp 0.9655
crap 24.0235
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
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 40
    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 40
        $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 40
        $miniState = 0;
97
98 40
        for (; $list->idx < $list->count; ++$list->idx) {
99
            /**
100
             * Token parsed at this moment.
101
             */
102 40
            $token = $list->tokens[$list->idx];
103
104
            // Skipping whitespaces and comments.
105 40
            if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
106 32
                continue;
107
            }
108
109 40
            if ($state === 0) {
110 40
                if ($token->keyword === 'ANALYZE' && $miniState === 0) {
111 12
                    $state = 1;
112 12
                    $this->statemenetAlias = 'ANALYZE';
113
                } elseif (
114 32
                    $token->keyword === 'EXPLAIN'
115 12
                    || $token->keyword === 'DESC'
116 32
                    || $token->keyword === 'DESCRIBE'
117
                ) {
118 32
                    $miniState === 1;
119 32
                    $this->statemenetAlias = $token->keyword;
120
121 32
                    $lastIdx = $list->idx;
122 32
                    $nextKeyword = $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ANALYZE');
123 32
                    if ($nextKeyword && $nextKeyword->keyword !== null) {
124 12
                        $miniState = 2;
125 12
                        $this->statemenetAlias .= ' ANALYZE';
126
                    } else {
127 24
                        $list->idx = $lastIdx;
128
                    }
129
130 40
                    $state = 1;
131
                }
132 40
            } elseif ($state === 1) {
133
                // Parsing options.
134 40
                $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
135 40
                $state = 2;
136 40
            } elseif ($state === 2) {
137 40
                $currIdx = $list->idx;
138 40
                $currToken = $list->getNext();
0 ignored issues
show
Unused Code introduced by
The assignment to $currToken is dead and can be removed.
Loading history...
139 40
                $nextToken = $list->getNext();
140 40
                $list->idx = $currIdx;
141
142 40
                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 40
                if ($token->type === Token::TYPE_NONE) {
152 16
                    $this->explainedTable = $token->value;
153 16
                    break;
154
                }
155
156
                if (
157 28
                    $token->keyword !== 'SELECT'
158 28
                    && $token->keyword !== 'INSERT'
159 28
                    && $token->keyword !== 'UPDATE'
160 28
                    && $token->keyword !== 'DELETE'
161
                ) {
162 8
                    $parser->error('Unexpected token.', $token);
163 8
                    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 20
                if ($subList instanceof ParserException) {
171
                    $parser->errors[] = $subList;
172
                    break;
173
                }
174
175 20
                $this->bodyParser = new Parser($subList);
176 20
                if (count($this->bodyParser->errors)) {
177 4
                    foreach ($this->bodyParser->errors as $error) {
178 4
                        $parser->errors[] = $error;
179
                    }
180
181 4
                    break;
182
                }
183
184 16
                $list->idx = $idxOfLastParsedToken;
185 16
                break;
186
            }
187
        }
188
    }
189
190 4
    public function build(): string
191
    {
192 4
        $str = $this->statemenetAlias . ' ';
193
194 4
        $str .= OptionsArray::build($this->options);
195
196 4
        if ($this->bodyParser) {
197 4
            foreach ($this->bodyParser->statements as $statement) {
198 4
                $str .= $statement->build();
199
            }
200 4
        } elseif ($this->connectionId) {
201 4
            $str .= 'FOR CONNECTION ';
202 4
            $str .= $this->connectionId;
203 4
        } elseif ($this->explainedTable) {
204 4
            $str .= $this->explainedTable;
205
        }
206
207 4
        return $str;
208
    }
209
}
210