Passed
Push — master ( ee792a...6ee6fa )
by Michal
03:51
created

src/Components/CreateDefinition.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * Parses the create definition of a column or a key.
5
 *
6
 * Used for parsing `CREATE TABLE` statement.
7
 */
8
9
namespace PhpMyAdmin\SqlParser\Components;
10
11
use PhpMyAdmin\SqlParser\Component;
12
use PhpMyAdmin\SqlParser\Context;
13
use PhpMyAdmin\SqlParser\Parser;
14
use PhpMyAdmin\SqlParser\Token;
15
use PhpMyAdmin\SqlParser\TokensList;
16
17
/**
18
 * Parses the create definition of a column or a key.
19
 *
20
 * Used for parsing `CREATE TABLE` statement.
21
 *
22
 * @category   Components
23
 *
24
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
25
 */
26
class CreateDefinition extends Component
27
{
28
    /**
29
     * All field options.
30
     *
31
     * @var array
32
     */
33
    public static $FIELD_OPTIONS = array(
34
        // Tells the `OptionsArray` to not sort the options.
35
        // See the note below.
36
        '_UNSORTED' => true,
37
38
        'NOT NULL' => 1,
39
        'NULL' => 1,
40
        'DEFAULT' => array(2, 'expr', array('breakOnAlias' => true)),
41
        'AUTO_INCREMENT' => 3,
42
        'PRIMARY' => 4,
43
        'PRIMARY KEY' => 4,
44
        'UNIQUE' => 4,
45
        'UNIQUE KEY' => 4,
46
        'COMMENT' => array(5, 'var'),
47
        'COLUMN_FORMAT' => array(6, 'var'),
48
        'ON UPDATE' => array(7, 'expr'),
49
50
        // Generated columns options.
51
        'GENERATED ALWAYS' => 8,
52
        'AS' => array(9, 'expr', array('parenthesesDelimited' => true)),
53
        'VIRTUAL' => 10,
54
        'PERSISTENT' => 11,
55
        'STORED' => 11,
56
        // Common entries.
57
        //
58
        // NOTE: Some of the common options are not in the same order which
59
        // causes troubles when checking if the options are in the right order.
60
        // I should find a way to define multiple sets of options and make the
61
        // parser select the right set.
62
        //
63
        // 'UNIQUE'                        => 4,
64
        // 'UNIQUE KEY'                    => 4,
65
        // 'COMMENT'                       => array(5, 'var'),
66
        // 'NOT NULL'                      => 1,
67
        // 'NULL'                          => 1,
68
        // 'PRIMARY'                       => 4,
69
        // 'PRIMARY KEY'                   => 4,
70
    );
71
72
    /**
73
     * The name of the new column.
74
     *
75
     * @var string
76
     */
77
    public $name;
78
79
    /**
80
     * Whether this field is a constraint or not.
81
     *
82
     * @var bool
83
     */
84
    public $isConstraint;
85
86
    /**
87
     * The data type of thew new column.
88
     *
89
     * @var DataType
90
     */
91
    public $type;
92
93
    /**
94
     * The key.
95
     *
96
     * @var Key
97
     */
98
    public $key;
99
100
    /**
101
     * The table that is referenced.
102
     *
103
     * @var Reference
104
     */
105
    public $references;
106
107
    /**
108
     * The options of this field.
109
     *
110
     * @var OptionsArray
111
     */
112
    public $options;
113
114
    /**
115
     * Constructor.
116
     *
117
     * @param string       $name         the name of the field
0 ignored issues
show
Should the type for parameter $name not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
118
     * @param OptionsArray $options      the options of this field
0 ignored issues
show
Should the type for parameter $options not be OptionsArray|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
119
     * @param DataType|Key $type         the data type of this field or the key
0 ignored issues
show
Should the type for parameter $type not be DataType|Key|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
120
     * @param bool         $isConstraint whether this field is a constraint or not
121
     * @param Reference    $references   references
0 ignored issues
show
Should the type for parameter $references not be Reference|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
122
     */
123 29
    public function __construct(
124
        $name = null,
125
        $options = null,
126
        $type = null,
127
        $isConstraint = false,
128
        $references = null
129
    ) {
130 29
        $this->name = $name;
131 29
        $this->options = $options;
132 29
        if ($type instanceof DataType) {
133 1
            $this->type = $type;
134 29
        } elseif ($type instanceof Key) {
135 1
            $this->key = $type;
136 1
            $this->isConstraint = $isConstraint;
137 1
            $this->references = $references;
138
        }
139 29
    }
140
141
    /**
142
     * @param Parser     $parser  the parser that serves as context
143
     * @param TokensList $list    the list of tokens that are being parsed
144
     * @param array      $options parameters for parsing
145
     *
146
     * @return CreateDefinition[]
147
     */
148 29
    public static function parse(Parser $parser, TokensList $list, array $options = array())
149
    {
150 29
        $ret = array();
151
152 29
        $expr = new self();
153
154
        /**
155
         * The state of the parser.
156
         *
157
         * Below are the states of the parser.
158
         *
159
         *      0 -----------------------[ ( ]------------------------> 1
160
         *
161
         *      1 --------------------[ CONSTRAINT ]------------------> 1
162
         *      1 -----------------------[ key ]----------------------> 2
163
         *      1 -------------[ constraint / column name ]-----------> 2
164
         *
165
         *      2 --------------------[ data type ]-------------------> 3
166
         *
167
         *      3 ---------------------[ options ]--------------------> 4
168
         *
169
         *      4 --------------------[ REFERENCES ]------------------> 4
170
         *
171
         *      5 ------------------------[ , ]-----------------------> 1
172
         *      5 ------------------------[ ) ]-----------------------> 6 (-1)
173
         *
174
         * @var int
175
         */
176 29
        $state = 0;
177
178 29
        for (; $list->idx < $list->count; ++$list->idx) {
179
            /**
180
             * Token parsed at this moment.
181
             *
182
             * @var Token
183
             */
184 29
            $token = $list->tokens[$list->idx];
185
186
            // End of statement.
187 29
            if ($token->type === Token::TYPE_DELIMITER) {
188 2
                break;
189
            }
190
191
            // Skipping whitespaces and comments.
192 28
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
193 25
                continue;
194
            }
195
196 28
            if ($state === 0) {
197 28
                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
198 27
                    $state = 1;
199
                } else {
200 1
                    $parser->error(
201 1
                        'An opening bracket was expected.',
202 1
                        $token
203
                    );
204
205 1
                    break;
206
                }
207 27
            } elseif ($state === 1) {
208 27
                if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'CONSTRAINT') {
209 6
                    $expr->isConstraint = true;
210 27
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_KEY)) {
211 12
                    $expr->key = Key::parse($parser, $list);
212 12
                    $state = 4;
213 27 View Code Duplication
                } elseif ($token->type === Token::TYPE_SYMBOL || $token->type === Token::TYPE_NONE) {
214 24
                    $expr->name = $token->value;
215 24
                    if (!$expr->isConstraint) {
216 24
                        $state = 2;
217
                    }
218 3
                } elseif ($token->type === Token::TYPE_KEYWORD) {
219 2
                    if ($token->flags & Token::FLAG_KEYWORD_RESERVED) {
220
                        // Reserved keywords can't be used
221
                        // as field names without backquotes
222 1
                        $parser->error(
223
                            'A symbol name was expected! '
224
                            . 'A reserved keyword can not be used '
225 1
                            . 'as a column name without backquotes.',
226 1
                            $token
227
                        );
228
229 1
                        return $ret;
230
                    }
231
232
                    // Non-reserved keywords are allowed without backquotes
233 1
                    $expr->name = $token->value;
234 1
                    $state = 2;
235
                } else {
236 1
                    $parser->error(
237 1
                        'A symbol name was expected!',
238 1
                        $token
239
                    );
240
241 1
                    return $ret;
242
                }
243 25
            } elseif ($state === 2) {
244 25
                $expr->type = DataType::parse($parser, $list);
245 25
                $state = 3;
246 25
            } elseif ($state === 3) {
247 25
                $expr->options = OptionsArray::parse($parser, $list, static::$FIELD_OPTIONS);
248 25
                $state = 4;
249 25
            } elseif ($state === 4) {
250 25
                if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'REFERENCES') {
251 5
                    ++$list->idx; // Skipping keyword 'REFERENCES'.
252 5
                    $expr->references = Reference::parse($parser, $list);
253
                } else {
254 25
                    --$list->idx;
255
                }
256 25
                $state = 5;
257 25
            } elseif ($state === 5) {
258 25
                if ((!empty($expr->type)) || (!empty($expr->key))) {
259 25
                    $ret[] = $expr;
260
                }
261 25
                $expr = new self();
262 25
                if ($token->value === ',') {
263 18
                    $state = 1;
264 24
                } elseif ($token->value === ')') {
265 23
                    $state = 6;
266 23
                    ++$list->idx;
267 23
                    break;
268
                } else {
269 1
                    $parser->error(
270 1
                        'A comma or a closing bracket was expected.',
271 1
                        $token
272
                    );
273 1
                    $state = 0;
274 1
                    break;
275
                }
276
            }
277
        }
278
279
        // Last iteration was not saved.
280 27
        if ((!empty($expr->type)) || (!empty($expr->key))) {
281 1
            $ret[] = $expr;
282
        }
283
284 27 View Code Duplication
        if (($state !== 0) && ($state !== 6)) {
285 1
            $parser->error(
286 1
                'A closing bracket was expected.',
287 1
                $list->tokens[$list->idx - 1]
288
            );
289
        }
290
291 27
        --$list->idx;
292
293 27
        return $ret;
294
    }
295
296
    /**
297
     * @param CreateDefinition|CreateDefinition[] $component the component to be built
298
     * @param array                               $options   parameters for building
299
     *
300
     * @return string
301
     */
302 6
    public static function build($component, array $options = array())
303
    {
304 6
        if (is_array($component)) {
305 4
            return "(\n  " . implode(",\n  ", $component) . "\n)";
306
        }
307
308 6
        $tmp = '';
309
310 6
        if ($component->isConstraint) {
311 1
            $tmp .= 'CONSTRAINT ';
312
        }
313
314 6
        if ((isset($component->name)) && ($component->name !== '')) {
315 5
            $tmp .= Context::escape($component->name) . ' ';
316
        }
317
318 6
        if (!empty($component->type)) {
319 4
            $tmp .= DataType::build(
320 4
                $component->type,
321 4
                array('lowercase' => true)
322 4
            ) . ' ';
323
        }
324
325 6
        if (!empty($component->key)) {
326 3
            $tmp .= $component->key . ' ';
327
        }
328
329 6
        if (!empty($component->references)) {
330 1
            $tmp .= 'REFERENCES ' . $component->references . ' ';
331
        }
332
333 6
        $tmp .= $component->options;
334
335 6
        return trim($tmp);
336
    }
337
}
338