1<?php 2 3/** 4 * Parses an Index hint. 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 * Parses an Index hint. 16 * 17 * @category Components 18 * 19 * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+ 20 */ 21class IndexHint extends Component 22{ 23 /** 24 * The type of hint (USE/FORCE/IGNORE) 25 * 26 * @var string 27 */ 28 public $type; 29 30 /** 31 * What the hint is for (INDEX/KEY) 32 * 33 * @var string 34 */ 35 public $indexOrKey; 36 37 /** 38 * The clause for which this hint is (JOIN/ORDER BY/GROUP BY) 39 * 40 * @var string 41 */ 42 public $for; 43 44 /** 45 * List of indexes in this hint 46 * 47 * @var array 48 */ 49 public $indexes = array(); 50 51 /** 52 * Constructor. 53 * 54 * @param string $type the type of hint (USE/FORCE/IGNORE) 55 * @param string $indexOrKey What the hint is for (INDEX/KEY) 56 * @param string $for the clause for which this hint is (JOIN/ORDER BY/GROUP BY) 57 * @param string $indexes List of indexes in this hint 58 */ 59 public function __construct(string $type = null, string $indexOrKey = null, string $for = null, array $indexes = array()) 60 { 61 $this->type = $type; 62 $this->indexOrKey = $indexOrKey; 63 $this->for = $for; 64 $this->indexes = $indexes; 65 } 66 67 /** 68 * @param Parser $parser the parser that serves as context 69 * @param TokensList $list the list of tokens that are being parsed 70 * @param array $options parameters for parsing 71 * 72 * @return IndexHint|Component[] 73 */ 74 public static function parse(Parser $parser, TokensList $list, array $options = array()) 75 { 76 $ret = array(); 77 $expr = new self(); 78 $expr->type = isset($options['type']) ? $options['type'] : null; 79 /** 80 * The state of the parser. 81 * 82 * Below are the states of the parser. 83 * 0 ----------------- [ USE/IGNORE/FORCE ]-----------------> 1 84 * 1 -------------------- [ INDEX/KEY ] --------------------> 2 85 * 2 ----------------------- [ FOR ] -----------------------> 3 86 * 2 -------------------- [ expr_list ] --------------------> 0 87 * 3 -------------- [ JOIN/GROUP BY/ORDER BY ] -------------> 4 88 * 4 -------------------- [ expr_list ] --------------------> 0 89 * @var int 90 */ 91 $state = 0; 92 93 // By design, the parser will parse first token after the keyword. So, the keyword 94 // must be analyzed too, in order to determine the type of this index hint. 95 if ($list->idx > 0) { 96 --$list->idx; 97 } 98 for (; $list->idx < $list->count; ++$list->idx) { 99 /** 100 * Token parsed at this moment. 101 * 102 * @var Token 103 */ 104 $token = $list->tokens[$list->idx]; 105 106 // End of statement. 107 if ($token->type === Token::TYPE_DELIMITER) { 108 break; 109 } 110 // Skipping whitespaces and comments. 111 if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) { 112 continue; 113 } 114 115 switch ($state) { 116 case 0: 117 if ($token->type === Token::TYPE_KEYWORD) { 118 if ($token->keyword === 'USE' || $token->keyword === 'IGNORE' || $token->keyword === 'FORCE') { 119 $expr->type = $token->keyword; 120 $state = 1; 121 } else { 122 break 2; 123 } 124 } 125 break; 126 case 1: 127 if ($token->type === Token::TYPE_KEYWORD) { 128 if ($token->keyword === 'INDEX' || $token->keyword === 'KEY') { 129 $expr->indexOrKey = $token->keyword; 130 } else { 131 $parser->error('Unexpected keyword.', $token); 132 } 133 $state = 2; 134 } else { 135 // we expect the token to be a keyword 136 $parser->error('Unexpected token.', $token); 137 } 138 break; 139 case 2: 140 if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'FOR') { 141 $state = 3; 142 } else { 143 $expr->indexes = ExpressionArray::parse($parser, $list); 144 $state = 0; 145 $ret[] = $expr; 146 $expr = new self(); 147 } 148 break; 149 case 3: 150 if ($token->type === Token::TYPE_KEYWORD) { 151 if ($token->keyword === 'JOIN' || $token->keyword === 'GROUP BY' || $token->keyword === 'ORDER BY') { 152 $expr->for = $token->keyword; 153 } else { 154 $parser->error('Unexpected keyword.', $token); 155 } 156 $state = 4; 157 } else { 158 // we expect the token to be a keyword 159 $parser->error('Unexpected token.', $token); 160 } 161 break; 162 case 4: 163 $expr->indexes = ExpressionArray::parse($parser, $list); 164 $state = 0; 165 $ret[] = $expr; 166 $expr = new self(); 167 break; 168 } 169 } 170 --$list->idx; 171 172 return $ret; 173 } 174 175 /** 176 * @param ArrayObj|ArrayObj[] $component the component to be built 177 * @param array $options parameters for building 178 * 179 * @return string 180 */ 181 public static function build($component, array $options = array()) 182 { 183 if (is_array($component)) { 184 return implode(' ', $component); 185 } 186 187 $ret = $component->type . ' ' . $component->indexOrKey . ' '; 188 if ($component->for !== null) { 189 $ret .= 'FOR ' . $component->for . ' '; 190 } 191 return $ret . ExpressionArray::build($component->indexes); 192 } 193} 194