/** * @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 (empty($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); } 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; } } 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; } } } } }
/** * @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; } } }
public function testBuild() { $parser = new Parser('CREATE TABLE `payment` (' . '-- snippet' . "\n" . '`customer_id` smallint(5) unsigned NOT NULL,' . 'CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE' . ') ENGINE=InnoDB"'); $this->assertEquals('CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE', CreateDefinition::build($parser->statements[0]->fields[1])); }