GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#60)
by Burhan
03:25
created

IndexDefinition::parseIndexColumns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Graze\Morphism\Parse;
3
4
use LogicException;
5
use RuntimeException;
6
7
/**
8
 * Represents the definition of an index.
9
 */
10
class IndexDefinition
11
{
12
    /** @var string|null */
13
    public $constraint = null;
14
15
    /** @var string */
16
    public $type = '';
17
18
    /** @var string|null */
19
    public $name = null;
20
21
    /**
22
     * @var array[] the columns which are indexed.
23
     *
24
     * Each entry is an array of the form:
25
     *
26
     * 'name'   => string
27
     * 'length' => int|null
28
     * 'sort'   => 'ASC'|'DESC'
29
     */
30
    public $columns = [];
31
32
    /** @var mixed[] Associative array of index options
33
     *
34
     * 'USING'          => string 'BTREE' | 'HASH'
35
     * 'KEY_BLOCK_SIZE' => integer
36
     * 'WITH PARSER'    => string;
37
     * 'COMMENT'        => string
38
     */
39
    public $options = [];
40
41
    /**
42
     * @var array Only present for FOREIGN KEYS
43
     *
44
     * 'table'     => string name of foreign table
45
     * 'columns'   => [ 'name' => string, 'length' => int|null, 'sort' => string][] columns referenced in foreign table
46
     * 'ON DELETE' => string 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'NO ACTION'
47
     * 'ON UPDATE' => string 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'NO ACTION'
48
     */
49
    public $reference = [];
50
51
    /**
52
     * Parses an index definition from $stream
53
     *
54
     * The type of key (PRIMARY KEY, UNIQUE KEY, etc) should already have
55
     * been parsed from the stream. If the optional preceding CONSTRAINT clause
56
     * was parsed, you should supply its optional name in $constraint.
57
     *
58
     * @param TokenStream $stream
59
     * @param string $type 'PRIMARY KEY' | 'UNIQUE KEY' | 'KEY' | 'FULLTEXT KEY' | 'FOREIGN KEY'
60
     * @param string|null $constraint name supplied in optional CONSTRAINT clause
61
     */
62
    public function parse(TokenStream $stream, $type, $constraint = null)
63
    {
64
        $this->type = $type;
65
66
        switch ($type) {
67
            case 'PRIMARY KEY':
68
                $this->parseOptionalIndexType($stream);
69
                $this->parseIndexColumns($stream);
70
                $this->parseIndexOptions($stream);
71
                break;
72
73
            case 'UNIQUE KEY':
74
            case 'KEY':
75
            case 'FULLTEXT KEY':
76
                $this->parseOptionalIndexType($stream);
77
                if (!isset($this->options['USING'])) {
78
                    $this->parseOptionalIndexName($stream);
79
                    $this->parseOptionalIndexType($stream);
80
                }
81
                if (!is_null($constraint)) {
82
                    $this->name = $constraint;
83
                }
84
                $this->parseIndexColumns($stream);
85
                $this->parseIndexOptions($stream);
86
                break;
87
88
            case 'FOREIGN KEY':
89
                $this->constraint = $constraint;
90
                $this->parseOptionalIndexName($stream);
91
                $this->parseIndexColumns($stream);
92
                $this->parseReferenceDefinition($stream);
93
                break;
94
95
            default:
96
                throw new LogicException("Internal error - unknown index type '$type'");
97
        }
98
    }
99
100
    /**
101
     * @param TokenStream $stream
102
     */
103
    private function parseOptionalIndexName(TokenStream $stream)
104
    {
105
        $mark = $stream->getMark();
106
        $token = $stream->nextToken();
107
        if ($token->type === Token::IDENTIFIER) {
108
            $this->name = $token->text;
109
        } else {
110
            $stream->rewind($mark);
111
        }
112
    }
113
114
    /**
115
     * @param TokenStream $stream
116
     */
117
    private function parseOptionalIndexType(TokenStream $stream)
118
    {
119
        if ($stream->consume('USING')) {
120
            $this->parseIndexType($stream);
121
        }
122
    }
123
124
    /**
125
     * @param TokenStream $stream
126
     */
127
    private function parseIndexType(TokenStream $stream)
128
    {
129
        if ($stream->consume('BTREE')) {
130
            $using = 'BTREE';
131
        } elseif ($stream->consume('HASH')) {
132
            $using = 'HASH';
133
        } else {
134
            throw new RuntimeException("Expected BTREE or HASH");
135
        }
136
        $this->options['USING'] = $using;
137
    }
138
139
    /**
140
     * @param TokenStream $stream
141
     */
142
    private function parseIndexColumns(TokenStream $stream)
143
    {
144
        $this->columns = $this->expectIndexColumns($stream);
145
    }
146
147
    /**
148
     * @param TokenStream $stream
149
     * @return array
150
     */
151
    private function expectIndexColumns(TokenStream $stream)
152
    {
153
        $columns = [];
154
        $stream->expect(Token::SYMBOL, '(');
155
        while (true) {
156
            $column = [
157
                'name'   => $stream->expectName(),
158
                'length' => null,
159
                'sort'   => 'ASC',
160
            ];
161
            if ($stream->consume([[Token::SYMBOL, '(']])) {
162
                $column['length'] = $stream->expectNumber();
163
                $stream->expect(Token::SYMBOL, ')');
164
            }
165
            if ($stream->consume('ASC')) {
166
                $column['sort'] = 'ASC';
167
            } elseif ($stream->consume('DESC')) {
168
                $column['sort'] = 'DESC';
169
            }
170
            $columns[] = $column;
171
            if (!$stream->consume([[Token::SYMBOL, ',']])) {
172
                break;
173
            }
174
        }
175
176
        $stream->expect(Token::SYMBOL, ')');
177
178
        return $columns;
179
    }
180
181
    /**
182
     * @param TokenStream $stream
183
     */
184
    private function parseIndexOptions(TokenStream $stream)
185
    {
186
        while (true) {
187
            if ($stream->consume('KEY_BLOCK_SIZE')) {
188
                $stream->consume([[Token::SYMBOL, '=']]);
189
                $this->options['KEY_BLOCK_SIZE'] = $stream->expectNumber();
190
            } elseif ($stream->consume('WITH PARSER')) {
191
                $this->options['WITH PARSER'] = $stream->expectName();
192
            } elseif ($stream->consume('COMMENT')) {
193
                $this->options['COMMENT'] = $stream->expectString();
194
            } elseif ($stream->consume('USING')) {
195
                $this->parseIndexType($stream);
196
            } else {
197
                break;
198
            }
199
        }
200
    }
201
202
    /**
203
     * @param TokenStream $stream
204
     */
205
    private function parseReferenceDefinition(TokenStream $stream)
206
    {
207
        $stream->expect(Token::IDENTIFIER, 'REFERENCES');
208
209
        $tableOrSchema = $stream->expectName();
210
        if ($stream->consume([[Token::SYMBOL, '.']])) {
211
            $schema = $tableOrSchema;
212
            $table = $stream->expectName();
213
        } else {
214
            $schema = null;
215
            $table = $tableOrSchema;
216
        }
217
218
        $this->reference['schema'] = $schema;
219
        $this->reference['table'] = $table;
220
        $this->reference['columns'] = $this->expectIndexColumns($stream);
221
        $this->reference['ON DELETE'] = 'RESTRICT';
222
        $this->reference['ON UPDATE'] = 'RESTRICT';
223
224
        while (true) {
225
            if ($stream->consume('MATCH')) {
226
                throw new RuntimeException("MATCH clause is not supported in this tool, or in MySQL itself!");
227
            } elseif ($stream->consume('ON DELETE')) {
228
                $this->parseReferenceOption($stream, 'ON DELETE');
229
            } elseif ($stream->consume('ON UPDATE')) {
230
                $this->parseReferenceOption($stream, 'ON UPDATE');
231
            } else {
232
                break;
233
            }
234
        }
235
    }
236
237
    /**
238
     * @param TokenStream $stream
239
     * @param string $clause
240
     */
241
    private function parseReferenceOption(TokenStream $stream, $clause)
242
    {
243
        $availableOptions = ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'];
244
        foreach ($availableOptions as $option) {
245
            if ($stream->consume($option)) {
246
                $this->reference[$clause] = $option;
247
                return;
248
            }
249
        }
250
        throw new RuntimeException("Expected one of: " . implode(", ", $availableOptions));
251
    }
252
253
    /**
254
     * Returns an array of all sequences of columns covered by this index. Includes optional lengths.
255
     *
256
     * E.g. the index ```KEY idx (x,y(12),z)``` will give ```[['x', 'y(12)', 'z'], ['x', 'y(12)'], ['x']]```.
257
     *
258
     * @return string[][]
259
     */
260
    public function getCovers()
261
    {
262
        // TODO - we should really have separate IndexPart objects so we can
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
263
        // correctly cope with pathological cases like:
264
        //
265
        //      CREATE TABLE evil (
266
        //          `bad(16)` varchar(32) not null,
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
267
        //          KEY (`bad(16)`(8))
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
268
        //      )
269
        //
270
        $covers = [];
271
        $cover = [];
272
        foreach ($this->columns as $column) {
273
            $name = $column['name'];
274
            if ($column['length']) {
275
                $name .= '(' . $column['length'] . ')';
276
            }
277
            $cover[] = $name;
278
            $covers[] = $cover;
279
        }
280
        return $covers;
281
    }
282
283
    /**
284
     * Returns the sequence of indexed columns, including optional lengths.
285
     *
286
     * E.g. the index ```KEY idx (x, y(12))``` will give ```['x', 'y(12)']```
287
     *
288
     * @return string[]
289
     */
290
    public function getColumns()
291
    {
292
        $columns = [];
293
        foreach ($this->columns as $column) {
294
            $name = $column['name'];
295
            if ($column['length']) {
296
                $name .= '(' . $column['length'] . ')';
297
            }
298
            $columns[] = $name;
299
        }
300
        return $columns;
301
    }
302
303
    /**
304
     * Returns an SQL fragment for declaring this index as part of a table definition.
305
     *
306
     * @return string
307
     */
308
    public function toString()
309
    {
310
        $line = '';
311
        if ($this->type === 'FOREIGN KEY') {
312
            $line = "CONSTRAINT " . Token::escapeIdentifier($this->constraint) . " ";
313
        }
314
        $line .= $this->type;
315
        if (!in_array($this->type, ['PRIMARY KEY', 'FOREIGN KEY'])) {
316
            $line .= " " . Token::escapeIdentifier($this->name);
317
        }
318
        $cols = [];
319
        foreach ($this->columns as $column) {
320
            $col = Token::escapeIdentifier($column['name']);
321
            if (!is_null($column['length'])) {
322
                $col .= "(" . $column['length'] . ")";
323
            }
324
            $cols[] = $col;
325
        }
326
        $line .= " (" . implode(',', $cols) . ")";
327
328
        if (isset($this->options['USING'])) {
329
            $line .= " USING " . $this->options['USING'];
330
        }
331
        if (isset($this->options['KEY_BLOCK_SIZE']) && $this->options['KEY_BLOCK_SIZE'] !== 0) {
332
            $line .= " KEY_BLOCK_SIZE=" . $this->options['KEY_BLOCK_SIZE'];
333
        }
334
        if (isset($this->options['COMMENT']) && $this->options['COMMENT'] !== '') {
335
            $line .= " COMMENT " . Token::escapeString($this->options['COMMENT']);
336
        }
337
        if (isset($this->options['WITH PARSER'])) {
338
            $line .= " WITH PARSER " . $this->options['WITH PARSER'];
339
        }
340
341
        if ($this->type === 'FOREIGN KEY') {
342
            $reference = Token::escapeIdentifier($this->reference['table']);
343
            if (!is_null($this->reference['schema'])) {
344
                $reference = Token::escapeIdentifier($this->reference['schema']) . '.' . $reference;
345
            }
346
            $line .= " REFERENCES $reference";
347
            $cols = [];
348
            foreach ($this->reference['columns'] as $column) {
349
                $col = Token::escapeIdentifier($column['name']);
350
                if (!is_null($column['length'])) {
351
                    $col .= "(" . $column['length'] . ")";
352
                }
353
                $cols[] = $col;
354
            }
355
            $line .= " (" . implode(',', $cols) . ")";
356
            foreach (['ON DELETE', 'ON UPDATE'] as $clause) {
357
                $action = $this->reference[$clause];
358
                if ($action !== 'RESTRICT') {
359
                    $line .= " $clause $action";
360
                }
361
            }
362
        }
363
        return $line;
364
    }
365
}
366