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

TableOptions::parseString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Graze\Morphism\Parse;
3
4
use RuntimeException;
5
6
/**
7
 * Represents a set of table options - MIN_ROWS, PACK_KEYS, COMMENT, etc.
8
 */
9
class TableOptions
10
{
11
    /** @var string|null */
12
    public $engine = null;
13
14
    /** @var CollationInfo */
15
    public $collation = null;
16
17
    /**
18
     * @var array
19
     * maps option names to values (string|int|null)
20
     */
21
    public $options = [];
22
23
    /** @var string */
24
    private $defaultEngine = null;
25
    /** @var CollationInfo|null */
26
    private $defaultCollation = null;
27
    /** @var array */
28
    private $defaultOptions = [
29
        'AUTO_INCREMENT'  => null,
30
        'MIN_ROWS'        => 0,
31
        'MAX_ROWS'        => 0,
32
        'AVG_ROW_LENGTH'  => 0,
33
        'PACK_KEYS'       => 'DEFAULT',
34
        'CHECKSUM'        => '0',
35
        'DELAY_KEY_WRITE' => '0',
36
        'ROW_FORMAT'      => 'DEFAULT',
37
        'KEY_BLOCK_SIZE'  => 0,
38
        'COMMENT'         => '',
39
        'CONNECTION'      => '',
40
    ];
41
42
    /**
43
     * Constructor
44
     * @param CollationInfo $databaseCollation
45
     */
46 1
    public function __construct(CollationInfo $databaseCollation)
47
    {
48 1
        $this->collation = new CollationInfo;
49 1
        $this->defaultCollation = clone $databaseCollation;
50 1
    }
51
52
    /**
53
     * Set the default storage engine for the table to use in case the
54
     * ENGINE option is not supplied.
55
     *
56
     * @param string $engine e.g. 'InnoDB"
57
     */
58
    public function setDefaultEngine($engine)
59
    {
60
        $this->defaultEngine = self::normaliseEngine($engine);
61
    }
62
63
    /**
64
     * Parses table options from $stream.
65
     * @param TokenStream $stream
66
     */
67
    public function parse(TokenStream $stream)
68
    {
69
        $this->engine = $this->defaultEngine;
70
        $this->options = $this->defaultOptions;
71
72
        while (true) {
73
            $mark = $stream->getMark();
74
            $token = $stream->nextToken();
75
            if ($token->type !== Token::IDENTIFIER) {
76
                $stream->rewind($mark);
77
                break;
78
            }
79
80
            if ($token->eq(Token::IDENTIFIER, 'DEFAULT')) {
81
                $token = $stream->nextToken();
82
                if (!($token->type === Token::IDENTIFIER &&
83
                      in_array(strtoupper($token->text), ['CHARSET', 'CHARACTER', 'COLLATE']))
84
                ) {
85
                    throw new RuntimeException("Expected CHARSET, CHARACTER SET or COLLATE");
86
                }
87
            }
88
89
            $this->parseOption($stream, strtoupper($token->text));
90
        }
91
92
        if (!$this->collation->isSpecified()) {
93
            $this->collation = clone $this->defaultCollation;
94
        }
95
    }
96
97
    /**
98
     * @param TokenStream $stream
99
     * @param string $option
100
     */
101
    private function parseOption(TokenStream $stream, $option)
102
    {
103
        switch ($option) {
104
            case 'ENGINE':
105
            case 'COLLATE':
106
            case 'CHARSET':
107
                $this->parseIdentifier($stream, $option);
108
                break;
109
110
            case 'CHARACTER':
111
                $stream->expect(Token::IDENTIFIER, 'SET');
112
                $this->parseIdentifier($stream, 'CHARSET');
113
                break;
114
115
            case 'AUTO_INCREMENT':
116
            case 'AVG_ROW_LENGTH':
117
            case 'KEY_BLOCK_SIZE':
118
            case 'MAX_ROWS':
119
            case 'MIN_ROWS':
120
                $this->parseNumber($stream, $option);
121
                break;
122
123
            case 'CHECKSUM':
124
            case 'DELAY_KEY_WRITE':
125
                $this->parseEnum($stream, $option, ['0', '1']);
126
                break;
127
128
            case 'PACK_KEYS':
129
                $this->parseEnum($stream, $option, ['DEFAULT', '0', '1']);
130
                break;
131
132
            case 'DATA':
133
            case 'INDEX':
134
                $stream->expect(Token::IDENTIFIER, 'DIRECTORY');
135
                // fall through //
136
            case 'COMMENT':
137
            case 'CONNECTION':
138
            case 'PASSWORD':
139
                $this->parseString($stream, $option);
140
                break;
141
142
            case 'INSERT_METHOD':
143
                $this->parseEnum($stream, $option, ['NO', 'FIRST', 'LAST']);
144
                throw new RuntimeException("$option is not currently supported by this tool");
145
146
            case 'ROW_FORMAT':
147
                $this->parseEnum($stream, $option, ['DEFAULT', 'DYNAMIC', 'FIXED', 'COMPRESSED', 'REDUNDANT', 'COMPACT']);
148
                break;
149
150
            case 'PARTITION':
151
            case 'STATS_AUTO_RECALC':
152
            case 'STATS_PERSISTENT':
153
            case 'STATS_SAMPLE_PAGES':
154
            case 'TABLESPACE':
155
            case 'UNION':
156
                throw new RuntimeException("$option is not currently supported by this tool");
157
158
            default:
159
                throw new RuntimeException("Unknown table option: $option");
160
        }
161
    }
162
163
    /**
164
     * @param string $engine
165
     * @return string
166
     */
167
    private static function normaliseEngine($engine)
168
    {
169
        $engine = strtoupper($engine);
170
        switch ($engine) {
171
            case 'INNODB':
172
                return 'InnoDB';
173
            case 'MYISAM':
174
                return 'MyISAM';
175
            default:
176
                return $engine;
177
        }
178
    }
179
180
    /**
181
     * @param string $option
182
     * @param string $value
183
     */
184
    private function setOption($option, $value)
185
    {
186
        switch ($option) {
187
            case 'ENGINE':
188
                $this->engine = self::normaliseEngine($value);
189
                break;
190
191
            case 'CHARSET':
192
                if (strtoupper($value) === 'DEFAULT') {
193
                    $this->collation = new CollationInfo();
194
                } else {
195
                    $this->collation->setCharset($value);
196
                }
197
                break;
198
199
            case 'COLLATE':
200
                if (strtoupper($value) === 'DEFAULT') {
201
                    $this->collation = new CollationInfo();
202
                } else {
203
                    $this->collation->setCollation($value);
204
                }
205
                break;
206
207
            default:
208
                $this->options[$option] = $value;
209
                break;
210
        }
211
    }
212
213
    /**
214
     * @param TokenStream $stream
215
     * @param string $option
216
     */
217
    private function parseIdentifier(TokenStream $stream, $option)
218
    {
219
        $stream->consume([[Token::SYMBOL, '=']]);
220
        $token = $stream->nextToken();
221
        if ($token->isEof()) {
222
            throw new RuntimeException("Unexpected end-of-file");
223
        }
224
        if (!in_array($token->type, [Token::IDENTIFIER, Token::STRING])) {
225
            throw new RuntimeException("Bad table option value: '$token->text'");
226
        }
227
        $this->setOption($option, strtolower($token->text));
228
    }
229
230
    /**
231
     * @param TokenStream $stream
232
     * @param string $option
233
     */
234
    private function parseNumber(TokenStream $stream, $option)
235
    {
236
        $stream->consume([[Token::SYMBOL, '=']]);
237
        $this->setOption($option, $stream->expectNumber());
238
    }
239
240
    /**
241
     * @param TokenStream $stream
242
     * @param string $option
243
     * @param array $enums
244
     */
245
    private function parseEnum(TokenStream $stream, $option, array $enums)
246
    {
247
        $stream->consume([[Token::SYMBOL, '=']]);
248
        $token = $stream->nextToken();
249
        if (!in_array($token->type, [Token::IDENTIFIER, Token::NUMBER])) {
250
            throw new RuntimeException("Bad table option value");
251
        }
252
        $value = strtoupper($token->text);
253
        if (!in_array($value, $enums)) {
254
            throw new RuntimeException("Invalid option value, expected " . implode(' | ', $enums));
255
        }
256
        $this->setOption($option, $value);
257
    }
258
259
    /**
260
     * @param TokenStream $stream
261
     * @param string $option
262
     */
263
    private function parseString(TokenStream $stream, $option)
264
    {
265
        $stream->consume([[Token::SYMBOL, '=']]);
266
        $this->setOption($option, $stream->expectString());
267
    }
268
269
    /**
270
     * Returns an SQL fragment to set the options as part of a CREATE TABLE statement.
271
     * Note that the AUTO_INCREMENT option is explicitly *not* included in the output.
272
     */
273
    public function toString()
274
    {
275
        $items = [];
276
277
        $items[] = "ENGINE=" . $this->engine;
278
279
        // (omit AUTO_INCREMENT)
280
281
        $collation = $this->collation;
282
        if ($collation->isSpecified()) {
283
            $items[] = "DEFAULT CHARSET=" . $collation->getCharset();
284
            if (!$collation->isDefaultCollation()) {
285
                $items[] = "COLLATE=" . $collation->getCollation();
286
            }
287
        }
288
289
        foreach ([
290
            'MIN_ROWS',
291
            'MAX_ROWS',
292
            'AVG_ROW_LENGTH',
293
            'PACK_KEYS',
294
            'CHECKSUM',
295
            'DELAY_KEY_WRITE',
296
            'ROW_FORMAT',
297
            'KEY_BLOCK_SIZE',
298
            'COMMENT',
299
            'CONNECTION',
300
        ] as $option) {
301
            if ($this->options[$option] !== $this->defaultOptions[$option]) {
302
                $value = $this->options[$option];
303
                if (in_array($option, ['COMMENT', 'CONNECTION'])) {
304
                    $value = Token::escapeString($value);
305
                }
306
                $items[] = "$option=$value";
307
            }
308
        }
309
310
        return implode(' ', $items);
311
    }
312
313
    /**
314
     * Returns an SQL fragment to transform these table options into those
315
     * specified by $that as part of an ALTER TABLE statement.
316
     *
317
     * The empty string is returned if nothing needs to be done.
318
     *
319
     * $flags           |
320
     * :----------------|
321
     * 'alterEngine'    | (bool) include 'ALTER TABLE ... ENGINE=' [default: true]
322
     *
323
     * @param TableOptions $that
324
     * @param array $flags
325
     * @return string
326
     */
327
    public function diff(TableOptions $that, array $flags = [])
328
    {
329
        $flags += [
330
            'alterEngine' => true,
331
        ];
332
333
        $alters = [];
334
        if ($flags['alterEngine']) {
335
            if (strcasecmp($this->engine, $that->engine) !== 0) {
336
                $alters[] = "ENGINE=" . $that->engine;
337
            }
338
        }
339
340
        $thisCollation = $this->collation->isSpecified()
341
            ? $this->collation->getCollation()
342
            : null;
343
        $thatCollation = $that->collation->isSpecified()
344
            ? $that->collation->getCollation()
345
            : null;
346
        if ($thisCollation !== $thatCollation) {
347
            // TODO - what if !$that->collation->isSpecified()
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...
Unused Code Comprehensibility introduced by
42% 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...
348
            if (!is_null($thatCollation)) {
349
                $alters[] = "DEFAULT CHARSET=" . $that->collation->getCharset();
350
                if (!$that->collation->isDefaultCollation()) {
351
                    $alters[] = "COLLATE=" . $thatCollation;
352
                }
353
            }
354
        }
355
356
        foreach ([
357
            'MIN_ROWS',
358
            'MAX_ROWS',
359
            'AVG_ROW_LENGTH',
360
            'PACK_KEYS',
361
            'CHECKSUM',
362
            'DELAY_KEY_WRITE',
363
364
            // The storage engine may pick a different row format when
365
            // ROW_FORMAT=DEFAULT (or no ROW_FORMAT)/ is specified, depending
366
            // on whether any variable length columns are present. Since we
367
            // don't (currently) explicitly specify ROW_FORMAT in any of our
368
            // tables, I'm choosing to ignore it for the time being...
369
        //  'ROW_FORMAT',
370
            'KEY_BLOCK_SIZE',
371
            'COMMENT',
372
            'CONNECTION',
373
        ] as $option) {
374
            $thisValue = $this->options[$option];
375
            $thatValue = $that->options[$option];
376
            if (in_array($option, ['COMMENT', 'CONNECTION'])) {
377
                $thisValue = Token::escapeString($thisValue);
378
                $thatValue = Token::escapeString($thatValue);
379
            }
380
381
            if ($thisValue !== $thatValue) {
382
                $alters[] = "$option=$thatValue";
383
            }
384
        }
385
386
        return implode(' ', $alters);
387
    }
388
}
389