/** * Function called before the token is processed. * * Skips the `TABLE` keyword after `RENAME`. * * @param Parser $parser The instance that requests parsing. * @param TokensList $list The list of tokens to be parsed. * @param Token $token The token that is being parsed. * * @return void */ public function before(Parser $parser, TokensList $list, Token $token) { if ($token->type === Token::TYPE_KEYWORD && $token->value === 'RENAME') { // Checking if it is the beginning of the query. $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'TABLE'); } }
public function testAdd() { $list = new TokensList(); foreach ($this->tokens as $token) { $list->add($token); } $this->assertEquals($this->getList(), $list); }
/** * @return string */ public function build() { $fields = ''; if (!empty($this->fields)) { if (is_array($this->fields)) { $fields = CreateDefinition::build($this->fields) . ' '; } elseif ($this->fields instanceof ArrayObj) { $fields = ArrayObj::build($this->fields); } } if ($this->options->has('DATABASE')) { return 'CREATE ' . OptionsArray::build($this->options) . ' ' . Expression::build($this->name) . ' ' . OptionsArray::build($this->entityOptions); } elseif ($this->options->has('TABLE')) { $partition = ''; if (!empty($this->partitionBy)) { $partition .= "\nPARTITION BY " . $this->partitionBy; } if (!empty($this->partitionsNum)) { $partition .= "\nPARTITIONS " . $this->partitionsNum; } if (!empty($this->subpartitionBy)) { $partition .= "\nSUBPARTITION BY " . $this->subpartitionBy; } if (!empty($this->subpartitionsNum)) { $partition .= "\nSUBPARTITIONS " . $this->subpartitionsNum; } if (!empty($this->partitions)) { $partition .= "\n" . PartitionDefinition::build($this->partitions); } return 'CREATE ' . OptionsArray::build($this->options) . ' ' . Expression::build($this->name) . ' ' . $fields . OptionsArray::build($this->entityOptions) . $partition; } elseif ($this->options->has('VIEW')) { return 'CREATE ' . OptionsArray::build($this->options) . ' ' . Expression::build($this->name) . ' ' . $fields . ' AS ' . TokensList::build($this->body) . ' ' . OptionsArray::build($this->entityOptions); } elseif ($this->options->has('TRIGGER')) { return 'CREATE ' . OptionsArray::build($this->options) . ' ' . Expression::build($this->name) . ' ' . OptionsArray::build($this->entityOptions) . ' ' . 'ON ' . Expression::build($this->table) . ' ' . 'FOR EACH ROW ' . TokensList::build($this->body); } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) { $tmp = ''; if ($this->options->has('FUNCTION')) { $tmp = 'RETURNS ' . DataType::build($this->return); } return 'CREATE ' . OptionsArray::build($this->options) . ' ' . Expression::build($this->name) . ' ' . ParameterDefinition::build($this->parameters) . ' ' . $tmp . ' ' . TokensList::build($this->body); } else { return 'CREATE ' . OptionsArray::build($this->options) . ' ' . Expression::build($this->name) . ' ' . TokensList::build($this->body); } return ''; }
/** * @param AlterOperation $component The component to be built. * @param array $options Parameters for building. * * @return string */ public static function build($component, array $options = array()) { $ret = $component->options . ' '; if (isset($component->field) && $component->field !== '') { $ret .= $component->field . ' '; } $ret .= TokensList::build($component->unknown); return $ret; }
/** * @param AlterOperation $component The component to be built. * * @return string */ public static function build($component) { $ret = OptionsArray::build($component->options) . ' '; if (!empty($component->field)) { $ret .= Expression::build($component->field) . ' '; } $ret .= TokensList::build($component->unknown); return $ret; }
/** * Jump to the end of the delimiter. * * @param Parser $parser The instance that requests parsing. * @param TokensList $list The list of tokens to be parsed. * @param Token $token The token that is being parsed. * * @return void */ public function after(Parser $parser, TokensList $list, Token $token) { $list->getNextOfType(Token::TYPE_DELIMITER); --$list->idx; }
/** * @param Parser $parser The parser that serves as context. * @param TokensList $list The list of tokens that are being parsed. * @param array $options Parameters for parsing. * * @return PartitionDefinition */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = new PartitionDefinition(); /** * The state of the parser. * * Below are the states of the parser. * * 0 -------------[ PARTITION | SUBPARTITION ]------------> 1 * * 1 -----------------------[ name ]----------------------> 2 * * 2 ----------------------[ VALUES ]---------------------> 3 * * 3 ---------------------[ LESS THAN ]-------------------> 4 * 3 ------------------------[ IN ]-----------------------> 4 * * 4 -----------------------[ expr ]----------------------> 5 * * 5 ----------------------[ options ]--------------------> 6 * * 6 ------------------[ subpartitions ]------------------> (END) * * @var int $state */ $state = 0; for (; $list->idx < $list->count; ++$list->idx) { /** * Token parsed at this moment. * * @var Token $token */ $token = $list->tokens[$list->idx]; // End of statement. if ($token->type === Token::TYPE_DELIMITER) { break; } // Skipping whitespaces and comments. if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) { continue; } if ($state === 0) { $ret->isSubpartition = $token->type === Token::TYPE_KEYWORD && $token->value === 'SUBPARTITION'; $state = 1; } elseif ($state === 1) { $ret->name = $token->value; // Looking ahead for a 'VALUES' keyword. $idx = $list->idx; $list->getNext(); $nextToken = $list->getNext(); $list->idx = $idx; $state = $nextToken->type === Token::TYPE_KEYWORD && $nextToken->value === 'VALUES' ? 2 : 5; } elseif ($state === 2) { $state = 3; } elseif ($state === 3) { $ret->type = $token->value; $state = 4; } elseif ($state === 4) { if ($token->value === 'MAXVALUE') { $ret->expr = $token->value; } else { $ret->expr = Expression::parse($parser, $list, array('parenthesesDelimited' => true, 'breakOnAlias' => true)); } $state = 5; } elseif ($state === 5) { $ret->options = OptionsArray::parse($parser, $list, static::$OPTIONS); $state = 6; } elseif ($state === 6) { if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { $ret->subpartitions = ArrayObj::parse($parser, $list, array('type' => 'SqlParser\\Components\\PartitionDefinition')); ++$list->idx; } break; } } --$list->idx; return $ret; }
public static function replaceTokens($list, array $find, array $replace) { /** * Whether the first parameter is a list. * * @var bool */ $isList = $list instanceof TokensList; // Parsing the tokens. if (!$isList) { $list = Lexer::getTokens($list); } /** * The list to be returned. * * @var array */ $newList = array(); /** * The length of the find pattern is calculated only once. * * @var int */ $findCount = count($find); /** * The starting index of the pattern. * * @var int */ $i = 0; while ($i < $list->count) { // A sequence may not start with a comment. if ($list->tokens[$i]->type === Token::TYPE_COMMENT) { $newList[] = $list->tokens[$i]; ++$i; continue; } /** * The index used to parse `$list->tokens`. * * This index might be running faster than `$k` because some tokens * are skipped. * * @var int */ $j = $i; /** * The index used to parse `$find`. * * This index might be running slower than `$j` because some tokens * are skipped. * * @var int */ $k = 0; // Checking if the next tokens match the pattern described. while ($j < $list->count && $k < $findCount) { // Comments are being skipped. if ($list->tokens[$j]->type === Token::TYPE_COMMENT) { ++$j; } if (!static::match($list->tokens[$j], $find[$k])) { // This token does not match the pattern. break; } // Going to next token and segment of find pattern. ++$j; ++$k; } // Checking if the sequence was found. if ($k === $findCount) { // Inserting new tokens. foreach ($replace as $token) { $newList[] = $token; } // Skipping next `$findCount` tokens. $i = $j; } else { // Adding the same token. $newList[] = $list->tokens[$i]; ++$i; } } return $isList ? new TokensList($newList) : TokensList::build($newList); }
/** * Parses the statements defined by the tokens list. * * @param Parser $parser The instance that requests parsing. * @param TokensList $list The list of tokens to be parsed. * * @return void */ public function parse(Parser $parser, TokensList $list) { /** * Array containing all list of clauses parsed. * This is used to check for duplicates. * * @var array $parsedClauses */ $parsedClauses = array(); // This may be corrected by the parser. $this->first = $list->idx; /** * Whether options were parsed or not. * For statements that do not have any options this is set to `true` by * default. * * @var bool $parsedOptions */ $parsedOptions = empty(static::$OPTIONS); for (; $list->idx < $list->count; ++$list->idx) { /** * Token parsed at this moment. * * @var Token $token */ $token = $list->tokens[$list->idx]; // End of statement. if ($token->type === Token::TYPE_DELIMITER) { break; } // Checking if this closing bracket is the pair for a bracket // outside the statement. if ($token->value === ')' && $parser->brackets > 0) { --$parser->brackets; continue; } // Only keywords are relevant here. Other parts of the query are // processed in the functions below. if ($token->type !== Token::TYPE_KEYWORD) { if ($token->type !== TOKEN::TYPE_COMMENT && $token->type !== Token::TYPE_WHITESPACE) { $parser->error(__('Unexpected token.'), $token); } continue; } // Unions are parsed by the parser because they represent more than // one statement. if ($token->value === 'UNION' || $token->value === 'UNION ALL' || $token->value === 'UNION DISTINCT') { break; } $lastIdx = $list->idx; // ON DUPLICATE KEY UPDATE ... // has to be parsed in parent statement (INSERT or REPLACE) // so look for it and break if (get_class($this) === 'SqlParser\\Statements\\SelectStatement' && $token->value === 'ON') { ++$list->idx; // Skip ON // look for ON DUPLICATE KEY UPDATE $first = $list->getNextOfType(Token::TYPE_KEYWORD); $second = $list->getNextOfType(Token::TYPE_KEYWORD); $third = $list->getNextOfType(Token::TYPE_KEYWORD); if ($first && $second && $third && $first->value === 'DUPLICATE' && $second->value === 'KEY' && $third->value === 'UPDATE') { $list->idx = $lastIdx; break; } } $list->idx = $lastIdx; /** * The name of the class that is used for parsing. * * @var Component $class */ $class = null; /** * The name of the field where the result of the parsing is stored. * * @var string $field */ $field = null; /** * Parser's options. * * @var array $options */ $options = array(); // Looking for duplicated clauses. if (!empty(Parser::$KEYWORD_PARSERS[$token->value]) || !empty(Parser::$STATEMENT_PARSERS[$token->value])) { if (!empty($parsedClauses[$token->value])) { $parser->error(__('This type of clause was previously parsed.'), $token); break; } $parsedClauses[$token->value] = true; } // Checking if this is the beginning of a clause. if (!empty(Parser::$KEYWORD_PARSERS[$token->value])) { $class = Parser::$KEYWORD_PARSERS[$token->value]['class']; $field = Parser::$KEYWORD_PARSERS[$token->value]['field']; if (!empty(Parser::$KEYWORD_PARSERS[$token->value]['options'])) { $options = Parser::$KEYWORD_PARSERS[$token->value]['options']; } } // Checking if this is the beginning of the statement. if (!empty(Parser::$STATEMENT_PARSERS[$token->value])) { if (!empty(static::$CLAUSES) && empty(static::$CLAUSES[$token->value])) { // Some keywords (e.g. `SET`) may be the beginning of a // statement and a clause. // If such keyword was found and it cannot be a clause of // this statement it means it is a new statement, but no // delimiter was found between them. $parser->error(__('A new statement was found, but no delimiter between it and the previous one.'), $token); break; } if (!$parsedOptions) { if (empty(static::$OPTIONS[$token->value])) { // Skipping keyword because if it is not a option. ++$list->idx; } $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS); $parsedOptions = true; } } elseif ($class === null) { // There is no parser for this keyword and isn't the beginning // of a statement (so no options) either. $parser->error(__('Unrecognized keyword.'), $token); continue; } $this->before($parser, $list, $token); // Parsing this keyword. if ($class !== null) { ++$list->idx; // Skipping keyword or last option. $this->{$field} = $class::parse($parser, $list, $options); } $this->after($parser, $list, $token); } // This may be corrected by the parser. $this->last = --$list->idx; // Go back to last used token. }
public function testGetNextOfTypeAndValue() { $list = new TokensList($this->tokens); $this->assertEquals($this->tokens[0], $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'SELECT')); $this->assertEquals(null, $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'SELECT')); }