Completed
Push — master ( 8de474...6cb1d9 )
by Michal
03:48
created

CreateDefinition::build()   C

Complexity

Conditions 8
Paths 33

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 8

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 0
loc 35
ccs 24
cts 24
cp 1
rs 5.3846
cc 8
eloc 20
nc 33
nop 2
crap 8
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
 * @package    SqlParser
9
 * @subpackage Components
10
 */
11
namespace SqlParser\Components;
12
13
use SqlParser\Context;
14
use SqlParser\Component;
15
use SqlParser\Parser;
16
use SqlParser\Token;
17
use SqlParser\TokensList;
18
19
/**
20
 * Parses the create definition of a column or a key.
21
 *
22
 * Used for parsing `CREATE TABLE` statement.
23
 *
24
 * @category   Components
25
 * @package    SqlParser
26
 * @subpackage Components
27
 * @author     Dan Ungureanu <[email protected]>
28
 * @license    http://opensource.org/licenses/GPL-2.0 GNU Public License
29
 */
30
class CreateDefinition extends Component
31
{
32
33
    /**
34
     * All field options.
35
     *
36
     * @var array
37
     */
38
    public static $FIELD_OPTIONS = array(
39
40
        // Tells the `OptionsArray` to not sort the options.
41
        // See the note below.
42
        '_UNSORTED'                     => true,
43
44
        'NOT NULL'                      => 1,
45
        'NULL'                          => 1,
46
        'DEFAULT'                       => array(2, 'expr', array('breakOnAlias' => true)),
47
        'AUTO_INCREMENT'                => 3,
48
        'PRIMARY'                       => 4,
49
        'PRIMARY KEY'                   => 4,
50
        'UNIQUE'                        => 4,
51
        'UNIQUE KEY'                    => 4,
52
        'COMMENT'                       => array(5, 'var'),
53
        'COLUMN_FORMAT'                 => array(6, 'var'),
54
        'ON UPDATE'                     => array(7, 'var'),
55
56
        // Generated columns options.
57
        'GENERATED ALWAYS'              => 8,
58
        'AS'                            => array(9, 'expr', array('parenthesesDelimited' => true)),
59
        'VIRTUAL'                       => 10,
60
        'PERSISTENT'                    => 11,
61
        'STORED'                        => 11,
62
        // Common entries.
63
        //
64
        // NOTE: Some of the common options are not in the same order which
65
        // causes troubles when checking if the options are in the right order.
66
        // I should find a way to define multiple sets of options and make the
67
        // parser select the right set.
68
        //
69
        // 'UNIQUE'                        => 4,
70
        // 'UNIQUE KEY'                    => 4,
71
        // 'COMMENT'                       => array(5, 'var'),
72
        // 'NOT NULL'                      => 1,
73
        // 'NULL'                          => 1,
74
        // 'PRIMARY'                       => 4,
75
        // 'PRIMARY KEY'                   => 4,
76
    );
77
78
    /**
79
     * The name of the new column.
80
     *
81
     * @var string
82
     */
83
    public $name;
84
85
    /**
86
     * Whether this field is a constraint or not.
87
     *
88
     * @var bool
89
     */
90
    public $isConstraint;
91
92
    /**
93
     * The data type of thew new column.
94
     *
95
     * @var DataType
96
     */
97
    public $type;
98
99
    /**
100
     * The key.
101
     *
102
     * @var Key
103
     */
104
    public $key;
105
106
    /**
107
     * The table that is referenced.
108
     *
109
     * @var Reference
110
     */
111
    public $references;
112
113
    /**
114
     * The options of this field.
115
     *
116
     * @var OptionsArray
117
     */
118
    public $options;
119
120
    /**
121
     * Constructor.
122
     *
123
     * @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...
124
     * @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...
125
     * @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...
126
     * @param bool         $isConstraint Whether this field is a constraint or not.
127
     * @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...
128
     */
129 23
    public function __construct(
130
        $name = null,
131
        $options = null,
132
        $type = null,
133 1
        $isConstraint = false,
134
        $references = null
135
    ) {
136 23
        $this->name = $name;
137 23
        $this->options = $options;
138 23
        if ($type instanceof DataType) {
139 1
            $this->type = $type;
140 23
        } elseif ($type instanceof Key) {
141 1
            $this->key = $type;
142 1
            $this->isConstraint = $isConstraint;
143 1
            $this->references = $references;
144 1
        }
145 23
    }
146
147
    /**
148
     * @param Parser     $parser  The parser that serves as context.
149
     * @param TokensList $list    The list of tokens that are being parsed.
150
     * @param array      $options Parameters for parsing.
151
     *
152
     * @return CreateDefinition[]
153
     */
154 23
    public static function parse(Parser $parser, TokensList $list, array $options = array())
155
    {
156 23
        $ret = array();
157
158 23
        $expr = new CreateDefinition();
159
160
        /**
161
         * The state of the parser.
162
         *
163
         * Below are the states of the parser.
164
         *
165
         *      0 -----------------------[ ( ]------------------------> 1
166
         *
167
         *      1 --------------------[ CONSTRAINT ]------------------> 1
168
         *      1 -----------------------[ key ]----------------------> 2
169
         *      1 -------------[ constraint / column name ]-----------> 2
170
         *
171
         *      2 --------------------[ data type ]-------------------> 3
172
         *
173
         *      3 ---------------------[ options ]--------------------> 4
174
         *
175
         *      4 --------------------[ REFERENCES ]------------------> 4
176
         *
177
         *      5 ------------------------[ , ]-----------------------> 1
178
         *      5 ------------------------[ ) ]-----------------------> 6 (-1)
179
         *
180
         * @var int $state
181
         */
182 23
        $state = 0;
183
184 23
        for (; $list->idx < $list->count; ++$list->idx) {
185
            /**
186
             * Token parsed at this moment.
187
             *
188
             * @var Token $token
189
             */
190 23
            $token = $list->tokens[$list->idx];
191
192
            // End of statement.
193 23
            if ($token->type === Token::TYPE_DELIMITER) {
194 2
                break;
195
            }
196
197
            // Skipping whitespaces and comments.
198 22
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
199 21
                continue;
200
            }
201
202 22
            if ($state === 0) {
203 22 View Code Duplication
                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
204 21
                    $state = 1;
205 21
                } else {
206 1
                    $parser->error(
207 1
                        __('An opening bracket was expected.'),
208
                        $token
209 1
                    );
210 1
                    break;
211
                }
212 21
            } elseif ($state === 1) {
213 21
                if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'CONSTRAINT')) {
214 6
                    $expr->isConstraint = true;
215 21
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_KEY)) {
216 11
                    $expr->key = Key::parse($parser, $list);
217 11
                    $state = 4;
218 21 View Code Duplication
                } elseif ($token->type === Token::TYPE_SYMBOL || $token->type === Token::TYPE_NONE) {
219 21
                    $expr->name = $token->value;
220 21
                    if (!$expr->isConstraint) {
221 21
                        $state = 2;
222 21
                    }
223 21
                } else {
224
                    $parser->error(
225
                        __('A symbol name was expected!'),
226
                        $token
227
                    );
228
                    return $ret;
229
                }
230 21
            } elseif ($state === 2) {
231 21
                $expr->type = DataType::parse($parser, $list);
232 21
                $state = 3;
233 21
            } elseif ($state === 3) {
234 21
                $expr->options = OptionsArray::parse($parser, $list, static::$FIELD_OPTIONS);
235 21
                $state = 4;
236 21 View Code Duplication
            } elseif ($state === 4) {
237 21
                if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'REFERENCES')) {
238 5
                    ++$list->idx; // Skipping keyword 'REFERENCES'.
239 5
                    $expr->references = Reference::parse($parser, $list);
240 5
                } else {
241 21
                    --$list->idx;
242
                }
243 21
                $state = 5;
244 21
            } elseif ($state === 5) {
245 21
                if ((!empty($expr->type)) || (!empty($expr->key))) {
246 21
                    $ret[] = $expr;
247 21
                }
248 21
                $expr = new CreateDefinition();
249 21
                if ($token->value === ',') {
250 17
                    $state = 1;
251 21
                } elseif ($token->value === ')') {
252 19
                    $state = 6;
253 19
                    ++$list->idx;
254 19
                    break;
255
                } else {
256 1
                    $parser->error(
257 1
                        __('A comma or a closing bracket was expected.'),
258
                        $token
259 1
                    );
260 1
                    $state = 0;
261 1
                    break;
262
                }
263 17
            }
264 21
        }
265
266
        // Last iteration was not saved.
267 23
        if ((!empty($expr->type)) || (!empty($expr->key))) {
268 1
            $ret[] = $expr;
269 1
        }
270
271 23 View Code Duplication
        if (($state !== 0) && ($state !== 6)) {
272 1
            $parser->error(
273 1
                __('A closing bracket was expected.'),
274 1
                $list->tokens[$list->idx - 1]
275 1
            );
276 1
        }
277
278 23
        --$list->idx;
279 23
        return $ret;
280
    }
281
282
    /**
283
     * @param CreateDefinition|CreateDefinition[] $component The component to be built.
284
     * @param array                               $options   Parameters for building.
285
     *
286
     * @return string
287
     */
288 6
    public static function build($component, array $options = array())
289
    {
290 6
        if (is_array($component)) {
291 4
            return "(\n  " . implode(",\n  ", $component) . "\n)";
292
        } else {
293 6
            $tmp = '';
294
295 6
            if ($component->isConstraint) {
296 1
                $tmp .= 'CONSTRAINT ';
297 1
            }
298
299 6
            if ((isset($component->name)) && ($component->name !== '')) {
300 5
                $tmp .= Context::escape($component->name) . ' ';
301 5
            }
302
303 6
            if (!empty($component->type)) {
304 4
                $tmp .= DataType::build(
305 4
                    $component->type,
306 4
                    array('lowercase' => true)
307 4
                ) . ' ';
308 4
            }
309
310 6
            if (!empty($component->key)) {
311 3
                $tmp .= $component->key . ' ';
312 3
            }
313
314 6
            if (!empty($component->references)) {
315 1
                $tmp .= 'REFERENCES ' . $component->references . ' ';
316 1
            }
317
318 6
            $tmp .= $component->options;
319
320 6
            return trim($tmp);
321
        }
322
    }
323
}
324