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