1<?php
2
3/**
4 * `SET` 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 * `SET` keyword parser.
16 *
17 * @category   Keywords
18 *
19 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20 */
21class SetOperation extends Component
22{
23    /**
24     * The name of the column that is being updated.
25     *
26     * @var string
27     */
28    public $column;
29
30    /**
31     * The new value.
32     *
33     * @var string
34     */
35    public $value;
36
37    /**
38     * Constructor.
39     *
40     * @param string $column Field's name..
41     * @param string $value  new value
42     */
43    public function __construct($column = '', $value = '')
44    {
45        $this->column = $column;
46        $this->value = $value;
47    }
48
49    /**
50     * @param Parser     $parser  the parser that serves as context
51     * @param TokensList $list    the list of tokens that are being parsed
52     * @param array      $options parameters for parsing
53     *
54     * @return SetOperation[]
55     */
56    public static function parse(Parser $parser, TokensList $list, array $options = array())
57    {
58        $ret = array();
59
60        $expr = new self();
61
62        /**
63         * The state of the parser.
64         *
65         * Below are the states of the parser.
66         *
67         *      0 ---------------------[ col_name ]--------------------> 0
68         *      0 ------------------------[ = ]------------------------> 1
69         *      1 -----------------------[ value ]---------------------> 1
70         *      1 ------------------------[ , ]------------------------> 0
71         *
72         * @var int
73         */
74        $state = 0;
75
76        /**
77         * Token when the parser has seen the latest comma
78         *
79         * @var Token
80         */
81        $commaLastSeenAt = null;
82
83        for (; $list->idx < $list->count; ++$list->idx) {
84            /**
85             * Token parsed at this moment.
86             *
87             * @var Token
88             */
89            $token = $list->tokens[$list->idx];
90
91            // End of statement.
92            if ($token->type === Token::TYPE_DELIMITER) {
93                break;
94            }
95
96            // Skipping whitespaces and comments.
97            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
98                continue;
99            }
100
101            // No keyword is expected.
102            if (($token->type === Token::TYPE_KEYWORD)
103                && ($token->flags & Token::FLAG_KEYWORD_RESERVED)
104                && ($state === 0)
105            ) {
106                break;
107            }
108
109            if ($state === 0) {
110                if ($token->token === '=') {
111                    $state = 1;
112                } elseif ($token->value !== ',') {
113                    $expr->column .= $token->token;
114                } elseif ($token->value === ',') {
115                    $commaLastSeenAt = $token;
116                }
117            } elseif ($state === 1) {
118                $tmp = Expression::parse(
119                    $parser,
120                    $list,
121                    array(
122                        'breakOnAlias' => true
123                    )
124                );
125                if (is_null($tmp)) {
126                    $parser->error('Missing expression.', $token);
127                    break;
128                }
129                $expr->column = trim($expr->column);
130                $expr->value = $tmp->expr;
131                $ret[] = $expr;
132                $expr = new self();
133                $state = 0;
134                $commaLastSeenAt = null;
135            }
136        }
137        --$list->idx;
138
139        // We saw a comma, but didn't see a column-value pair after it
140        if ($commaLastSeenAt !== null) {
141            $parser->error('Unexpected token.', $commaLastSeenAt);
142        }
143
144        return $ret;
145    }
146
147    /**
148     * @param SetOperation|SetOperation[] $component the component to be built
149     * @param array                       $options   parameters for building
150     *
151     * @return string
152     */
153    public static function build($component, array $options = array())
154    {
155        if (is_array($component)) {
156            return implode(', ', $component);
157        }
158
159        return $component->column . ' = ' . $component->value;
160    }
161}
162