1<?php
2
3/**
4 * Parses an Index hint.
5 */
6
7namespace PhpMyAdmin\SqlParser\Components;
8
9use PhpMyAdmin\SqlParser\Component;
10use PhpMyAdmin\SqlParser\Parser;
11use PhpMyAdmin\SqlParser\Token;
12use PhpMyAdmin\SqlParser\TokensList;
13
14/**
15 * Parses an Index hint.
16 *
17 * @category   Components
18 *
19 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20 */
21class IndexHint extends Component
22{
23    /**
24     * The type of hint (USE/FORCE/IGNORE)
25     *
26     * @var string
27     */
28    public $type;
29
30    /**
31     * What the hint is for (INDEX/KEY)
32     *
33     * @var string
34     */
35    public $indexOrKey;
36
37    /**
38     * The clause for which this hint is (JOIN/ORDER BY/GROUP BY)
39     *
40     * @var string
41     */
42    public $for;
43
44    /**
45     * List of indexes in this hint
46     *
47     * @var array
48     */
49    public $indexes = array();
50
51    /**
52     * Constructor.
53     *
54     * @param string $type       the type of hint (USE/FORCE/IGNORE)
55     * @param string $indexOrKey What the hint is for (INDEX/KEY)
56     * @param string $for        the clause for which this hint is (JOIN/ORDER BY/GROUP BY)
57     * @param string $indexes    List of indexes in this hint
58     */
59    public function __construct(string $type = null, string $indexOrKey = null, string $for = null, array $indexes = array())
60    {
61        $this->type = $type;
62        $this->indexOrKey = $indexOrKey;
63        $this->for = $for;
64        $this->indexes = $indexes;
65    }
66
67    /**
68     * @param Parser     $parser  the parser that serves as context
69     * @param TokensList $list    the list of tokens that are being parsed
70     * @param array      $options parameters for parsing
71     *
72     * @return IndexHint|Component[]
73     */
74    public static function parse(Parser $parser, TokensList $list, array $options = array())
75    {
76        $ret = array();
77        $expr = new self();
78        $expr->type = isset($options['type']) ? $options['type'] : null;
79        /**
80         * The state of the parser.
81         *
82         * Below are the states of the parser.
83         *      0 ----------------- [ USE/IGNORE/FORCE ]-----------------> 1
84         *      1 -------------------- [ INDEX/KEY ] --------------------> 2
85         *      2 ----------------------- [ FOR ] -----------------------> 3
86         *      2 -------------------- [ expr_list ] --------------------> 0
87         *      3 -------------- [ JOIN/GROUP BY/ORDER BY ] -------------> 4
88         *      4 -------------------- [ expr_list ] --------------------> 0
89         * @var int
90         */
91        $state = 0;
92
93        // By design, the parser will parse first token after the keyword. So, the keyword
94        // must be analyzed too, in order to determine the type of this index hint.
95        if ($list->idx > 0) {
96            --$list->idx;
97        }
98        for (; $list->idx < $list->count; ++$list->idx) {
99            /**
100             * Token parsed at this moment.
101             *
102             * @var Token
103             */
104            $token = $list->tokens[$list->idx];
105
106            // End of statement.
107            if ($token->type === Token::TYPE_DELIMITER) {
108                break;
109            }
110            // Skipping whitespaces and comments.
111            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
112                continue;
113            }
114
115            switch ($state) {
116                case 0:
117                    if ($token->type === Token::TYPE_KEYWORD) {
118                        if ($token->keyword === 'USE' || $token->keyword === 'IGNORE' || $token->keyword === 'FORCE') {
119                            $expr->type = $token->keyword;
120                            $state = 1;
121                        } else {
122                            break 2;
123                        }
124                    }
125                    break;
126                case 1:
127                    if ($token->type === Token::TYPE_KEYWORD) {
128                        if ($token->keyword === 'INDEX' || $token->keyword === 'KEY') {
129                            $expr->indexOrKey = $token->keyword;
130                        } else {
131                            $parser->error('Unexpected keyword.', $token);
132                        }
133                        $state = 2;
134                    } else {
135                        // we expect the token to be a keyword
136                        $parser->error('Unexpected token.', $token);
137                    }
138                    break;
139                case 2:
140                    if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'FOR') {
141                        $state = 3;
142                    } else {
143                        $expr->indexes = ExpressionArray::parse($parser, $list);
144                        $state = 0;
145                        $ret[] = $expr;
146                        $expr = new self();
147                    }
148                    break;
149                case 3:
150                    if ($token->type === Token::TYPE_KEYWORD) {
151                        if ($token->keyword === 'JOIN' || $token->keyword === 'GROUP BY' || $token->keyword === 'ORDER BY') {
152                            $expr->for = $token->keyword;
153                        } else {
154                            $parser->error('Unexpected keyword.', $token);
155                        }
156                        $state = 4;
157                    } else {
158                        // we expect the token to be a keyword
159                        $parser->error('Unexpected token.', $token);
160                    }
161                    break;
162                case 4:
163                    $expr->indexes = ExpressionArray::parse($parser, $list);
164                    $state = 0;
165                    $ret[] = $expr;
166                    $expr = new self();
167                    break;
168            }
169        }
170        --$list->idx;
171
172        return $ret;
173    }
174
175    /**
176     * @param ArrayObj|ArrayObj[] $component the component to be built
177     * @param array               $options   parameters for building
178     *
179     * @return string
180     */
181    public static function build($component, array $options = array())
182    {
183        if (is_array($component)) {
184            return implode(' ', $component);
185        }
186
187        $ret = $component->type . ' ' . $component->indexOrKey . ' ';
188        if ($component->for !== null) {
189            $ret .= 'FOR ' . $component->for . ' ';
190        }
191        return $ret . ExpressionArray::build($component->indexes);
192    }
193}
194