Completed
Push — master ( 20e749...a87ad6 )
by Michal
04:38
created

CreateDefinition::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 9
cts 10
cp 0.9
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 14
nc 3
nop 5
crap 3.009
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\Context;
12
use PhpMyAdmin\SqlParser\Component;
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
Documentation introduced by
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
Documentation introduced by
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
Documentation introduced by
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
Documentation introduced by
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
        } 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
                        $token
203
                    );
204 28
                    break;
205
                }
206 27
            } elseif ($state === 1) {
207 27
                if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'CONSTRAINT') {
208 6
                    $expr->isConstraint = true;
209 27
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_KEY)) {
210 12
                    $expr->key = Key::parse($parser, $list);
211 12
                    $state = 4;
212 27 View Code Duplication
                } elseif ($token->type === Token::TYPE_SYMBOL || $token->type === Token::TYPE_NONE) {
213 24
                    $expr->name = $token->value;
214 24
                    if (!$expr->isConstraint) {
215 24
                        $state = 2;
216
                    }
217 3
                } elseif ($token->type === Token::TYPE_KEYWORD) {
218 2
                    if ($token->flags & Token::FLAG_KEYWORD_RESERVED) {
219
                        // Reserved keywords can't be used
220
                        // as field names without backquotes
221 1
                        $parser->error(
222
                            'A symbol name was expected! '
223
                            . 'A reserved keyword can not be used '
224 1
                            . 'as a column name without backquotes.',
225
                            $token
226
                        );
227
228 1
                        return $ret;
229
                    } else {
230
                        // Non-reserved keywords are allowed without backquotes
231 1
                        $expr->name = $token->value;
232 1
                        $state = 2;
233
                    }
234
                } else {
235 1
                    $parser->error(
236 1
                        'A symbol name was expected!',
237
                        $token
238
                    );
239
240 26
                    return $ret;
241
                }
242 25
            } elseif ($state === 2) {
243 25
                $expr->type = DataType::parse($parser, $list);
244 25
                $state = 3;
245 25
            } elseif ($state === 3) {
246 25
                $expr->options = OptionsArray::parse($parser, $list, static::$FIELD_OPTIONS);
247 25
                $state = 4;
248 25
            } elseif ($state === 4) {
249 25
                if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'REFERENCES') {
250 5
                    ++$list->idx; // Skipping keyword 'REFERENCES'.
251 5
                    $expr->references = Reference::parse($parser, $list);
252
                } else {
253 25
                    --$list->idx;
254
                }
255 25
                $state = 5;
256 25
            } elseif ($state === 5) {
257 25
                if ((!empty($expr->type)) || (!empty($expr->key))) {
258 25
                    $ret[] = $expr;
259
                }
260 25
                $expr = new self();
261 25
                if ($token->value === ',') {
262 18
                    $state = 1;
263 24
                } elseif ($token->value === ')') {
264 23
                    $state = 6;
265 23
                    ++$list->idx;
266 23
                    break;
267
                } else {
268 1
                    $parser->error(
269 1
                        'A comma or a closing bracket was expected.',
270
                        $token
271
                    );
272 1
                    $state = 0;
273 1
                    break;
274
                }
275
            }
276
        }
277
278
        // Last iteration was not saved.
279 27
        if ((!empty($expr->type)) || (!empty($expr->key))) {
280 1
            $ret[] = $expr;
281
        }
282
283 27
        if (($state !== 0) && ($state !== 6)) {
284 1
            $parser->error(
285 1
                'A closing bracket was expected.',
286 1
                $list->tokens[$list->idx - 1]
287
            );
288
        }
289
290 27
        --$list->idx;
291
292 27
        return $ret;
293
    }
294
295
    /**
296
     * @param CreateDefinition|CreateDefinition[] $component the component to be built
297
     * @param array                               $options   parameters for building
298
     *
299
     * @return string
300
     */
301 6
    public static function build($component, array $options = array())
302
    {
303 6
        if (is_array($component)) {
304 4
            return "(\n  " . implode(",\n  ", $component) . "\n)";
305
        } else {
306 6
            $tmp = '';
307
308 6
            if ($component->isConstraint) {
309 1
                $tmp .= 'CONSTRAINT ';
310
            }
311
312 6
            if ((isset($component->name)) && ($component->name !== '')) {
313 5
                $tmp .= Context::escape($component->name) . ' ';
314
            }
315
316 6
            if (!empty($component->type)) {
317 4
                $tmp .= DataType::build(
318 4
                    $component->type,
319 4
                    array('lowercase' => true)
320 4
                ) . ' ';
321
            }
322
323 6
            if (!empty($component->key)) {
324 3
                $tmp .= $component->key . ' ';
325
            }
326
327 6
            if (!empty($component->references)) {
328 1
                $tmp .= 'REFERENCES ' . $component->references . ' ';
329
            }
330
331 6
            $tmp .= $component->options;
332
333 6
            return trim($tmp);
334
        }
335
    }
336
}
337