/** * Function called after the token was processed. * * Parses the additional options from the end. * * @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) { // [some options] is going to be parsed first. // // There is a parser specified in `Parser::$KEYWORD_PARSERS` // which parses the name of the tables. // // Finally, we parse here [some more options] and that's all. ++$list->idx; $this->options->merge(OptionsArray::parse($parser, $list, static::$OPTIONS)); }
/** * @return string */ public function build() { $ret = OptionsArray::build($this->options); if ($this->type === TransactionStatement::TYPE_BEGIN) { foreach ($this->statements as $statement) { $ret .= ';' . $statement->build(); } $ret .= ';' . $this->end->build(); } return $ret; }
/** * @param FieldDefinition|FieldDefinition[] $component The component to be built. * * @return string */ public static function build($component) { if (is_array($component)) { $ret = array(); foreach ($component as $c) { $ret[] = static::build($c); } return "(\n" . implode(",\n", $ret) . "\n)"; } else { $tmp = ''; if ($component->isConstraint) { $tmp .= 'CONSTRAINT '; } if (!empty($component->name)) { $tmp .= Context::escape($component->name) . ' '; } if (!empty($component->type)) { $tmp .= DataType::build($component->type) . ' '; } if (!empty($component->key)) { $tmp .= Key::build($component->key) . ' '; } if (!empty($component->references)) { $tmp .= 'REFERENCES ' . Reference::build($component->references) . ' '; } $tmp .= OptionsArray::build($component->options); return trim($tmp); } }
/** * @param Key $component The component to be built. * * @return string */ public static function build($component) { $ret = $component->type . ' '; if (!empty($component->name)) { $ret .= Context::escape($component->name) . ' '; } $ret .= '(' . implode(',', Context::escape($component->columns)) . ')'; $ret .= OptionsArray::build($component->options); return trim($ret); }
/** * @return string */ public function build() { return 'SET ' . OptionsArray::build($this->options) . ' ' . SetOperation::build($this->set); }
/** * @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) { ++$list->idx; // Skipping `CREATE`. // Parsing options. $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS); ++$list->idx; // Skipping last option. // Parsing the field name. $this->name = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true)); if (!isset($this->name) || $this->name === '') { $parser->error(__('The name of the entity was expected.'), $list->tokens[$list->idx]); } else { ++$list->idx; // Skipping field. } if ($this->options->has('DATABASE')) { $this->entityOptions = OptionsArray::parse($parser, $list, static::$DB_OPTIONS); } elseif ($this->options->has('TABLE')) { $this->fields = CreateDefinition::parse($parser, $list); if (empty($this->fields)) { $parser->error(__('At least one column definition was expected.'), $list->tokens[$list->idx]); } ++$list->idx; $this->entityOptions = OptionsArray::parse($parser, $list, static::$TABLE_OPTIONS); /** * The field that is being filled (`partitionBy` or * `subpartitionBy`). * * @var string $field */ $field = null; /** * The number of brackets. `false` means no bracket was found * previously. At least one bracket is required to validate the * expression. * * @var int|bool $brackets */ $brackets = false; /* * Handles partitions. */ 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 comments. if ($token->type === Token::TYPE_COMMENT) { continue; } if ($token->type === Token::TYPE_KEYWORD && $token->value === 'PARTITION BY') { $field = 'partitionBy'; $brackets = false; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'SUBPARTITION BY') { $field = 'subpartitionBy'; $brackets = false; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'PARTITIONS') { $token = $list->getNextOfType(Token::TYPE_NUMBER); --$list->idx; // `getNextOfType` also advances one position. $this->partitionsNum = $token->value; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'SUBPARTITIONS') { $token = $list->getNextOfType(Token::TYPE_NUMBER); --$list->idx; // `getNextOfType` also advances one position. $this->subpartitionsNum = $token->value; } elseif (!empty($field)) { /* * Handling the content of `PARTITION BY` and `SUBPARTITION BY`. */ // Counting brackets. if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { // This is used instead of `++$brackets` because, // initially, `$brackets` is `false` cannot be // incremented. $brackets = $brackets + 1; } elseif ($token->type === Token::TYPE_OPERATOR && $token->value === ')') { --$brackets; } // Building the expression used for partitioning. $this->{$field} .= $token->type === Token::TYPE_WHITESPACE ? ' ' : $token->token; // Last bracket was read, the expression ended. // Comparing with `0` and not `false`, because `false` means // that no bracket was found and at least one must is // required. if ($brackets === 0) { $this->{$field} = trim($this->{$field}); $field = null; } } elseif ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { if (!empty($this->partitionBy)) { $this->partitions = ArrayObj::parse($parser, $list, array('type' => 'SqlParser\\Components\\PartitionDefinition')); } break; } } } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) { $this->parameters = ParameterDefinition::parse($parser, $list); if ($this->options->has('FUNCTION')) { $token = $list->getNextOfType(Token::TYPE_KEYWORD); if ($token->value !== 'RETURNS') { $parser->error(__('A "RETURNS" keyword was expected.'), $token); } else { ++$list->idx; $this->return = DataType::parse($parser, $list); } } ++$list->idx; $this->entityOptions = OptionsArray::parse($parser, $list, static::$FUNC_OPTIONS); ++$list->idx; for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; $this->body[] = $token; } } elseif ($this->options->has('VIEW')) { $token = $list->getNext(); // Skipping whitespaces and comments. // Parsing columns list. if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { --$list->idx; // getNext() also goes forward one field. $this->fields = ArrayObj::parse($parser, $list); ++$list->idx; // Skipping last token from the array. $list->getNext(); } // Parsing the `AS` keyword. for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; if ($token->type === Token::TYPE_DELIMITER) { break; } $this->body[] = $token; } } elseif ($this->options->has('TRIGGER')) { // Parsing the time and the event. $this->entityOptions = OptionsArray::parse($parser, $list, static::$TRIGGER_OPTIONS); ++$list->idx; $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON'); ++$list->idx; // Skipping `ON`. // Parsing the name of the table. $this->table = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true)); ++$list->idx; $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW'); ++$list->idx; // Skipping `FOR EACH ROW`. for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; $this->body[] = $token; } } else { for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; if ($token->type === Token::TYPE_DELIMITER) { break; } $this->body[] = $token; } } }
/** * 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) { /** * Whether the beginning of this statement was previously parsed. * * This is used to delimit statements that don't use the usual * delimiters. * * @var bool */ $parsedBeginning = false; // 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; } // 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') { break; } /** * The name of the class that is used for parsing. * @var string $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(); 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']; } } if (!empty(Parser::$STATEMENT_PARSERS[$token->value])) { if ($parsedBeginning) { // New statement has started. We let the parser construct a // new statement and parse that one $parser->error(__('A new statement was found, but no delimiter between them.'), $token); break; } $parsedBeginning = true; 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 testBuild() { $component = new OptionsArray(array('ALL', 'SQL_CALC_FOUND_ROWS', array('name' => 'MAX_STATEMENT_TIME', 'value' => '42', 'equals' => true))); $this->assertEquals(OptionsArray::build($component), 'ALL SQL_CALC_FOUND_ROWS MAX_STATEMENT_TIME=42'); }
/** * @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 Key[] */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = new Key(); /** * Last parsed column. * * @var array */ $lastColumn = array(); /** * The state of the parser. * * Below are the states of the parser. * * 0 ----------------------[ type ]-----------------------> 1 * * 1 ----------------------[ name ]-----------------------> 1 * 1 ---------------------[ columns ]---------------------> 2 * * 2 ---------------------[ options ]---------------------> 3 * * @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->type = $token->value; $state = 1; } elseif ($state === 1) { if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { $state = 2; } else { $ret->name = $token->value; } } elseif ($state === 2) { if ($token->type === Token::TYPE_OPERATOR) { if ($token->value === '(') { $state = 3; } elseif ($token->value === ',' || $token->value === ')') { $state = $token->value === ',' ? 2 : 4; if (!empty($lastColumn)) { $ret->columns[] = $lastColumn; $lastColumn = array(); } } } else { $lastColumn['name'] = $token->value; } } elseif ($state === 3) { if ($token->type === Token::TYPE_OPERATOR && $token->value === ')') { $state = 2; } else { $lastColumn['length'] = $token->value; } } elseif ($state === 4) { $ret->options = OptionsArray::parse($parser, $list, static::$KEY_OPTIONS); ++$list->idx; break; } } --$list->idx; return $ret; }
/** * @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) { ++$list->idx; // Skipping `INSERT`. // parse any options if provided $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS); ++$list->idx; $token = $list->tokens[$list->idx]; /** * The state of the parser. * * Below are the states of the parser. * * 0 ---------------------------------[ INTO ]----------------------------------> 1 * * 1 -------------------------[ VALUES/VALUE/SET/SELECT ]-----------------------> 2 * * 2 -------------------------[ ON DUPLICATE KEY UPDATE ]-----------------------> 3 * * @var int $state */ $state = 0; /** * For keeping track of semi-states on encountering * ON DUPLICATE KEY UPDATE ... * */ $miniState = 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) { if ($token->type === Token::TYPE_KEYWORD && $token->value !== 'INTO') { $parser->error(__('Unexpected keyword.'), $token); break; } else { ++$list->idx; $this->into = IntoKeyword::parse($parser, $list); } $state = 1; } elseif ($state === 1) { if ($token->type === Token::TYPE_KEYWORD) { if ($token->value === 'VALUE' || $token->value === 'VALUES') { ++$list->idx; // skip VALUES $this->values = Array2d::parse($parser, $list); } elseif ($token->value === 'SET') { ++$list->idx; // skip SET $this->set = SetOperation::parse($parser, $list); } elseif ($token->value === 'SELECT') { $this->select = new SelectStatement($parser, $list); } else { $parser->error(__('Unexpected keyword.'), $token); break; } $state = 2; $miniState = 1; } else { $parser->error(__('Unexpected token.'), $token); break; } } elseif ($state == 2) { $lastCount = $miniState; if ($miniState === 1 && $token->value === 'ON') { $miniState++; } elseif ($miniState === 2 && $token->value === 'DUPLICATE') { $miniState++; } elseif ($miniState === 3 && $token->value === 'KEY') { $miniState++; } elseif ($miniState === 4 && $token->value === 'UPDATE') { $miniState++; } if ($lastCount === $miniState) { $parser->error(__('Unexpected token.'), $token); break; } if ($miniState === 5) { ++$list->idx; $this->onDuplicateSet = SetOperation::parse($parser, $list); $state = 3; } } } --$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 AlterOperation */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = new AlterOperation(); /** * Counts brackets. * * @var int $brackets */ $brackets = 0; /** * The state of the parser. * * Below are the states of the parser. * * 0 ---------------------[ options ]---------------------> 1 * * 1 ----------------------[ field ]----------------------> 2 * * 2 -------------------------[ , ]-----------------------> 0 * * @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 comments. if ($token->type === Token::TYPE_COMMENT) { continue; } // Skipping whitespaces. if ($token->type === Token::TYPE_WHITESPACE) { if ($state === 2) { // When parsing the unknown part, the whitespaces are // included to not break anything. $ret->unknown[] = $token; } continue; } if ($state === 0) { $ret->options = OptionsArray::parse($parser, $list, $options); if ($ret->options->has('AS')) { for (; $list->idx < $list->count; ++$list->idx) { if ($list->tokens[$list->idx]->type === Token::TYPE_DELIMITER) { break; } $ret->unknown[] = $list->tokens[$list->idx]; } break; } $state = 1; } elseif ($state === 1) { $ret->field = Expression::parse($parser, $list, array('breakOnAlias' => true, 'parseField' => 'column')); if ($ret->field === null) { // No field was read. We go back one token so the next // iteration will parse the same token, but in state 2. --$list->idx; } $state = 2; } elseif ($state === 2) { if ($token->type === Token::TYPE_OPERATOR) { if ($token->value === '(') { ++$brackets; } elseif ($token->value === ')') { --$brackets; } elseif ($token->value === ',' && $brackets === 0) { break; } } elseif (!empty(Parser::$STATEMENT_PARSERS[$token->value])) { // We have reached the end of ALTER operation and suddenly found // a start to new statement, but have not find a delimiter between them if (!($token->value == 'SET' && $list->tokens[$list->idx - 1]->value == 'CHARACTER')) { $parser->error(__('A new statement was found, but no delimiter between it and the previous one.'), $token); break; } } $ret->unknown[] = $token; } } if ($ret->options->isEmpty()) { $parser->error(__('Unrecognized alter operation.'), $list->tokens[$list->idx]); } --$list->idx; return $ret; }
/** * @param DataType $component The component to be built. * * @return string */ public static function build($component) { $tmp = ''; if (!empty($component->parameters)) { $tmp = '(' . implode(',', $component->parameters) . ')'; } return trim(strtolower($component->name) . $tmp . ' ' . OptionsArray::build($component->options)); }
/** * @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) { ++$list->idx; // Skipping `REPLACE`. // parse any options if provided $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS); ++$list->idx; $token = $list->tokens[$list->idx]; /** * The state of the parser. * * Below are the states of the parser. * * 0 ---------------------------------[ INTO ]----------------------------------> 1 * * 1 -------------------------[ VALUES/VALUE/SET/SELECT ]-----------------------> 2 * * @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) { if ($token->type === Token::TYPE_KEYWORD && $token->value !== 'INTO') { $parser->error(__('Unexpected keyword.'), $token); break; } else { ++$list->idx; $this->into = IntoKeyword::parse($parser, $list, array('fromReplace' => true)); } $state = 1; } elseif ($state === 1) { if ($token->type === Token::TYPE_KEYWORD) { if ($token->value === 'VALUE' || $token->value === 'VALUES') { ++$list->idx; // skip VALUES $this->values = Array2d::parse($parser, $list); } elseif ($token->value === 'SET') { ++$list->idx; // skip SET $this->set = SetOperation::parse($parser, $list); } elseif ($token->value === 'SELECT') { $this->select = new SelectStatement($parser, $list); } else { $parser->error(__('Unexpected keyword.'), $token); break; } $state = 2; } else { $parser->error(__('Unexpected token.'), $token); break; } } } --$list->idx; }
/** * @param Reference $component The component to be built. * * @return string */ public static function build($component) { return trim(Context::escape($component->table) . ' (' . implode(', ', Context::escape($component->columns)) . ') ' . OptionsArray::build($component->options)); }
/** * 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) { // Handle special end options in Select statement // See Statements\SelectStatement::$END_OPTIONS if (get_class($this) === 'SqlParser\\Statements\\SelectStatement' && ($token->value === 'FOR UPDATE' || $token->value === 'LOCK IN SHARE MODE')) { $this->end_options = OptionsArray::parse($parser, $list, static::$END_OPTIONS); } else { // 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. }
/** * @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 AlterOperation */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = new AlterOperation(); /** * Counts brackets. * * @var int $brackets */ $brackets = 0; /** * The state of the parser. * * Below are the states of the parser. * * 0 ---------------------[ options ]---------------------> 1 * * 1 ----------------------[ field ]----------------------> 2 * * 2 -------------------------[ , ]-----------------------> 0 * * @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 comments. if ($token->type === Token::TYPE_COMMENT) { continue; } // Skipping whitespaces. if ($token->type === Token::TYPE_WHITESPACE) { if ($state === 2) { // When parsing the unknown part, the whitespaces are // included to not break anything. $ret->unknown[] = $token; } continue; } if ($state === 0) { $ret->options = OptionsArray::parse($parser, $list, static::$OPTIONS); $state = 1; } elseif ($state === 1) { $ret->field = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true)); if ($ret->field === null) { // No field was read. We go back one token so the next // iteration will parse the same token, but in state 2. --$list->idx; } $state = 2; } elseif ($state === 2) { if ($token->type === Token::TYPE_OPERATOR) { if ($token->value === '(') { ++$brackets; } elseif ($token->value === ')') { --$brackets; } elseif ($token->value === ',' && $brackets === 0) { break; } } $ret->unknown[] = $token; } } if ($ret->options->isEmpty()) { $parser->error(__('Unrecognized alter operation.'), $list->tokens[$list->idx]); } --$list->idx; return $ret; }
/** * 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; } // 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') { break; } /** * 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. }
/** * @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; $state = $ret->isSubpartition ? 5 : 2; } 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('bracketsDelimited' => true, 'noAlias' => 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; }
/** * @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) { ++$list->idx; // Skipping `CREATE`. // Parsing options. $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS); ++$list->idx; // Skipping last option. // Parsing the field name. $this->name = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true)); ++$list->idx; // Skipping field. if ($this->options->has('DATABASE')) { $this->entityOptions = OptionsArray::parse($parser, $list, static::$DB_OPTIONS); } elseif ($this->options->has('TABLE')) { $this->fields = FieldDefinition::parse($parser, $list); ++$list->idx; $this->entityOptions = OptionsArray::parse($parser, $list, static::$TABLE_OPTIONS); } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) { $this->parameters = ParameterDefinition::parse($parser, $list); if ($this->options->has('FUNCTION')) { $token = $list->getNextOfType(Token::TYPE_KEYWORD); if ($token->value !== 'RETURNS') { $parser->error('\'RETURNS\' keyword was expected.', $token); } else { ++$list->idx; $this->return = DataType::parse($parser, $list); } } ++$list->idx; $this->entityOptions = OptionsArray::parse($parser, $list, static::$FUNC_OPTIONS); ++$list->idx; for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; $this->body[] = $token; } } else { if ($this->options->has('VIEW')) { $token = $list->getNext(); // Skipping whitespaces and comments. // Parsing columns list. if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { --$list->idx; // getNext() also goes forward one field. $this->fields = ArrayObj::parse($parser, $list); ++$list->idx; // Skipping last token from the array. $list->getNext(); } // Parsing the `AS` keyword. for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; if ($token->type === Token::TYPE_DELIMITER) { break; } $this->body[] = $token; } } else { if ($this->options->has('TRIGGER')) { // Parsing the time and the event. $this->entityOptions = OptionsArray::parse($parser, $list, static::$TRIGGER_OPTIONS); ++$list->idx; $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON'); ++$list->idx; // Skipping `ON`. // Parsing the name of the table. $this->table = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true)); ++$list->idx; $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW'); ++$list->idx; // Skipping `FOR EACH ROW`. for (; $list->idx < $list->count; ++$list->idx) { $token = $list->tokens[$list->idx]; $this->body[] = $token; } } } } }
/** * 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) { // 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) ? false : true; 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; } // Only keywords are relevant here. Other parts of the query are // processed in the functions below. if ($token->type !== Token::TYPE_KEYWORD) { continue; } // Unions are parsed by the parser because they represent more than // one statement. if ($token->value === 'UNION') { break; } /** * The name of the class that is used for parsing. * @var string $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(); 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']; } } if (!empty(Parser::$STATEMENT_PARSERS[$token->value])) { if (!$parsedOptions) { ++$list->idx; // Skipping keyword. $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->value . '".', $token); continue; } $this->before($parser, $list, $token); // Parsing this keyword. if ($class !== null) { ++$list->idx; // Skipping keyword. $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. }
/** * @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 CreateDefinition[] */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = array(); $expr = new CreateDefinition(); /** * The state of the parser. * * Below are the states of the parser. * * 0 -----------------------[ ( ]------------------------> 1 * * 1 --------------------[ CONSTRAINT ]------------------> 1 * 1 -----------------------[ key ]----------------------> 2 * 1 -------------[ constraint / column name ]-----------> 2 * * 2 --------------------[ data type ]-------------------> 3 * * 3 ---------------------[ options ]--------------------> 4 * * 4 --------------------[ REFERENCES ]------------------> 4 * * 5 ------------------------[ , ]-----------------------> 1 * 5 ------------------------[ ) ]-----------------------> 6 (-1) * * @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) { if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { $state = 1; } else { $parser->error(__('An opening bracket was expected.'), $token); break; } } elseif ($state === 1) { if ($token->type === Token::TYPE_KEYWORD && $token->value === 'CONSTRAINT') { $expr->isConstraint = true; } elseif ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_KEY) { $expr->key = Key::parse($parser, $list); $state = 4; } else { $expr->name = $token->value; if (!$expr->isConstraint) { $state = 2; } } } elseif ($state === 2) { $expr->type = DataType::parse($parser, $list); $state = 3; } elseif ($state === 3) { $expr->options = OptionsArray::parse($parser, $list, static::$FIELD_OPTIONS); $state = 4; } elseif ($state === 4) { if ($token->type === Token::TYPE_KEYWORD && $token->value === 'REFERENCES') { ++$list->idx; // Skipping keyword 'REFERENCES'. $expr->references = Reference::parse($parser, $list); } else { --$list->idx; } $state = 5; } elseif ($state === 5) { if (!empty($expr->type) || !empty($expr->key)) { $ret[] = $expr; } $expr = new CreateDefinition(); if ($token->value === ',') { $state = 1; } elseif ($token->value === ')') { $state = 6; ++$list->idx; break; } else { $parser->error(__('A comma or a closing bracket was expected.'), $token); $state = 0; break; } } } // Last iteration was not saved. if (!empty($expr->type) || !empty($expr->key)) { $ret[] = $expr; } if ($state !== 0 && $state !== 6) { $parser->error(__('A closing bracket was expected.'), $list->tokens[$list->idx - 1]); } --$list->idx; return $ret; }
/** * @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 DataType */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = new DataType(); /** * The state of the parser. * * Below are the states of the parser. * * 0 -------------------[ data type ]--------------------> 1 * * 1 ----------------[ size and options ]----------------> 2 * * @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]; // Skipping whitespaces and comments. if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) { continue; } if ($state === 0) { $ret->name = strtoupper($token->value); if ($token->type !== Token::TYPE_KEYWORD || !($token->flags & Token::FLAG_KEYWORD_DATA_TYPE)) { $parser->error(__('Unrecognized data type.'), $token); } $state = 1; } elseif ($state === 1) { if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { $parameters = ArrayObj::parse($parser, $list); ++$list->idx; $ret->parameters = $ret->name === 'ENUM' || $ret->name === 'SET' ? $parameters->raw : $parameters->values; } $ret->options = OptionsArray::parse($parser, $list, static::$DATA_TYPE_OPTIONS); ++$list->idx; break; } } if (empty($ret->name)) { return null; } --$list->idx; return $ret; }
/** * @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 Reference */ public static function parse(Parser $parser, TokensList $list, array $options = array()) { $ret = new Reference(); /** * The state of the parser. * * Below are the states of the parser. * * 0 ----------------------[ table ]---------------------> 1 * * 1 ---------------------[ columns ]--------------------> 2 * * 2 ---------------------[ options ]--------------------> (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->table = $token->value; $state = 1; } elseif ($state === 1) { $ret->columns = ArrayObj::parse($parser, $list)->values; $state = 2; } elseif ($state === 2) { $ret->options = OptionsArray::parse($parser, $list, static::$REFERENCES_OPTIONS); ++$list->idx; break; } } --$list->idx; return $ret; }
/** * @return string */ public function build() { $tmp = array(); foreach ($this->altered as $altered) { $tmp[] = $altered::build($altered); } return 'ALTER ' . OptionsArray::build($this->options) . ' TABLE ' . Expression::build($this->table) . ' ' . implode(', ', $tmp); }
/** * @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) { ++$list->idx; // Skipping `DELETE`. // parse any options if provided $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS); ++$list->idx; /** * The state of the parser. * * Below are the states of the parser. * * 0 ---------------------------------[ FROM ]----------------------------------> 2 * 0 ------------------------------[ table[.*] ]--------------------------------> 1 * 1 ---------------------------------[ FROM ]----------------------------------> 2 * 2 --------------------------------[ USING ]----------------------------------> 3 * 2 --------------------------------[ WHERE ]----------------------------------> 4 * 2 --------------------------------[ ORDER ]----------------------------------> 5 * 2 --------------------------------[ LIMIT ]----------------------------------> 6 * * @var int $state */ $state = 0; /** * If the query is multi-table or not * * @var bool $multiTable */ $multiTable = false; 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; } if ($state === 0) { if ($token->type === Token::TYPE_KEYWORD && $token->value !== 'FROM') { $parser->error(__('Unexpected keyword.'), $token); break; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'FROM') { ++$list->idx; // Skip 'FROM' $this->from = ExpressionArray::parse($parser, $list); $state = 2; } else { $this->columns = ExpressionArray::parse($parser, $list); $state = 1; } } elseif ($state === 1) { if ($token->type === Token::TYPE_KEYWORD && $token->value !== 'FROM') { $parser->error(__('Unexpected keyword.'), $token); break; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'FROM') { ++$list->idx; // Skip 'FROM' $this->from = ExpressionArray::parse($parser, $list); $state = 2; } else { $parser->error(__('Unexpected token.'), $token); break; } } elseif ($state === 2) { if ($token->type === Token::TYPE_KEYWORD && $token->value === 'USING') { ++$list->idx; // Skip 'USING' $this->using = ExpressionArray::parse($parser, $list); $state = 3; $multiTable = true; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'WHERE') { ++$list->idx; // Skip 'WHERE' $this->where = Condition::parse($parser, $list); $state = 4; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'ORDER BY') { ++$list->idx; // Skip 'ORDER BY' $this->order = OrderKeyword::parse($parser, $list); $state = 5; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'LIMIT') { ++$list->idx; // Skip 'LIMIT' $this->limit = Limit::parse($parser, $list); $state = 6; } elseif ($token->type === Token::TYPE_KEYWORD) { $parser->error(__('Unexpected keyword.'), $token); break; } } elseif ($state === 3) { if ($token->type === Token::TYPE_KEYWORD && $token->value === 'WHERE') { ++$list->idx; // Skip 'WHERE' $this->where = Condition::parse($parser, $list); $state = 4; } elseif ($token->type === Token::TYPE_KEYWORD) { $parser->error(__('Unexpected keyword.'), $token); break; } else { $parser->error(__('Unexpected token.'), $token); break; } } elseif ($state === 4) { if ($multiTable === true && $token->type === Token::TYPE_KEYWORD) { $parser->error(__('This type of clause is not valid in Multi-table queries.'), $token); break; } if ($token->type === Token::TYPE_KEYWORD && $token->value === 'ORDER BY') { ++$list->idx; // Skip 'ORDER BY' $this->order = OrderKeyword::parse($parser, $list); $state = 5; } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'LIMIT') { ++$list->idx; // Skip 'LIMIT' $this->limit = Limit::parse($parser, $list); $state = 6; } elseif ($token->type === Token::TYPE_KEYWORD) { $parser->error(__('Unexpected keyword.'), $token); break; } } elseif ($state === 5) { if ($token->type === Token::TYPE_KEYWORD && $token->value === 'LIMIT') { ++$list->idx; // Skip 'LIMIT' $this->limit = Limit::parse($parser, $list); $state = 6; } elseif ($token->type === Token::TYPE_KEYWORD) { $parser->error(__('Unexpected keyword.'), $token); break; } } } if ($state >= 2) { foreach ($this->from as $from_expr) { $from_expr->database = $from_expr->table; $from_expr->table = $from_expr->column; $from_expr->column = null; } } --$list->idx; }
/** * @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; }