1<?php 2 3/** 4 * `JOIN` 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 * `JOIN` keyword parser. 16 * 17 * @category Keywords 18 * 19 * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+ 20 */ 21class JoinKeyword extends Component 22{ 23 /** 24 * Types of join. 25 * 26 * @var array 27 */ 28 public static $JOINS = array( 29 'CROSS JOIN' => 'CROSS', 30 'FULL JOIN' => 'FULL', 31 'FULL OUTER JOIN' => 'FULL', 32 'INNER JOIN' => 'INNER', 33 'JOIN' => 'JOIN', 34 'LEFT JOIN' => 'LEFT', 35 'LEFT OUTER JOIN' => 'LEFT', 36 'RIGHT JOIN' => 'RIGHT', 37 'RIGHT OUTER JOIN' => 'RIGHT', 38 'NATURAL JOIN' => 'NATURAL', 39 'NATURAL LEFT JOIN' => 'NATURAL LEFT', 40 'NATURAL RIGHT JOIN' => 'NATURAL RIGHT', 41 'NATURAL LEFT OUTER JOIN' => 'NATURAL LEFT OUTER', 42 'NATURAL RIGHT OUTER JOIN' => 'NATURAL RIGHT OUTER', 43 'STRAIGHT_JOIN' => 'STRAIGHT' 44 ); 45 46 /** 47 * Type of this join. 48 * 49 * @see static::$JOINS 50 * 51 * @var string 52 */ 53 public $type; 54 55 /** 56 * Join expression. 57 * 58 * @var Expression 59 */ 60 public $expr; 61 62 /** 63 * Join conditions. 64 * 65 * @var Condition[] 66 */ 67 public $on; 68 69 /** 70 * Columns in Using clause. 71 * 72 * @var ArrayObj 73 */ 74 public $using; 75 76 /** 77 * Constructor. 78 * 79 * @param string $type Join type 80 * @param Expression $expr join expression 81 * @param Condition[] $on join conditions 82 * @param ArrayObj $using columns joined 83 * 84 * @see JoinKeyword::$JOINS 85 */ 86 public function __construct($type = null, $expr = null, $on = null, $using = null) 87 { 88 $this->type = $type; 89 $this->expr = $expr; 90 $this->on = $on; 91 $this->using = $using; 92 } 93 94 /** 95 * @param Parser $parser the parser that serves as context 96 * @param TokensList $list the list of tokens that are being parsed 97 * @param array $options parameters for parsing 98 * 99 * @return JoinKeyword[] 100 */ 101 public static function parse(Parser $parser, TokensList $list, array $options = array()) 102 { 103 $ret = array(); 104 105 $expr = new self(); 106 107 /** 108 * The state of the parser. 109 * 110 * Below are the states of the parser. 111 * 112 * 0 -----------------------[ JOIN ]----------------------> 1 113 * 114 * 1 -----------------------[ expr ]----------------------> 2 115 * 116 * 2 ------------------------[ ON ]-----------------------> 3 117 * 2 -----------------------[ USING ]---------------------> 4 118 * 119 * 3 --------------------[ conditions ]-------------------> 0 120 * 121 * 4 ----------------------[ columns ]--------------------> 0 122 * 123 * @var int 124 */ 125 $state = 0; 126 127 // By design, the parser will parse first token after the keyword. 128 // In this case, the keyword must be analyzed too, in order to determine 129 // the type of this join. 130 if ($list->idx > 0) { 131 --$list->idx; 132 } 133 134 for (; $list->idx < $list->count; ++$list->idx) { 135 /** 136 * Token parsed at this moment. 137 * 138 * @var Token 139 */ 140 $token = $list->tokens[$list->idx]; 141 142 // End of statement. 143 if ($token->type === Token::TYPE_DELIMITER) { 144 break; 145 } 146 147 // Skipping whitespaces and comments. 148 if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) { 149 continue; 150 } 151 152 if ($state === 0) { 153 if (($token->type === Token::TYPE_KEYWORD) 154 && ! empty(static::$JOINS[$token->keyword]) 155 ) { 156 $expr->type = static::$JOINS[$token->keyword]; 157 $state = 1; 158 } else { 159 break; 160 } 161 } elseif ($state === 1) { 162 $expr->expr = Expression::parse($parser, $list, array('field' => 'table')); 163 $state = 2; 164 } elseif ($state === 2) { 165 if ($token->type === Token::TYPE_KEYWORD) { 166 switch ($token->keyword) { 167 case 'ON': 168 $state = 3; 169 break; 170 case 'USING': 171 $state = 4; 172 break; 173 default: 174 if (! empty(static::$JOINS[$token->keyword]) 175 ) { 176 $ret[] = $expr; 177 $expr = new self(); 178 $expr->type = static::$JOINS[$token->keyword]; 179 $state = 1; 180 } else { 181 /* Next clause is starting */ 182 break 2; 183 } 184 break; 185 } 186 } 187 } elseif ($state === 3) { 188 $expr->on = Condition::parse($parser, $list); 189 $ret[] = $expr; 190 $expr = new self(); 191 $state = 0; 192 } elseif ($state === 4) { 193 $expr->using = ArrayObj::parse($parser, $list); 194 $ret[] = $expr; 195 $expr = new self(); 196 $state = 0; 197 } 198 } 199 200 if (! empty($expr->type)) { 201 $ret[] = $expr; 202 } 203 204 --$list->idx; 205 206 return $ret; 207 } 208 209 /** 210 * @param JoinKeyword[] $component the component to be built 211 * @param array $options parameters for building 212 * 213 * @return string 214 */ 215 public static function build($component, array $options = array()) 216 { 217 $ret = array(); 218 foreach ($component as $c) { 219 $ret[] = array_search($c->type, static::$JOINS) . ' ' . $c->expr 220 . (! empty($c->on) 221 ? ' ON ' . Condition::build($c->on) : '') 222 . (! empty($c->using) 223 ? ' USING ' . ArrayObj::build($c->using) : ''); 224 } 225 226 return implode(' ', $ret); 227 } 228} 229