Completed
Push — master ( e05c46...93167f )
by Dan
03:50
created

AlterOperation::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 3
crap 1
1
<?php
2
3
/**
4
 * Parses an alter operation.
5
 */
6
7
namespace PhpMyAdmin\SqlParser\Components;
8
9
use PhpMyAdmin\SqlParser\Component;
10
use PhpMyAdmin\SqlParser\Parser;
11
use PhpMyAdmin\SqlParser\Token;
12
use PhpMyAdmin\SqlParser\TokensList;
13
14
/**
15
 * Parses an alter operation.
16
 *
17
 * @category   Components
18
 *
19
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20
 */
21
class AlterOperation extends Component
22
{
23
    /**
24
     * All database options.
25
     *
26
     * @var array
27
     */
28
    public static $DB_OPTIONS = array(
29
        'CHARACTER SET' => array(1, 'var'),
30
        'CHARSET' => array(1, 'var'),
31
        'DEFAULT CHARACTER SET' => array(1, 'var'),
32
        'DEFAULT CHARSET' => array(1, 'var'),
33
        'UPGRADE' => array(1, 'var'),
34
        'COLLATE' => array(2, 'var'),
35
        'DEFAULT COLLATE' => array(2, 'var'),
36
    );
37
38
    /**
39
     * All table options.
40
     *
41
     * @var array
42
     */
43
    public static $TABLE_OPTIONS = array(
44
        'ENGINE' => array(1, 'var='),
45
        'AUTO_INCREMENT' => array(1, 'var='),
46
        'AVG_ROW_LENGTH' => array(1, 'var'),
47
        'MAX_ROWS' => array(1, 'var'),
48
        'ROW_FORMAT' => array(1, 'var'),
49
        'COMMENT' => array(1, 'var'),
50
        'ADD' => 1,
51
        'ALTER' => 1,
52
        'ANALYZE' => 1,
53
        'CHANGE' => 1,
54
        'CHECK' => 1,
55
        'COALESCE' => 1,
56
        'CONVERT' => 1,
57
        'DISABLE' => 1,
58
        'DISCARD' => 1,
59
        'DROP' => 1,
60
        'ENABLE' => 1,
61
        'IMPORT' => 1,
62
        'MODIFY' => 1,
63
        'OPTIMIZE' => 1,
64
        'ORDER' => 1,
65
        'PARTITION' => 1,
66
        'REBUILD' => 1,
67
        'REMOVE' => 1,
68
        'RENAME' => 1,
69
        'REORGANIZE' => 1,
70
        'REPAIR' => 1,
71
        'UPGRADE' => 1,
72
73
        'COLUMN' => 2,
74
        'CONSTRAINT' => 2,
75
        'DEFAULT' => 2,
76
        'TO' => 2,
77
        'BY' => 2,
78
        'FOREIGN' => 2,
79
        'FULLTEXT' => 2,
80
        'KEY' => 2,
81
        'KEYS' => 2,
82
        'PARTITIONING' => 2,
83
        'PRIMARY KEY' => 2,
84
        'SPATIAL' => 2,
85
        'TABLESPACE' => 2,
86
        'INDEX' => 2,
87
    );
88
89
    /**
90
     * All view options.
91
     *
92
     * @var array
93
     */
94
    public static $VIEW_OPTIONS = array(
95
        'AS' => 1,
96
    );
97
98
    /**
99
     * Options of this operation.
100
     *
101
     * @var OptionsArray
102
     */
103
    public $options;
104
105
    /**
106
     * The altered field.
107
     *
108
     * @var Expression
109
     */
110
    public $field;
111
112
    /**
113
     * Unparsed tokens.
114
     *
115
     * @var Token[]|string
116
     */
117
    public $unknown = array();
118
119
    /**
120
     * Constructor.
121
     *
122
     * @param OptionsArray $options options of alter operation
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...
123
     * @param Expression   $field   altered field
0 ignored issues
show
Documentation introduced by
Should the type for parameter $field not be Expression|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 array        $unknown unparsed tokens found at the end of operation
125
     */
126 9
    public function __construct(
127
        $options = null,
128
        $field = null,
129
        $unknown = array()
130
    ) {
131 9
        $this->options = $options;
132 9
        $this->field = $field;
133 9
        $this->unknown = $unknown;
134 9
    }
135
136
    /**
137
     * @param Parser     $parser  the parser that serves as context
138
     * @param TokensList $list    the list of tokens that are being parsed
139
     * @param array      $options parameters for parsing
140
     *
141
     * @return AlterOperation
142
     */
143 9
    public static function parse(Parser $parser, TokensList $list, array $options = array())
144
    {
145 9
        $ret = new self();
146
147
        /**
148
         * Counts brackets.
149
         *
150
         * @var int
151
         */
152 9
        $brackets = 0;
153
154
        /**
155
         * The state of the parser.
156
         *
157
         * Below are the states of the parser.
158
         *
159
         *      0 ---------------------[ options ]---------------------> 1
160
         *
161
         *      1 ----------------------[ field ]----------------------> 2
162
         *
163
         *      2 -------------------------[ , ]-----------------------> 0
164
         *
165
         * @var int
166
         */
167 9
        $state = 0;
168
169 9
        for (; $list->idx < $list->count; ++$list->idx) {
170
            /**
171
             * Token parsed at this moment.
172
             *
173
             * @var Token
174
             */
175 9
            $token = $list->tokens[$list->idx];
176
177
            // End of statement.
178 9
            if ($token->type === Token::TYPE_DELIMITER) {
179 6
                break;
180
            }
181
182
            // Skipping comments.
183 9
            if ($token->type === Token::TYPE_COMMENT) {
184 1
                continue;
185
            }
186
187
            // Skipping whitespaces.
188 9
            if ($token->type === Token::TYPE_WHITESPACE) {
189 3
                if ($state === 2) {
190
                    // When parsing the unknown part, the whitespaces are
191
                    // included to not break anything.
192 3
                    $ret->unknown[] = $token;
193
                }
194 3
                continue;
195
            }
196
197 9
            if ($state === 0) {
198 9
                $ret->options = OptionsArray::parse($parser, $list, $options);
199
200 9
                if ($ret->options->has('AS')) {
201 1 View Code Duplication
                    for (; $list->idx < $list->count; ++$list->idx) {
202 1
                        if ($list->tokens[$list->idx]->type === Token::TYPE_DELIMITER) {
203 1
                            break;
204
                        }
205 1
                        $ret->unknown[] = $list->tokens[$list->idx];
206
                    }
207 1
                    break;
208
                }
209
210 8
                $state = 1;
211 6 View Code Duplication
            } elseif ($state === 1) {
212 6
                $ret->field = Expression::parse(
213
                    $parser,
214
                    $list,
215
                    array(
216 6
                        'breakOnAlias' => true,
217
                        'parseField' => 'column',
218
                    )
219
                );
220 6
                if ($ret->field === null) {
221
                    // No field was read. We go back one token so the next
222
                    // iteration will parse the same token, but in state 2.
223 3
                    --$list->idx;
224
                }
225 6
                $state = 2;
226 5
            } elseif ($state === 2) {
227 5
                if ($token->type === Token::TYPE_OPERATOR) {
228 4
                    if ($token->value === '(') {
229 4
                        ++$brackets;
230 4
                    } elseif ($token->value === ')') {
231 4
                        --$brackets;
232 3
                    } elseif (($token->value === ',') && ($brackets === 0)) {
233 4
                        break;
234
                    }
235 5
                } elseif (!empty(Parser::$STATEMENT_PARSERS[$token->value])) {
236
                    // We have reached the end of ALTER operation and suddenly found
237
                    // a start to new statement, but have not find a delimiter between them
238
239 2
                    if (!($token->value == 'SET' && $list->tokens[$list->idx - 1]->value == 'CHARACTER')) {
240 2
                        $parser->error(
241 2
                            'A new statement was found, but no delimiter between it and the previous one.',
242
                            $token
243
                        );
244 2
                        break;
245
                    }
246
                }
247 5
                $ret->unknown[] = $token;
248
            }
249
        }
250
251 9
        if ($ret->options->isEmpty()) {
252 1
            $parser->error(
253 1
                'Unrecognized alter operation.',
254 1
                $list->tokens[$list->idx]
255
            );
256
        }
257
258 9
        --$list->idx;
259
260 9
        return $ret;
261
    }
262
263
    /**
264
     * @param AlterOperation $component the component to be built
265
     * @param array          $options   parameters for building
266
     *
267
     * @return string
268
     */
269 1
    public static function build($component, array $options = array())
270
    {
271 1
        $ret = $component->options . ' ';
272 1
        if ((isset($component->field)) && ($component->field !== '')) {
273 1
            $ret .= $component->field . ' ';
274
        }
275 1
        $ret .= TokensList::build($component->unknown);
276
277 1
        return $ret;
278
    }
279
}
280