1<?php
2
3/**
4 * `INTO` keyword parser.
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 * `INTO` keyword parser.
16 *
17 * @category   Keywords
18 *
19 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20 */
21class IntoKeyword extends Component
22{
23    /**
24     * FIELDS/COLUMNS Options for `SELECT...INTO` statements.
25     *
26     * @var array
27     */
28    public static $FIELDS_OPTIONS = array(
29        'TERMINATED BY' => array(
30            1,
31            'expr',
32        ),
33        'OPTIONALLY' => 2,
34        'ENCLOSED BY' => array(
35            3,
36            'expr',
37        ),
38        'ESCAPED BY' => array(
39            4,
40            'expr',
41        )
42    );
43
44    /**
45     * LINES Options for `SELECT...INTO` statements.
46     *
47     * @var array
48     */
49    public static $LINES_OPTIONS = array(
50        'STARTING BY' => array(
51            1,
52            'expr',
53        ),
54        'TERMINATED BY' => array(
55            2,
56            'expr',
57        )
58    );
59
60    /**
61     * Type of target (OUTFILE or SYMBOL).
62     *
63     * @var string
64     */
65    public $type;
66
67    /**
68     * The destination, which can be a table or a file.
69     *
70     * @var string|Expression
71     */
72    public $dest;
73
74    /**
75     * The name of the columns.
76     *
77     * @var array
78     */
79    public $columns;
80
81    /**
82     * The values to be selected into (SELECT .. INTO @var1).
83     *
84     * @var Expression[]
85     */
86    public $values;
87
88    /**
89     * Options for FIELDS/COLUMNS keyword.
90     *
91     * @var OptionsArray
92     *
93     * @see static::$FIELDS_OPTIONS
94     */
95    public $fields_options;
96
97    /**
98     * Whether to use `FIELDS` or `COLUMNS` while building.
99     *
100     * @var bool
101     */
102    public $fields_keyword;
103
104    /**
105     * Options for OPTIONS keyword.
106     *
107     * @var OptionsArray
108     *
109     * @see static::$LINES_OPTIONS
110     */
111    public $lines_options;
112
113    /**
114     * Constructor.
115     *
116     * @param string            $type           type of destination (may be OUTFILE)
117     * @param string|Expression $dest           actual destination
118     * @param array             $columns        column list of destination
119     * @param array             $values         selected fields
120     * @param OptionsArray      $fields_options options for FIELDS/COLUMNS keyword
121     * @param bool              $fields_keyword options for OPTIONS keyword
122     */
123    public function __construct(
124        $type = null,
125        $dest = null,
126        $columns = null,
127        $values = null,
128        $fields_options = null,
129        $fields_keyword = null
130    ) {
131        $this->type = $type;
132        $this->dest = $dest;
133        $this->columns = $columns;
134        $this->values = $values;
135        $this->fields_options = $fields_options;
136        $this->fields_keyword = $fields_keyword;
137    }
138
139    /**
140     * @param Parser     $parser  the parser that serves as context
141     * @param TokensList $list    the list of tokens that are being parsed
142     * @param array      $options parameters for parsing
143     *
144     * @return IntoKeyword
145     */
146    public static function parse(Parser $parser, TokensList $list, array $options = array())
147    {
148        $ret = new self();
149
150        /**
151         * The state of the parser.
152         *
153         * Below are the states of the parser.
154         *
155         *      0 -----------------------[ name ]----------------------> 1
156         *      0 ---------------------[ OUTFILE ]---------------------> 2
157         *
158         *      1 ------------------------[ ( ]------------------------> (END)
159         *
160         *      2 ---------------------[ filename ]--------------------> 1
161         *
162         * @var int
163         */
164        $state = 0;
165
166        for (; $list->idx < $list->count; ++$list->idx) {
167            /**
168             * Token parsed at this moment.
169             *
170             * @var Token
171             */
172            $token = $list->tokens[$list->idx];
173
174            // End of statement.
175            if ($token->type === Token::TYPE_DELIMITER) {
176                break;
177            }
178
179            // Skipping whitespaces and comments.
180            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
181                continue;
182            }
183
184            if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
185                if (($state === 0) && ($token->keyword === 'OUTFILE')) {
186                    $ret->type = 'OUTFILE';
187                    $state = 2;
188                    continue;
189                }
190
191                // No other keyword is expected except for $state = 4, which expects `LINES`
192                if ($state !== 4) {
193                    break;
194                }
195            }
196
197            if ($state === 0) {
198                if ((isset($options['fromInsert'])
199                    && $options['fromInsert'])
200                    || (isset($options['fromReplace'])
201                    && $options['fromReplace'])
202                ) {
203                    $ret->dest = Expression::parse(
204                        $parser,
205                        $list,
206                        array(
207                            'parseField' => 'table',
208                            'breakOnAlias' => true
209                        )
210                    );
211                } else {
212                    $ret->values = ExpressionArray::parse($parser, $list);
213                }
214                $state = 1;
215            } elseif ($state === 1) {
216                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
217                    $ret->columns = ArrayObj::parse($parser, $list)->values;
218                    ++$list->idx;
219                }
220                break;
221            } elseif ($state === 2) {
222                $ret->dest = $token->value;
223
224                $state = 3;
225            } elseif ($state === 3) {
226                $ret->parseFileOptions($parser, $list, $token->value);
227                $state = 4;
228            } elseif ($state === 4) {
229                if ($token->type === Token::TYPE_KEYWORD && $token->keyword !== 'LINES') {
230                    break;
231                }
232
233                $ret->parseFileOptions($parser, $list, $token->value);
234                $state = 5;
235            }
236        }
237
238        --$list->idx;
239
240        return $ret;
241    }
242
243    public function parseFileOptions(Parser $parser, TokensList $list, $keyword = 'FIELDS')
244    {
245        ++$list->idx;
246
247        if ($keyword === 'FIELDS' || $keyword === 'COLUMNS') {
248            // parse field options
249            $this->fields_options = OptionsArray::parse(
250                $parser,
251                $list,
252                static::$FIELDS_OPTIONS
253            );
254
255            $this->fields_keyword = ($keyword === 'FIELDS');
256        } else {
257            // parse line options
258            $this->lines_options = OptionsArray::parse(
259                $parser,
260                $list,
261                static::$LINES_OPTIONS
262            );
263        }
264    }
265
266    /**
267     * @param IntoKeyword $component the component to be built
268     * @param array       $options   parameters for building
269     *
270     * @return string
271     */
272    public static function build($component, array $options = array())
273    {
274        if ($component->dest instanceof Expression) {
275            $columns = ! empty($component->columns) ? '(`' . implode('`, `', $component->columns) . '`)' : '';
276
277            return $component->dest . $columns;
278        } elseif (isset($component->values)) {
279            return ExpressionArray::build($component->values);
280        }
281
282        $ret = 'OUTFILE "' . $component->dest . '"';
283
284        $fields_options_str = OptionsArray::build($component->fields_options);
285        if (trim($fields_options_str) !== '') {
286            $ret .= $component->fields_keyword ? ' FIELDS' : ' COLUMNS';
287            $ret .= ' ' . $fields_options_str;
288        }
289
290        $lines_options_str = OptionsArray::build($component->lines_options, array('expr' => true));
291        if (trim($lines_options_str) !== '') {
292            $ret .= ' LINES ' . $lines_options_str;
293        }
294
295        return $ret;
296    }
297}
298