1<?php 2 3/** 4 * `INSERT` statement. 5 */ 6 7namespace PhpMyAdmin\SqlParser\Statements; 8 9use PhpMyAdmin\SqlParser\Components\ArrayObj; 10use PhpMyAdmin\SqlParser\Components\Array2d; 11use PhpMyAdmin\SqlParser\Components\IntoKeyword; 12use PhpMyAdmin\SqlParser\Components\OptionsArray; 13use PhpMyAdmin\SqlParser\Components\SetOperation; 14use PhpMyAdmin\SqlParser\Parser; 15use PhpMyAdmin\SqlParser\Statement; 16use PhpMyAdmin\SqlParser\Token; 17use PhpMyAdmin\SqlParser\TokensList; 18 19/** 20 * `INSERT` statement. 21 * 22 * INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] 23 * [INTO] tbl_name 24 * [PARTITION (partition_name,...)] 25 * [(col_name,...)] 26 * {VALUES | VALUE} ({expr | DEFAULT},...),(...),... 27 * [ ON DUPLICATE KEY UPDATE 28 * col_name=expr 29 * [, col_name=expr] ... ] 30 * 31 * or 32 * 33 * INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] 34 * [INTO] tbl_name 35 * [PARTITION (partition_name,...)] 36 * SET col_name={expr | DEFAULT}, ... 37 * [ ON DUPLICATE KEY UPDATE 38 * col_name=expr 39 * [, col_name=expr] ... ] 40 * 41 * or 42 * 43 * INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE] 44 * [INTO] tbl_name 45 * [PARTITION (partition_name,...)] 46 * [(col_name,...)] 47 * SELECT ... 48 * [ ON DUPLICATE KEY UPDATE 49 * col_name=expr 50 * [, col_name=expr] ... ] 51 * 52 * @category Statements 53 * 54 * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+ 55 */ 56class InsertStatement extends Statement 57{ 58 /** 59 * Options for `INSERT` statements. 60 * 61 * @var array 62 */ 63 public static $OPTIONS = array( 64 'LOW_PRIORITY' => 1, 65 'DELAYED' => 2, 66 'HIGH_PRIORITY' => 3, 67 'IGNORE' => 4 68 ); 69 70 /** 71 * Tables used as target for this statement. 72 * 73 * @var IntoKeyword 74 */ 75 public $into; 76 77 /** 78 * Values to be inserted. 79 * 80 * @var ArrayObj[]|null 81 */ 82 public $values; 83 84 /** 85 * If SET clause is present 86 * holds the SetOperation. 87 * 88 * @var SetOperation[] 89 */ 90 public $set; 91 92 /** 93 * If SELECT clause is present 94 * holds the SelectStatement. 95 * 96 * @var SelectStatement 97 */ 98 public $select; 99 100 /** 101 * If ON DUPLICATE KEY UPDATE clause is present 102 * holds the SetOperation. 103 * 104 * @var SetOperation[] 105 */ 106 public $onDuplicateSet; 107 108 /** 109 * @return string 110 */ 111 public function build() 112 { 113 $ret = 'INSERT ' . $this->options; 114 $ret = trim($ret) . ' INTO ' . $this->into; 115 116 if (! is_null($this->values) && count($this->values) > 0) { 117 $ret .= ' VALUES ' . Array2d::build($this->values); 118 } elseif (! is_null($this->set) && count($this->set) > 0) { 119 $ret .= ' SET ' . SetOperation::build($this->set); 120 } elseif (! is_null($this->select) && strlen($this->select) > 0) { 121 $ret .= ' ' . $this->select->build(); 122 } 123 124 if (! is_null($this->onDuplicateSet) && count($this->onDuplicateSet) > 0) { 125 $ret .= ' ON DUPLICATE KEY UPDATE ' . SetOperation::build($this->onDuplicateSet); 126 } 127 128 return $ret; 129 } 130 131 /** 132 * @param Parser $parser the instance that requests parsing 133 * @param TokensList $list the list of tokens to be parsed 134 */ 135 public function parse(Parser $parser, TokensList $list) 136 { 137 ++$list->idx; // Skipping `INSERT`. 138 139 // parse any options if provided 140 $this->options = OptionsArray::parse( 141 $parser, 142 $list, 143 static::$OPTIONS 144 ); 145 ++$list->idx; 146 147 /** 148 * The state of the parser. 149 * 150 * Below are the states of the parser. 151 * 152 * 0 ---------------------------------[ INTO ]----------------------------------> 1 153 * 154 * 1 -------------------------[ VALUES/VALUE/SET/SELECT ]-----------------------> 2 155 * 156 * 2 -------------------------[ ON DUPLICATE KEY UPDATE ]-----------------------> 3 157 * 158 * @var int 159 */ 160 $state = 0; 161 162 /** 163 * For keeping track of semi-states on encountering 164 * ON DUPLICATE KEY UPDATE ... 165 */ 166 $miniState = 0; 167 168 for (; $list->idx < $list->count; ++$list->idx) { 169 /** 170 * Token parsed at this moment. 171 * 172 * @var Token 173 */ 174 $token = $list->tokens[$list->idx]; 175 176 // End of statement. 177 if ($token->type === Token::TYPE_DELIMITER) { 178 break; 179 } 180 181 // Skipping whitespaces and comments. 182 if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) { 183 continue; 184 } 185 186 if ($state === 0) { 187 if ($token->type === Token::TYPE_KEYWORD 188 && $token->keyword !== 'INTO' 189 ) { 190 $parser->error('Unexpected keyword.', $token); 191 break; 192 } 193 194 ++$list->idx; 195 $this->into = IntoKeyword::parse( 196 $parser, 197 $list, 198 array('fromInsert' => true) 199 ); 200 201 $state = 1; 202 } elseif ($state === 1) { 203 if ($token->type === Token::TYPE_KEYWORD) { 204 if ($token->keyword === 'VALUE' 205 || $token->keyword === 'VALUES' 206 ) { 207 ++$list->idx; // skip VALUES 208 209 $this->values = Array2d::parse($parser, $list); 210 } elseif ($token->keyword === 'SET') { 211 ++$list->idx; // skip SET 212 213 $this->set = SetOperation::parse($parser, $list); 214 } elseif ($token->keyword === 'SELECT') { 215 $this->select = new SelectStatement($parser, $list); 216 } else { 217 $parser->error( 218 'Unexpected keyword.', 219 $token 220 ); 221 break; 222 } 223 $state = 2; 224 $miniState = 1; 225 } else { 226 $parser->error( 227 'Unexpected token.', 228 $token 229 ); 230 break; 231 } 232 } elseif ($state === 2) { 233 $lastCount = $miniState; 234 235 if ($miniState === 1 && $token->keyword === 'ON') { 236 ++$miniState; 237 } elseif ($miniState === 2 && $token->keyword === 'DUPLICATE') { 238 ++$miniState; 239 } elseif ($miniState === 3 && $token->keyword === 'KEY') { 240 ++$miniState; 241 } elseif ($miniState === 4 && $token->keyword === 'UPDATE') { 242 ++$miniState; 243 } 244 245 if ($lastCount === $miniState) { 246 $parser->error( 247 'Unexpected token.', 248 $token 249 ); 250 break; 251 } 252 253 if ($miniState === 5) { 254 ++$list->idx; 255 $this->onDuplicateSet = SetOperation::parse($parser, $list); 256 $state = 3; 257 } 258 } 259 } 260 261 --$list->idx; 262 } 263} 264