Inheritance: extends SqlParser\Component
 /**
  * @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 = array();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ----------------------[ array ]---------------------> 1
      *
      *      1 ------------------------[ , ]------------------------> 0
      *      1 -----------------------[ else ]----------------------> (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 ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED && ~$token->flags & Token::FLAG_KEYWORD_FUNCTION && $token->value !== 'DUAL' && $token->value !== 'NULL' && $token->value !== 'CASE') {
             // No keyword is expected.
             break;
         }
         if ($state === 0) {
             if ($token->type === Token::TYPE_KEYWORD && $token->value === 'CASE') {
                 $expr = CaseExpression::parse($parser, $list, $options);
             } else {
                 $expr = Expression::parse($parser, $list, $options);
             }
             if ($expr === null) {
                 break;
             }
             $ret[] = $expr;
             $state = 1;
         } elseif ($state === 1) {
             if ($token->value === ',') {
                 $state = 0;
             } else {
                 break;
             }
         }
     }
     if ($state === 0) {
         $parser->error(__('An expression was expected.'), $list->tokens[$list->idx]);
     }
     --$list->idx;
     return $ret;
 }
Example #2
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;
 }
 public function testParseBuild6()
 {
     $caseExprQuery = 'case when 1=1 then "Some" ' . 'when 1=2 then "SomeOther" end';
     $component = CaseExpression::parse(new Parser(), $this->getTokensList($caseExprQuery));
     $this->assertEquals(CaseExpression::build($component), 'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" END');
 }