1<?php 2/** 3 * FromProcessor.php 4 * 5 * This file implements the processor for the FROM statement. 6 * 7 * PHP version 5 8 * 9 * LICENSE: 10 * Copyright (c) 2010-2014 Justin Swanhart and André Rothe 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. The name of the author may not be used to endorse or promote products 22 * derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 27 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * @author André Rothe <andre.rothe@phosco.info> 36 * @author George Schneeloch <noisecapella@gmail.com> 37 * @copyright 2010-2014 Justin Swanhart and André Rothe 38 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 39 * @version SVN: $Id$ 40 * 41 */ 42 43namespace PHPSQLParser\processors; 44use PHPSQLParser\utils\ExpressionType; 45 46/** 47 * This class processes the FROM statement. 48 * 49 * @author André Rothe <andre.rothe@phosco.info> 50 * @author Marco Th. <marco64th@gmail.com> 51 * @author George Schneeloch <noisecapella@gmail.com> 52 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 53 * 54 */ 55class FromProcessor extends AbstractProcessor { 56 57 protected function processExpressionList($unparsed) { 58 $processor = new ExpressionListProcessor($this->options); 59 return $processor->process($unparsed); 60 } 61 62 protected function processColumnList($unparsed) { 63 $processor = new ColumnListProcessor($this->options); 64 return $processor->process($unparsed); 65 } 66 67 protected function processSQLDefault($unparsed) { 68 $processor = new DefaultProcessor($this->options); 69 return $processor->process($unparsed); 70 } 71 72 protected function initParseInfo($parseInfo = false) { 73 // first init 74 if ($parseInfo === false) { 75 $parseInfo = array('join_type' => "", 'saved_join_type' => "JOIN"); 76 } 77 // loop init 78 return array('expression' => "", 'token_count' => 0, 'table' => "", 'no_quotes' => "", 'alias' => false, 79 'hints' => false, 'join_type' => "", 'next_join_type' => "", 80 'saved_join_type' => $parseInfo['saved_join_type'], 'ref_type' => false, 'ref_expr' => false, 81 'base_expr' => false, 'sub_tree' => false, 'subquery' => ""); 82 } 83 84 protected function processFromExpression(&$parseInfo) { 85 $res = array(); 86 87 // exchange the join types (join_type is save now, saved_join_type holds the next one) 88 $parseInfo['join_type'] = $parseInfo['saved_join_type']; // initialized with JOIN 89 $parseInfo['saved_join_type'] = ($parseInfo['next_join_type'] ? $parseInfo['next_join_type'] : 'JOIN'); 90 91 // we have a reg_expr, so we have to parse it 92 if ($parseInfo['ref_expr'] !== false) { 93 $unparsed = $this->splitSQLIntoTokens($parseInfo['ref_expr']); 94 95 // here we can get a comma separated list 96 foreach ($unparsed as $k => $v) { 97 if ($this->isCommaToken($v)) { 98 $unparsed[$k] = ""; 99 } 100 } 101 if ($parseInfo['ref_type'] === 'USING') { 102 // unparsed has only one entry, the column list 103 $ref = $this->processColumnList($this->removeParenthesisFromStart($unparsed[0])); 104 $ref = array(array('expr_type' => ExpressionType::COLUMN_LIST, 'base_expr' => $unparsed[0], 'sub_tree' => $ref)); 105 } else { 106 $ref = $this->processExpressionList($unparsed); 107 } 108 $parseInfo['ref_expr'] = (empty($ref) ? false : $ref); 109 } 110 111 // there is an expression, we have to parse it 112 if (substr(trim($parseInfo['table']), 0, 1) == '(') { 113 $parseInfo['expression'] = $this->removeParenthesisFromStart($parseInfo['table']); 114 115 if (preg_match("/^\\s*(-- [\\w\\s]+\\n)?\\s*SELECT/i", $parseInfo['expression'])) { 116 $parseInfo['sub_tree'] = $this->processSQLDefault($parseInfo['expression']); 117 $res['expr_type'] = ExpressionType::SUBQUERY; 118 } else { 119 $tmp = $this->splitSQLIntoTokens($parseInfo['expression']); 120 $unionProcessor = new UnionProcessor($this->options); 121 $unionQueries = $unionProcessor->process($tmp); 122 123 // If there was no UNION or UNION ALL in the query, then the query is 124 // stored at $queries[0]. 125 if (!empty($unionQueries) && !UnionProcessor::isUnion($unionQueries)) { 126 $sub_tree = $this->process($unionQueries[0]); 127 } 128 else { 129 $sub_tree = $unionQueries; 130 } 131 $parseInfo['sub_tree'] = $sub_tree; 132 $res['expr_type'] = ExpressionType::TABLE_EXPRESSION; 133 } 134 } else { 135 $res['expr_type'] = ExpressionType::TABLE; 136 $res['table'] = $parseInfo['table']; 137 $res['no_quotes'] = $this->revokeQuotation($parseInfo['table']); 138 } 139 140 $res['alias'] = $parseInfo['alias']; 141 $res['hints'] = $parseInfo['hints']; 142 $res['join_type'] = $parseInfo['join_type']; 143 $res['ref_type'] = $parseInfo['ref_type']; 144 $res['ref_clause'] = $parseInfo['ref_expr']; 145 $res['base_expr'] = trim($parseInfo['expression']); 146 $res['sub_tree'] = $parseInfo['sub_tree']; 147 return $res; 148 } 149 150 public function process($tokens) { 151 $parseInfo = $this->initParseInfo(); 152 $expr = array(); 153 $token_category = ''; 154 $prevToken = ''; 155 156 $skip_next = false; 157 $i = 0; 158 159 foreach ($tokens as $token) { 160 $upper = strtoupper(trim($token)); 161 162 if ($skip_next && $token !== "") { 163 $parseInfo['token_count']++; 164 $skip_next = false; 165 continue; 166 } else { 167 if ($skip_next) { 168 continue; 169 } 170 } 171 172 if ($this->isCommentToken($token)) { 173 $expr[] = parent::processComment($token); 174 continue; 175 } 176 177 switch ($upper) { 178 case 'CROSS': 179 case ',': 180 case 'INNER': 181 case 'STRAIGHT_JOIN': 182 break; 183 184 case 'OUTER': 185 case 'JOIN': 186 if ($token_category === 'LEFT' || $token_category === 'RIGHT' || $token_category === 'NATURAL') { 187 $token_category = ''; 188 $parseInfo['next_join_type'] = strtoupper(trim($prevToken)); // it seems to be a join 189 } 190 break; 191 192 case 'LEFT': 193 case 'RIGHT': 194 case 'NATURAL': 195 $token_category = $upper; 196 $prevToken = $token; 197 $i++; 198 continue 2; 199 200 default: 201 if ($token_category === 'LEFT' || $token_category === 'RIGHT') { 202 if ($upper === '') { 203 $prevToken .= $token; 204 break; 205 } else { 206 $token_category = ''; // it seems to be a function 207 $parseInfo['expression'] .= $prevToken; 208 if ($parseInfo['ref_type'] !== false) { // all after ON / USING 209 $parseInfo['ref_expr'] .= $prevToken; 210 } 211 $prevToken = ''; 212 } 213 } 214 $parseInfo['expression'] .= $token; 215 if ($parseInfo['ref_type'] !== false) { // all after ON / USING 216 $parseInfo['ref_expr'] .= $token; 217 } 218 break; 219 } 220 221 if ($upper === '') { 222 $i++; 223 continue; 224 } 225 226 switch ($upper) { 227 case 'AS': 228 $parseInfo['alias'] = array('as' => true, 'name' => "", 'base_expr' => $token); 229 $parseInfo['token_count']++; 230 $n = 1; 231 $str = ""; 232 while ($str === "" && isset($tokens[$i + $n])) { 233 $parseInfo['alias']['base_expr'] .= ($tokens[$i + $n] === "" ? " " : $tokens[$i + $n]); 234 $str = trim($tokens[$i + $n]); 235 ++$n; 236 } 237 $parseInfo['alias']['name'] = $str; 238 $parseInfo['alias']['no_quotes'] = $this->revokeQuotation($str); 239 $parseInfo['alias']['base_expr'] = trim($parseInfo['alias']['base_expr']); 240 break; 241 242 case 'IGNORE': 243 case 'USE': 244 case 'FORCE': 245 $token_category = 'IDX_HINT'; 246 $parseInfo['hints'][]['hint_type'] = $upper; 247 continue 2; 248 249 case 'KEY': 250 case 'INDEX': 251 if ($token_category === 'CREATE') { 252 $token_category = $upper; // TODO: what is it for a statement? 253 continue 2; 254 } 255 if ($token_category === 'IDX_HINT') { 256 $cur_hint = (count($parseInfo['hints']) - 1); 257 $parseInfo['hints'][$cur_hint]['hint_type'] .= " " . $upper; 258 continue 2; 259 } 260 break; 261 262 case 'USING': 263 case 'ON': 264 $parseInfo['ref_type'] = $upper; 265 $parseInfo['ref_expr'] = ""; 266 267 case 'CROSS': 268 case 'INNER': 269 case 'OUTER': 270 case 'NATURAL': 271 $parseInfo['token_count']++; 272 break; 273 274 case 'FOR': 275 $parseInfo['token_count']++; 276 $skip_next = true; 277 break; 278 279 case 'STRAIGHT_JOIN': 280 $parseInfo['next_join_type'] = "STRAIGHT_JOIN"; 281 if ($parseInfo['subquery']) { 282 $parseInfo['sub_tree'] = $this->parse($this->removeParenthesisFromStart($parseInfo['subquery'])); 283 $parseInfo['expression'] = $parseInfo['subquery']; 284 } 285 286 $expr[] = $this->processFromExpression($parseInfo); 287 $parseInfo = $this->initParseInfo($parseInfo); 288 break; 289 290 case ',': 291 $parseInfo['next_join_type'] = 'CROSS'; 292 293 case 'JOIN': 294 if ($parseInfo['subquery']) { 295 $parseInfo['sub_tree'] = $this->parse($this->removeParenthesisFromStart($parseInfo['subquery'])); 296 $parseInfo['expression'] = $parseInfo['subquery']; 297 } 298 299 $expr[] = $this->processFromExpression($parseInfo); 300 $parseInfo = $this->initParseInfo($parseInfo); 301 break; 302 303 default: 304 // TODO: enhance it, so we can have base_expr to calculate the position of the keywords 305 // build a subtree under "hints" 306 if ($token_category === 'IDX_HINT') { 307 $token_category = ''; 308 $cur_hint = (count($parseInfo['hints']) - 1); 309 $parseInfo['hints'][$cur_hint]['hint_list'] = $token; 310 break; 311 } 312 313 if ($parseInfo['token_count'] === 0) { 314 if ($parseInfo['table'] === "") { 315 $parseInfo['table'] = $token; 316 $parseInfo['no_quotes'] = $this->revokeQuotation($token); 317 } 318 } else if ($parseInfo['token_count'] === 1) { 319 $parseInfo['alias'] = array('as' => false, 'name' => trim($token), 320 'no_quotes' => $this->revokeQuotation($token), 321 'base_expr' => trim($token)); 322 } 323 $parseInfo['token_count']++; 324 break; 325 } 326 $i++; 327 } 328 329 $expr[] = $this->processFromExpression($parseInfo); 330 return $expr; 331 } 332 333} 334 335?> 336