예제 #1
0
 /**
  * @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 Limit
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new Limit();
     $offset = 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;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED) {
             break;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->value === 'OFFSET') {
             if ($offset) {
                 $parser->error(__('An offset was expected.'), $token);
             }
             $offset = true;
             continue;
         }
         if ($token->type === Token::TYPE_OPERATOR && $token->value === ',') {
             $ret->offset = $ret->rowCount;
             $ret->rowCount = 0;
             continue;
         }
         if ($offset) {
             $ret->offset = $token->value;
             $offset = false;
         } else {
             $ret->rowCount = $token->value;
         }
     }
     if ($offset) {
         $parser->error(__('An offset was expected.'), $list->tokens[$list->idx - 1]);
     }
     --$list->idx;
     return $ret;
 }
예제 #2
0
 /**
  * @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;
 }
예제 #3
0
 /**
  * Possible options:
  *
  *      `field`
  *
  *          First field to be filled.
  *          If this is not specified, it takes the value of `parseField`.
  *
  *      `parseField`
  *
  *          Specifies the type of the field parsed. It may be `database`,
  *          `table` or `column`. These expressions may not include
  *          parentheses.
  *
  *      `breakOnAlias`
  *
  *          If not empty, breaks when the alias occurs (it is not included).
  *
  *      `breakOnParentheses`
  *
  *          If not empty, breaks when the first parentheses occurs.
  *
  *      `parenthesesDelimited`
  *
  *          If not empty, breaks after last parentheses occurred.
  *
  * @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 Expression
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new Expression();
     /**
      * Whether current tokens make an expression or a table reference.
      *
      * @var bool $isExpr
      */
     $isExpr = false;
     /**
      * Whether a period was previously found.
      *
      * @var bool $dot
      */
     $dot = false;
     /**
      * Whether an alias is expected. Is 2 if `AS` keyword was found.
      *
      * @var bool $alias
      */
     $alias = false;
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * Keeps track of the last two previous tokens.
      *
      * @var Token[] $prev
      */
     $prev = array(null, null);
     // When a field is parsed, no parentheses are expected.
     if (!empty($options['parseField'])) {
         $options['breakOnParentheses'] = true;
         $options['field'] = $options['parseField'];
     }
     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) {
             if ($isExpr) {
                 $ret->expr .= $token->token;
             }
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD) {
             if ($brackets > 0 && empty($ret->subquery) && !empty(Parser::$STATEMENT_PARSERS[$token->value])) {
                 // A `(` was previously found and this keyword is the
                 // beginning of a statement, so this is a subquery.
                 $ret->subquery = $token->value;
             } elseif ($token->flags & Token::FLAG_KEYWORD_FUNCTION && (empty($options['parseField']) && !$alias)) {
                 $isExpr = true;
             } elseif ($token->flags & Token::FLAG_KEYWORD_RESERVED && $brackets === 0) {
                 if (empty(self::$ALLOWED_KEYWORDS[$token->value])) {
                     // A reserved keyword that is not allowed in the
                     // expression was found so the expression must have
                     // ended and a new clause is starting.
                     break;
                 }
                 if ($token->value === 'AS') {
                     if (!empty($options['breakOnAlias'])) {
                         break;
                     }
                     if ($alias) {
                         $parser->error(__('An alias was expected.'), $token);
                         break;
                     }
                     $alias = true;
                     continue;
                 } elseif ($token->value === 'CASE') {
                     // For a use of CASE like
                     // 'SELECT a = CASE .... END, b=1, `id`, ... FROM ...'
                     $tempCaseExpr = CaseExpression::parse($parser, $list);
                     $ret->expr .= CaseExpression::build($tempCaseExpr);
                     $isExpr = true;
                     continue;
                 }
                 $isExpr = true;
             } elseif ($brackets === 0 && count($ret->expr) > 0 && !$alias) {
                 /* End of expression */
                 break;
             }
         }
         if ($token->type === Token::TYPE_NUMBER || $token->type === Token::TYPE_BOOL || $token->type === Token::TYPE_SYMBOL && $token->flags & Token::FLAG_SYMBOL_VARIABLE || $token->type === Token::TYPE_OPERATOR && $token->value !== '.') {
             if (!empty($options['parseField'])) {
                 break;
             }
             // Numbers, booleans and operators (except dot) are usually part
             // of expressions.
             $isExpr = true;
         }
         if ($token->type === Token::TYPE_OPERATOR) {
             if (!empty($options['breakOnParentheses']) && ($token->value === '(' || $token->value === ')')) {
                 // No brackets were expected.
                 break;
             }
             if ($token->value === '(') {
                 ++$brackets;
                 if (empty($ret->function) && $prev[1] !== null && ($prev[1]->type === Token::TYPE_NONE || $prev[1]->type === Token::TYPE_SYMBOL || $prev[1]->type === Token::TYPE_KEYWORD && $prev[1]->flags & Token::FLAG_KEYWORD_FUNCTION)) {
                     $ret->function = $prev[1]->value;
                 }
             } elseif ($token->value === ')' && $brackets == 0) {
                 // Not our bracket
                 break;
             } elseif ($token->value === ')') {
                 --$brackets;
                 if ($brackets === 0) {
                     if (!empty($options['parenthesesDelimited'])) {
                         // The current token is the last bracket, the next
                         // one will be outside the expression.
                         $ret->expr .= $token->token;
                         ++$list->idx;
                         break;
                     }
                 } elseif ($brackets < 0) {
                     // $parser->error(__('Unexpected closing bracket.'), $token);
                     // $brackets = 0;
                     break;
                 }
             } elseif ($token->value === ',') {
                 // Expressions are comma-delimited.
                 if ($brackets === 0) {
                     break;
                 }
             }
         }
         // Saving the previous tokens.
         $prev[0] = $prev[1];
         $prev[1] = $token;
         if ($alias) {
             // An alias is expected (the keyword `AS` was previously found).
             if (!empty($ret->alias)) {
                 $parser->error(__('An alias was previously found.'), $token);
                 break;
             }
             $ret->alias = $token->value;
             $alias = false;
         } elseif ($isExpr) {
             // Handling aliases.
             if ($brackets === 0 && ($prev[0] === null || ($prev[0]->type !== Token::TYPE_OPERATOR || $prev[0]->token === ')') && ($prev[0]->type !== Token::TYPE_KEYWORD || !($prev[0]->flags & Token::FLAG_KEYWORD_RESERVED))) && ($prev[1]->type === Token::TYPE_STRING || $prev[1]->type === Token::TYPE_SYMBOL && !($prev[1]->flags & Token::FLAG_SYMBOL_VARIABLE) || $prev[1]->type === Token::TYPE_NONE)) {
                 if (!empty($ret->alias)) {
                     $parser->error(__('An alias was previously found.'), $token);
                     break;
                 }
                 $ret->alias = $prev[1]->value;
             } else {
                 $ret->expr .= $token->token;
             }
         } elseif (!$isExpr) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === '.') {
                 // Found a `.` which means we expect a column name and
                 // the column name we parsed is actually the table name
                 // and the table name is actually a database name.
                 if (!empty($ret->database) || $dot) {
                     $parser->error(__('Unexpected dot.'), $token);
                 }
                 $ret->database = $ret->table;
                 $ret->table = $ret->column;
                 $ret->column = null;
                 $dot = true;
                 $ret->expr .= $token->token;
             } else {
                 $field = empty($options['field']) ? 'column' : $options['field'];
                 if (empty($ret->{$field})) {
                     $ret->{$field} = $token->value;
                     $ret->expr .= $token->token;
                     $dot = false;
                 } else {
                     // No alias is expected.
                     if (!empty($options['breakOnAlias'])) {
                         break;
                     }
                     if (!empty($ret->alias)) {
                         $parser->error(__('An alias was previously found.'), $token);
                         break;
                     }
                     $ret->alias = $token->value;
                 }
             }
         }
     }
     if ($alias) {
         $parser->error(__('An alias was expected.'), $list->tokens[$list->idx - 1]);
     }
     // White-spaces might be added at the end.
     $ret->expr = trim($ret->expr);
     if ($ret->expr === '') {
         return null;
     }
     --$list->idx;
     return $ret;
 }
예제 #4
0
 /**
  * @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 Expression
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new Expression();
     /**
      * Whether current tokens make an expression or a table reference.
      *
      * @var bool $isExpr
      */
     $isExpr = false;
     /**
      * Whether a period was previously found.
      *
      * @var bool $dot
      */
     $dot = false;
     /**
      * Whether an alias is expected. Is 2 if `AS` keyword was found.
      *
      * @var int $alias
      */
     $alias = 0;
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * Keeps track of the previous token.
      * Possible values:
      *     string, if function was previously found;
      *     true, if opening bracket was previously found;
      *     null, in any other case.
      *
      * @var string|bool $prev
      */
     $prev = null;
     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) {
             if ($isExpr && !$alias) {
                 $ret->expr .= $token->token;
             }
             if ($alias === 0 && empty($options['noAlias']) && !$isExpr && !$dot && !empty($ret->expr)) {
                 $alias = 1;
             }
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED && $token->value !== 'DUAL') {
             // Keywords may be found only between brackets.
             if ($brackets === 0) {
                 if (empty($options['noAlias']) && $token->value === 'AS') {
                     $alias = 2;
                     continue;
                 }
                 if (!($token->flags & Token::FLAG_KEYWORD_FUNCTION)) {
                     break;
                 }
             } elseif ($prev === true) {
                 if (empty($ret->subquery) && !empty(Parser::$STATEMENT_PARSERS[$token->value])) {
                     // A `(` was previously found and this keyword is the
                     // beginning of a statement, so this is a subquery.
                     $ret->subquery = $token->value;
                 }
             }
         }
         if ($token->type === Token::TYPE_OPERATOR) {
             if (!empty($options['noBrackets']) && ($token->value === '(' || $token->value === ')')) {
                 break;
             }
             if ($token->value === '(') {
                 ++$brackets;
                 if (empty($ret->function) && $prev !== null && $prev !== true) {
                     // A function name was previously found and now an open
                     // bracket, so this is a function call.
                     $ret->function = $prev;
                 }
                 $isExpr = true;
             } elseif ($token->value === ')') {
                 --$brackets;
                 if ($brackets === 0) {
                     if (!empty($options['bracketsDelimited'])) {
                         // The current token is the last brackets, the next
                         // one will be outside.
                         $ret->expr .= $token->token;
                         ++$list->idx;
                         break;
                     }
                 } elseif ($brackets < 0) {
                     // $parser->error(__('Unexpected closing bracket.'), $token);
                     // $brackets = 0;
                     break;
                 }
             } elseif ($token->value === ',') {
                 if ($brackets === 0) {
                     break;
                 }
             }
         }
         if ($token->type === Token::TYPE_NUMBER || $token->type === Token::TYPE_BOOL || $token->type === Token::TYPE_SYMBOL && $token->flags & Token::FLAG_SYMBOL_VARIABLE || $token->type === Token::TYPE_OPERATOR && $token->value !== '.') {
             // Numbers, booleans and operators are usually part of expressions.
             $isExpr = true;
         }
         if ($alias) {
             // An alias is expected (the keyword `AS` was previously found).
             if (!empty($ret->alias)) {
                 $parser->error(__('An alias was previously found.'), $token);
             }
             $ret->alias = $token->value;
             $alias = 0;
         } else {
             if (!$isExpr) {
                 if ($token->type === Token::TYPE_OPERATOR && $token->value === '.') {
                     // Found a `.` which means we expect a column name and
                     // the column name we parsed is actually the table name
                     // and the table name is actually a database name.
                     if (!empty($ret->database) || $dot) {
                         $parser->error(__('Unexpected dot.'), $token);
                     }
                     $ret->database = $ret->table;
                     $ret->table = $ret->column;
                     $ret->column = null;
                     $dot = true;
                 } else {
                     // We found the name of a column (or table if column
                     // field should be skipped; used to parse table names).
                     $field = !empty($options['skipColumn']) ? 'table' : 'column';
                     if (!empty($ret->{$field})) {
                         // No alias is expected.
                         if (!empty($options['noAlias'])) {
                             break;
                         }
                         // Parsing aliases without `AS` keyword and any
                         // whitespace.
                         // Example: SELECT 1`foo`
                         if ($token->type === Token::TYPE_STRING || $token->type === Token::TYPE_SYMBOL && $token->flags & Token::FLAG_SYMBOL_BACKTICK) {
                             if (!empty($ret->alias)) {
                                 $parser->error(__('An alias was previously found.'), $token);
                             }
                             $ret->alias = $token->value;
                         }
                     } else {
                         $ret->{$field} = $token->value;
                     }
                     $dot = false;
                 }
             } else {
                 // Parsing aliases without `AS` keyword.
                 // Example: SELECT 'foo' `bar`
                 if ($brackets === 0 && empty($options['noAlias'])) {
                     if ($token->type === Token::TYPE_NONE || $token->type === Token::TYPE_STRING || $token->type === Token::TYPE_SYMBOL && $token->flags & Token::FLAG_SYMBOL_BACKTICK) {
                         if (!empty($ret->alias)) {
                             $parser->error(__('An alias was previously found.'), $token);
                         }
                         $ret->alias = $token->value;
                         continue;
                     }
                 }
             }
             $ret->expr .= $token->token;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_FUNCTION) {
             $prev = strtoupper($token->value);
         } elseif ($token->type === Token::TYPE_OPERATOR || $token->value === '(') {
             $prev = true;
         } else {
             $prev = null;
         }
     }
     if ($alias === 2) {
         $parser->error(__('An alias was expected.'), $list->tokens[$list->idx - 1]);
     }
     // Whitespaces might be added at the end.
     $ret->expr = trim($ret->expr);
     if (empty($ret->expr)) {
         return null;
     }
     --$list->idx;
     return $ret;
 }
예제 #5
0
 /**
  * @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 mixed
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = empty($options['type']) ? new ArrayObj() : array();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -----------------------[ ( ]------------------------> 1
      *
      *      1 ------------------[ array element ]-----------------> 2
      *
      *      2 ------------------------[ , ]-----------------------> 1
      *      2 ------------------------[ ) ]-----------------------> (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) {
             if ($token->type !== Token::TYPE_OPERATOR || $token->value !== '(') {
                 $parser->error(__('An opening bracket was expected.'), $token);
                 break;
             }
             $state = 1;
         } elseif ($state === 1) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === ')') {
                 // Empty array.
                 break;
             }
             if (empty($options['type'])) {
                 $ret->values[] = $token->value;
                 $ret->raw[] = $token->token;
             } else {
                 $ret[] = $options['type']::parse($parser, $list, empty($options['typeOptions']) ? array() : $options['typeOptions']);
             }
             $state = 2;
         } elseif ($state === 2) {
             if ($token->type !== Token::TYPE_OPERATOR || $token->value !== ',' && $token->value !== ')') {
                 $parser->error(__('A comma or a closing bracket was expected'), $token);
                 break;
             }
             if ($token->value === ',') {
                 $state = 1;
             } else {
                 // )
                 break;
             }
         }
     }
     return $ret;
 }
예제 #6
0
 /**
  * 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.
 }
예제 #7
0
 /**
  * @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;
 }
예제 #8
0
 /**
  * @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 ArrayObj
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new ArrayObj();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -----------------------[ ( ]------------------------> 1
      *
      *      1 ------------------[ array element ]-----------------> 2
      *
      *      2 ------------------------[ , ]-----------------------> 1
      *      2 ------------------------[ ) ]-----------------------> -1
      *
      * @var int
      */
     $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 !== '(') {
                 $parser->error('An open bracket was expected.', $token);
                 break;
             }
             $state = 1;
         } elseif ($state === 1) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === ')') {
                 // Empty array.
                 break;
             }
             $ret->values[] = $token->value;
             $ret->raw[] = $token->token;
             $state = 2;
         } elseif ($state === 2) {
             if ($token->type !== Token::TYPE_OPERATOR || $token->value !== ',' && $token->value !== ')') {
                 $parser->error('Symbols \')\' or \',\' were expected', $token);
                 break;
             }
             if ($token->value === ',') {
                 $state = 1;
             } else {
                 // )
                 break;
             }
         }
     }
     return $ret;
 }
예제 #9
0
 /**
  * @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 OptionsArray
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new OptionsArray();
     /**
      * The ID that will be assigned to duplicate options.
      *
      * @var int $lastAssignedId
      */
     $lastAssignedId = count($options) + 1;
     /**
      * The option that was processed last time.
      *
      * @var array $lastOption
      */
     $lastOption = null;
     /**
      * The index of the option that was processed last time.
      *
      * @var int $lastOptionId
      */
     $lastOptionId = 0;
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ---------------------[ option ]----------------------> 1
      *
      *      1 -------------------[ = (optional) ]------------------> 2
      *
      *      2 ----------------------[ value ]----------------------> 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 whitespace if not parsing value.
         if ($token->type === Token::TYPE_WHITESPACE && $brackets === 0) {
             continue;
         }
         if ($lastOption === null) {
             $upper = strtoupper($token->token);
             if (isset($options[$upper])) {
                 $lastOption = $options[$upper];
                 $lastOptionId = is_array($lastOption) ? $lastOption[0] : $lastOption;
                 $state = 0;
                 // Checking for option conflicts.
                 // For example, in `SELECT` statements the keywords `ALL`
                 // and `DISTINCT` conflict and if used together, they
                 // produce an invalid query.
                 //
                 // Usually, tokens can be identified in the array by the
                 // option ID, but if conflicts occur, a generated option ID
                 // is used.
                 //
                 // The first pseudo duplicate ID is the maximum value of the
                 // real options (e.g.  if there are 5 options, the first
                 // fake ID is 6).
                 if (isset($ret->options[$lastOptionId])) {
                     $parser->error(sprintf(__('This option conflicts with "%1$s".'), is_array($ret->options[$lastOptionId]) ? $ret->options[$lastOptionId]['name'] : $ret->options[$lastOptionId]), $token);
                     $lastOptionId = $lastAssignedId++;
                 }
             } else {
                 // There is no option to be processed.
                 break;
             }
         }
         if ($state === 0) {
             if (!is_array($lastOption)) {
                 // This is a just keyword option without any value.
                 // This is the beginning and the end of it.
                 $ret->options[$lastOptionId] = $token->value;
                 $lastOption = null;
                 $state = 0;
             } elseif ($lastOption[1] === 'var' || $lastOption[1] === 'var=') {
                 // This is a keyword that is followed by a value.
                 // This is only the beginning. The value is parsed in state
                 // 1 and 2. State 1 is used to skip the first equals sign
                 // and state 2 to parse the actual value.
                 $ret->options[$lastOptionId] = array('name' => $token->value, 'equals' => $lastOption[1] === 'var=', 'expr' => '', 'value' => '');
                 $state = 1;
             } elseif ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
                 // This is a keyword that is followed by an expression.
                 // The expression is used by the specialized parser.
                 // Skipping this option in order to parse the expression.
                 ++$list->idx;
                 $ret->options[$lastOptionId] = array('name' => $token->value, 'equals' => $lastOption[1] === 'expr=', 'expr' => '');
                 $state = 1;
             }
         } elseif ($state === 1) {
             $state = 2;
             if ($token->token === '=') {
                 $ret->options[$lastOptionId]['equals'] = true;
                 continue;
             }
         }
         // This is outside the `elseif` group above because the change might
         // change this iteration.
         if ($state === 2) {
             if ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
                 $ret->options[$lastOptionId]['expr'] = Expression::parse($parser, $list, empty($lastOption[2]) ? array() : $lastOption[2]);
                 $ret->options[$lastOptionId]['value'] = $ret->options[$lastOptionId]['expr']->expr;
                 $lastOption = null;
                 $state = 0;
             } else {
                 if ($token->token === '(') {
                     ++$brackets;
                 } elseif ($token->token === ')') {
                     --$brackets;
                 }
                 $ret->options[$lastOptionId]['expr'] .= $token->token;
                 if (!($token->token === '(' && $brackets === 1 || $token->token === ')' && $brackets === 0)) {
                     // First pair of brackets is being skipped.
                     $ret->options[$lastOptionId]['value'] .= $token->value;
                 }
                 // Checking if we finished parsing.
                 if ($brackets === 0) {
                     $lastOption = null;
                 }
             }
         }
     }
     /*
      * We reached the end of statement without getting a value
      * for an option for which a value was required
      */
     if ($state === 1 && $lastOption && ($lastOption[1] == 'expr' || $lastOption[1] == 'var' || $lastOption[1] == 'var=' || $lastOption[1] == 'expr=')) {
         $parser->error(sprintf(__('Value/Expression for the option %1$s was expected'), $ret->options[$lastOptionId]['name']), $list->tokens[$list->idx - 1]);
     }
     if (empty($options['_UNSORTED'])) {
         ksort($ret->options);
     }
     --$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.
 }
예제 #11
0
 /**
  * @expectedException SqlParser\Exceptions\ParserException
  * @expectedExceptionMessage strict error
  * @expectedExceptionCode 3
  */
 public function testErrorStrict()
 {
     $parser = new Parser(new TokensList());
     $parser->strict = true;
     $parser->error(__('strict error'), new Token('foo'), 3);
 }
예제 #12
0
 /**
  * @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 ArrayObj|Component[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = empty($options['type']) ? new ArrayObj() : array();
     /**
      * The last raw expression.
      *
      * @var string $lastRaw
      */
     $lastRaw = '';
     /**
      * The last value.
      *
      * @var string $lastValue
      */
     $lastValue = '';
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * Last separator (bracket or comma).
      *
      * @var boolean $isCommaLast
      */
     $isCommaLast = 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;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             $lastRaw .= $token->token;
             $lastValue = trim($lastValue) . ' ';
             continue;
         }
         if ($brackets === 0 && ($token->type !== Token::TYPE_OPERATOR || $token->value !== '(')) {
             $parser->error(__('An opening bracket was expected.'), $token);
             break;
         }
         if ($token->type === Token::TYPE_OPERATOR) {
             if ($token->value === '(') {
                 if (++$brackets === 1) {
                     // 1 is the base level.
                     continue;
                 }
             } elseif ($token->value === ')') {
                 if (--$brackets === 0) {
                     // Array ended.
                     break;
                 }
             } elseif ($token->value === ',') {
                 if ($brackets === 1) {
                     $isCommaLast = true;
                     if (empty($options['type'])) {
                         $ret->raw[] = trim($lastRaw);
                         $ret->values[] = trim($lastValue);
                         $lastRaw = $lastValue = '';
                     }
                 }
                 continue;
             }
         }
         if (empty($options['type'])) {
             $lastRaw .= $token->token;
             $lastValue .= $token->value;
         } else {
             $ret[] = $options['type']::parse($parser, $list, empty($options['typeOptions']) ? array() : $options['typeOptions']);
         }
     }
     // Handling last element.
     //
     // This is treated differently to treat the following cases:
     //
     //           => array()
     //      (,)  => array('', '')
     //      ()   => array()
     //      (a,) => array('a', '')
     //      (a)  => array('a')
     //
     $lastRaw = trim($lastRaw);
     if (empty($options['type']) && (strlen($lastRaw) > 0 || $isCommaLast)) {
         $ret->raw[] = $lastRaw;
         $ret->values[] = trim($lastValue);
     }
     return $ret;
 }
예제 #13
0
 /**
  * @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 OptionsFragment
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new OptionsFragment();
     /**
      * The ID that will be assigned to duplicate options.
      * @var int
      */
     $lastAssignedId = count($options) + 1;
     /**
      * The option that was processed last time.
      * @var array
      */
     $lastOption = null;
     $lastOptionId = 0;
     $brackets = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          * @var 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 ($lastOption === null) {
             if (isset($options[strtoupper($token->value)])) {
                 $lastOption = $options[strtoupper($token->value)];
                 $lastOptionId = is_array($lastOption) ? $lastOption[0] : $lastOption;
                 // Checking for option conflicts.
                 // For example, in `SELECT` statements the keywords `ALL` and `DISTINCT`
                 // conflict and if used together, they produce an invalid query.
                 // Usually, tokens can be identified in the array by the option ID,
                 // but if conflicts occur, a psuedo option ID is used.
                 // The first pseudo duplicate ID is the maximum value of the real
                 // options (e.g.  if there are 5 options, the first fake ID is 6).
                 if (isset($ret->options[$lastOptionId])) {
                     $parser->error('This option conflicts with \'' . $ret->options[$lastOptionId] . '\'.', $token);
                     $lastOptionId = $lastAssignedId++;
                 }
             } else {
                 // There is no option to be processed.
                 break;
             }
         }
         if (is_array($lastOption)) {
             if (empty($ret->options[$lastOptionId])) {
                 $ret->options[$lastOptionId] = array('name' => $token->value, 'value' => '');
             } else {
                 if ($token->value !== '=') {
                     if ($token->value === '(') {
                         ++$brackets;
                     } elseif ($token->value === ')') {
                         --$brackets;
                     } else {
                         $ret->options[$lastOptionId]['value'] .= $token->value;
                     }
                     if ($brackets === 0) {
                         $lastOption = null;
                     }
                 }
             }
         } else {
             $ret->options[$lastOptionId] = $token->value;
             $lastOption = null;
         }
     }
     ksort($ret->options);
     --$list->idx;
     return $ret;
 }
예제 #14
0
 /**
  * Validates the order of the clauses in parsed statement
  * Ideally this should be called after successfully
  * completing the parsing of each statement
  *
  * @param Parser     $parser The instance that requests parsing.
  * @param TokensList $list   The list of tokens to be parsed.
  *
  * @return boolean
  */
 public function validateClauseOrder($parser, $list)
 {
     $clauses = array_flip(array_keys($this->getClauses()));
     if (empty($clauses) || count($clauses) == 0) {
         return true;
     }
     $minIdx = -1;
     /**
      * For tracking JOIN clauses in a query
      *   0 - JOIN not found till now
      *   1 - JOIN has been found
      *   2 - A Non-JOIN clause has been found
      *       after a previously found JOIN clause
      *
      * @var int $joinStart
      */
     $joinStart = 0;
     $error = 0;
     foreach ($clauses as $clauseType => $index) {
         $clauseStartIdx = Utils\Query::getClauseStartOffset($this, $list, $clauseType);
         // Handle ordering of Multiple Joins in a query
         if ($clauseStartIdx != -1) {
             if ($joinStart == 0 && stripos($clauseType, 'JOIN')) {
                 $joinStart = 1;
             } elseif ($joinStart == 1 && !stripos($clauseType, 'JOIN')) {
                 $joinStart = 2;
             } elseif ($joinStart == 2 && stripos($clauseType, 'JOIN')) {
                 $error = 1;
             }
         }
         if ($clauseStartIdx != -1 && $clauseStartIdx < $minIdx) {
             if ($joinStart == 0 || $joinStart == 2 && ($error = 1)) {
                 $token = $list->tokens[$clauseStartIdx];
                 $parser->error(__('Unexpected ordering of clauses.'), $token);
                 return false;
             } else {
                 $minIdx = $clauseStartIdx;
             }
         } elseif ($clauseStartIdx != -1) {
             $minIdx = $clauseStartIdx;
         }
     }
     return true;
 }