1<?php
2
3/**
4 * Parses the definition of a key.
5 */
6
7namespace PhpMyAdmin\SqlParser\Components;
8
9use PhpMyAdmin\SqlParser\Component;
10use PhpMyAdmin\SqlParser\Context;
11use PhpMyAdmin\SqlParser\Parser;
12use PhpMyAdmin\SqlParser\Token;
13use PhpMyAdmin\SqlParser\TokensList;
14
15/**
16 * Parses the definition of a key.
17 *
18 * Used for parsing `CREATE TABLE` statement.
19 *
20 * @category   Components
21 *
22 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
23 */
24class Key extends Component
25{
26    /**
27     * All key options.
28     *
29     * @var array
30     */
31    public static $KEY_OPTIONS = array(
32        'KEY_BLOCK_SIZE' => array(
33            1,
34            'var',
35        ),
36        'USING' => array(
37            2,
38            'var',
39        ),
40        'WITH PARSER' => array(
41            3,
42            'var',
43        ),
44        'COMMENT' => array(
45            4,
46            'var=',
47        )
48    );
49
50    /**
51     * The name of this key.
52     *
53     * @var string
54     */
55    public $name;
56
57    /**
58     * Columns.
59     *
60     * @var array
61     */
62    public $columns;
63
64    /**
65     * The type of this key.
66     *
67     * @var string
68     */
69    public $type;
70
71    /**
72     * The options of this key.
73     *
74     * @var OptionsArray
75     */
76    public $options;
77
78    /**
79     * Constructor.
80     *
81     * @param string       $name    the name of the key
82     * @param array        $columns the columns covered by this key
83     * @param string       $type    the type of this key
84     * @param OptionsArray $options the options of this key
85     */
86    public function __construct(
87        $name = null,
88        array $columns = array(),
89        $type = null,
90        $options = null
91    ) {
92        $this->name = $name;
93        $this->columns = $columns;
94        $this->type = $type;
95        $this->options = $options;
96    }
97
98    /**
99     * @param Parser     $parser  the parser that serves as context
100     * @param TokensList $list    the list of tokens that are being parsed
101     * @param array      $options parameters for parsing
102     *
103     * @return Key
104     */
105    public static function parse(Parser $parser, TokensList $list, array $options = array())
106    {
107        $ret = new self();
108
109        /**
110         * Last parsed column.
111         *
112         * @var array
113         */
114        $lastColumn = array();
115
116        /**
117         * The state of the parser.
118         *
119         * Below are the states of the parser.
120         *
121         *      0 ----------------------[ type ]-----------------------> 1
122         *
123         *      1 ----------------------[ name ]-----------------------> 1
124         *      1 ---------------------[ columns ]---------------------> 2
125         *
126         *      2 ---------------------[ options ]---------------------> 3
127         *
128         * @var int
129         */
130        $state = 0;
131
132        for (; $list->idx < $list->count; ++$list->idx) {
133            /**
134             * Token parsed at this moment.
135             *
136             * @var Token
137             */
138            $token = $list->tokens[$list->idx];
139
140            // End of statement.
141            if ($token->type === Token::TYPE_DELIMITER) {
142                break;
143            }
144
145            // Skipping whitespaces and comments.
146            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
147                continue;
148            }
149
150            if ($state === 0) {
151                $ret->type = $token->value;
152                $state = 1;
153            } elseif ($state === 1) {
154                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
155                    $state = 2;
156                } else {
157                    $ret->name = $token->value;
158                }
159            } elseif ($state === 2) {
160                if ($token->type === Token::TYPE_OPERATOR) {
161                    if ($token->value === '(') {
162                        $state = 3;
163                    } elseif (($token->value === ',') || ($token->value === ')')) {
164                        $state = ($token->value === ',') ? 2 : 4;
165                        if (! empty($lastColumn)) {
166                            $ret->columns[] = $lastColumn;
167                            $lastColumn = array();
168                        }
169                    }
170                } else {
171                    $lastColumn['name'] = $token->value;
172                }
173            } elseif ($state === 3) {
174                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
175                    $state = 2;
176                } else {
177                    $lastColumn['length'] = $token->value;
178                }
179            } elseif ($state === 4) {
180                $ret->options = OptionsArray::parse($parser, $list, static::$KEY_OPTIONS);
181                ++$list->idx;
182                break;
183            }
184        }
185
186        --$list->idx;
187
188        return $ret;
189    }
190
191    /**
192     * @param Key   $component the component to be built
193     * @param array $options   parameters for building
194     *
195     * @return string
196     */
197    public static function build($component, array $options = array())
198    {
199        $ret = $component->type . ' ';
200        if (! empty($component->name)) {
201            $ret .= Context::escape($component->name) . ' ';
202        }
203
204        $columns = array();
205        foreach ($component->columns as $column) {
206            $tmp = Context::escape($column['name']);
207            if (isset($column['length'])) {
208                $tmp .= '(' . $column['length'] . ')';
209            }
210            $columns[] = $tmp;
211        }
212
213        $ret .= '(' . implode(',', $columns) . ') ' . $component->options;
214
215        return trim($ret);
216    }
217}
218