- <?php
- namespace PHPCR\Util\QOM;
- use DateTime;
- use Exception;
- use InvalidArgumentException;
- use LogicException;
- use PHPCR\PropertyType;
- use PHPCR\Query\InvalidQueryException;
- use PHPCR\Query\QOM\ChildNodeJoinConditionInterface;
- use PHPCR\Query\QOM\ColumnInterface;
- use PHPCR\Query\QOM\ComparisonInterface;
- use PHPCR\Query\QOM\ConstraintInterface;
- use PHPCR\Query\QOM\DescendantNodeJoinConditionInterface;
- use PHPCR\Query\QOM\DynamicOperandInterface;
- use PHPCR\Query\QOM\EquiJoinConditionInterface;
- use PHPCR\Query\QOM\FullTextSearchInterface;
- use PHPCR\Query\QOM\JoinConditionInterface;
- use PHPCR\Query\QOM\JoinInterface;
- use PHPCR\Query\QOM\NotInterface;
- use PHPCR\Query\QOM\OrderingInterface;
- use PHPCR\Query\QOM\PropertyValueInterface;
- use PHPCR\Query\QOM\QueryObjectModelConstantsInterface as Constants;
- use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
- use PHPCR\Query\QOM\QueryObjectModelInterface;
- use PHPCR\Query\QOM\SameNodeJoinConditionInterface;
- use PHPCR\Query\QOM\SelectorInterface;
- use PHPCR\Query\QOM\SourceInterface;
- use PHPCR\Query\QOM\StaticOperandInterface;
- use PHPCR\Util\ValueConverter;
- /**
-  * Parse SQL2 statements and output a corresponding QOM objects tree.
-  *
-  * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
-  * @license http://opensource.org/licenses/MIT MIT License
-  */
- class Sql2ToQomQueryConverter
- {
-     /**
-      * The factory to create QOM objects.
-      *
-      * @var QueryObjectModelFactoryInterface
-      */
-     protected $factory;
-     /**
-      * Scanner to parse SQL2.
-      *
-      * @var Sql2Scanner;
-      */
-     protected $scanner;
-     /**
-      * The SQL2 query (the converter is not reentrant).
-      *
-      * @var string
-      */
-     protected $sql2;
-     /**
-      * The selector is not required for SQL2 but for QOM.
-      *
-      * We keep all selectors we encounter. If there is exactly one, it is used
-      * whenever we encounter non-qualified names.
-      *
-      * @var string|array
-      */
-     protected $implicitSelectorName = null;
-     /**
-      * @var ValueConverter
-      */
-     private $valueConverter;
-     /**
-      * Instantiate a converter.
-      *
-      * @param QueryObjectModelFactoryInterface $factory
-      * @param ValueConverter                   $valueConverter To override default converter.
-      */
-     public function __construct(QueryObjectModelFactoryInterface $factory, ValueConverter $valueConverter = null)
-     {
-         $this->factory = $factory;
-         $this->valueConverter = $valueConverter ?: new ValueConverter();
-     }
-     /**
-      * 6.7.1. Query
-      * Parse an SQL2 query and return the corresponding QOM QueryObjectModel.
-      *
-      * @param string $sql2
-      *
-      * @throws InvalidQueryException
-      *
-      * @return QueryObjectModelInterface
-      */
-     public function parse($sql2)
-     {
-         $this->implicitSelectorName = null;
-         $this->sql2 = $sql2;
-         $this->scanner = new Sql2Scanner($sql2);
-         $source = null;
-         $columnData = [];
-         $constraint = null;
-         $orderings = [];
-         while ($this->scanner->lookupNextToken() !== '') {
-             switch (strtoupper($this->scanner->lookupNextToken())) {
-                 case 'SELECT':
-                     $this->scanner->expectToken('SELECT');
-                     $columnData = $this->scanColumns();
-                     break;
-                 case 'FROM':
-                     $this->scanner->expectToken('FROM');
-                     $source = $this->parseSource();
-                     break;
-                 case 'WHERE':
-                     $this->scanner->expectToken('WHERE');
-                     $constraint = $this->parseConstraint();
-                     break;
-                 case 'ORDER':
-                     // Ordering, check there is a BY
-                     $this->scanner->expectTokens(['ORDER', 'BY']);
-                     $orderings = $this->parseOrderings();
-                     break;
-                 default:
-                     throw new InvalidQueryException('Error parsing query, unknown query part "'.$this->scanner->lookupNextToken().'" in: '.$this->sql2);
-             }
-         }
-         if (!$source instanceof SourceInterface) {
-             throw new InvalidQueryException('Invalid query, source could not be determined: '.$sql2);
-         }
-         $columns = $this->buildColumns($columnData);
-         return $this->factory->createQuery($source, $constraint, $orderings, $columns);
-     }
-     /**
-      * 6.7.2. Source
-      * Parse an SQL2 source definition and return the corresponding QOM Source.
-      *
-      * @return SourceInterface
-      */
-     protected function parseSource()
-     {
-         $selector = $this->parseSelector();
-         $next = $this->scanner->lookupNextToken();
-         $left = $selector;
-         while (in_array(strtoupper($next), ['JOIN', 'INNER', 'RIGHT', 'LEFT'])) {
-             $left = $this->parseJoin($left);
-             $next = $this->scanner->lookupNextToken();
-         }
-         return $left;
-     }
-     /**
-      * 6.7.3. Selector
-      * Parse an SQL2 selector and return a QOM\SelectorInterface.
-      *
-      * @return SelectorInterface
-      */
-     protected function parseSelector()
-     {
-         $nodetype = $this->fetchTokenWithoutBrackets();
-         if ($this->scanner->tokenIs($this->scanner->lookupNextToken(), 'AS')) {
-             $this->scanner->fetchNextToken(); // Consume the AS
-             $selectorName = $this->parseName();
-             $this->updateImplicitSelectorName($selectorName);
-             return $this->factory->selector($selectorName, $nodetype);
-         }
-         $this->updateImplicitSelectorName($nodetype);
-         return $this->factory->selector($nodetype, $nodetype);
-     }
-     /**
-      * 6.7.4. Name.
-      *
-      * @return string
-      */
-     protected function parseName()
-     {
-         return $this->scanner->fetchNextToken();
-     }
-     /**
-      * 6.7.5. Join
-      * 6.7.6. Join type
-      * Parse an SQL2 join source and return a QOM\Join.
-      *
-      * @param SourceInterface $leftSelector the left selector as it has been read by parseSource
-      *
-      * @return JoinInterface
-      */
-     protected function parseJoin(SourceInterface $leftSelector)
-     {
-         $joinType = $this->parseJoinType();
-         $right = $this->parseSelector();
-         $joinCondition = $this->parseJoinCondition();
-         return $this->factory->join($leftSelector, $right, $joinType, $joinCondition);
-     }
-     /**
-      * 6.7.6. Join type.
-      *
-      * @throws InvalidQueryException
-      *
-      * @return string
-      */
-     protected function parseJoinType()
-     {
-         $joinType = Constants::JCR_JOIN_TYPE_INNER;
-         $token = $this->scanner->fetchNextToken();
-         switch ($token) {
-             case 'JOIN':
-                 // Token already fetched, nothing to do
-                 break;
-             case 'INNER':
-                 $this->scanner->fetchNextToken();
-                 break;
-             case 'LEFT':
-                 $this->scanner->expectTokens(['OUTER', 'JOIN']);
-                 $joinType = Constants::JCR_JOIN_TYPE_LEFT_OUTER;
-                 break;
-             case 'RIGHT':
-                 $this->scanner->expectTokens(['OUTER', 'JOIN']);
-                 $joinType = Constants::JCR_JOIN_TYPE_RIGHT_OUTER;
-                 break;
-             default:
-                 throw new InvalidQueryException("Syntax error: Expected JOIN, INNER JOIN, RIGHT JOIN or LEFT JOIN in '{$this->sql2}'");
-         }
-         return $joinType;
-     }
-     /**
-      * 6.7.7. JoinCondition
-      * Parse an SQL2 join condition and return a JoinConditionInterface.
-      *
-      * @return JoinConditionInterface
-      */
-     protected function parseJoinCondition()
-     {
-         $this->scanner->expectToken('ON');
-         $token = $this->scanner->lookupNextToken();
-         if ($this->scanner->tokenIs($token, 'ISSAMENODE')) {
-             return $this->parseSameNodeJoinCondition();
-         }
-         if ($this->scanner->tokenIs($token, 'ISCHILDNODE')) {
-             return $this->parseChildNodeJoinCondition();
-         }
-         if ($this->scanner->tokenIs($token, 'ISDESCENDANTNODE')) {
-             return $this->parseDescendantNodeJoinCondition();
-         }
-         return $this->parseEquiJoin();
-     }
-     /**
-      * 6.7.8. EquiJoinCondition
-      * Parse an SQL2 equijoin condition and return a EquiJoinConditionInterface.
-      *
-      * @return EquiJoinConditionInterface
-      */
-     protected function parseEquiJoin()
-     {
-         list($selectorName1, $prop1) = $this->parseIdentifier();
-         $this->scanner->expectToken('=');
-         list($selectorName2, $prop2) = $this->parseIdentifier();
-         return $this->factory->equiJoinCondition($selectorName1, $prop1, $selectorName2, $prop2);
-     }
-     /**
-      * 6.7.9 SameNodeJoinCondition
-      * Parse an SQL2 same node join condition and return a SameNodeJoinConditionInterface.
-      *
-      * @return SameNodeJoinConditionInterface
-      */
-     protected function parseSameNodeJoinCondition()
-     {
-         $this->scanner->expectTokens(['ISSAMENODE', '(']);
-         $selectorName1 = $this->fetchTokenWithoutBrackets();
-         $this->scanner->expectToken(',');
-         $selectorName2 = $this->fetchTokenWithoutBrackets();
-         $token = $this->scanner->lookupNextToken();
-         if ($this->scanner->tokenIs($token, ',')) {
-             $this->scanner->fetchNextToken(); // consume the coma
-             $path = $this->parsePath();
-         } else {
-             $path = null;
-         }
-         $this->scanner->expectToken(')');
-         return $this->factory->sameNodeJoinCondition($selectorName1, $selectorName2, $path);
-     }
-     /**
-      * 6.7.10 ChildNodeJoinCondition
-      * Parse an SQL2 child node join condition and return a ChildNodeJoinConditionInterface.
-      *
-      * @return ChildNodeJoinConditionInterface
-      */
-     protected function parseChildNodeJoinCondition()
-     {
-         $this->scanner->expectTokens(['ISCHILDNODE', '(']);
-         $child = $this->fetchTokenWithoutBrackets();
-         $this->scanner->expectToken(',');
-         $parent = $this->fetchTokenWithoutBrackets();
-         $this->scanner->expectToken(')');
-         return $this->factory->childNodeJoinCondition($child, $parent);
-     }
-     /**
-      * 6.7.11 DescendantNodeJoinCondition
-      * Parse an SQL2 descendant node join condition and return a DescendantNodeJoinConditionInterface.
-      *
-      * @return DescendantNodeJoinConditionInterface
-      */
-     protected function parseDescendantNodeJoinCondition()
-     {
-         $this->scanner->expectTokens(['ISDESCENDANTNODE', '(']);
-         $descendant = $this->fetchTokenWithoutBrackets();
-         $this->scanner->expectToken(',');
-         $parent = $this->fetchTokenWithoutBrackets();
-         $this->scanner->expectToken(')');
-         return $this->factory->descendantNodeJoinCondition($descendant, $parent);
-     }
-     /**
-      * 6.7.13 And
-      * 6.7.14 Or.
-      *
-      * @param ConstraintInterface $lhs     Left hand side
-      * @param int                 $minprec Precedence
-      *
-      * @throws Exception
-      *
-      * @return ConstraintInterface
-      */
-     protected function parseConstraint($lhs = null, $minprec = 0)
-     {
-         if ($lhs === null) {
-             $lhs = $this->parsePrimaryConstraint();
-         }
-         $opprec = [
-             'OR'  => 1,
-             'AND' => 2,
-         ];
-         $op = strtoupper($this->scanner->lookupNextToken());
-         while (isset($opprec[$op]) && $opprec[$op] >= $minprec) {
-             $this->scanner->fetchNextToken();
-             $rhs = $this->parsePrimaryConstraint();
-             $nextop = strtoupper($this->scanner->lookupNextToken());
-             while (isset($opprec[$nextop]) && $opprec[$nextop] > $opprec[$op]) {
-                 $rhs = $this->parseConstraint($rhs, $opprec[$nextop]);
-                 $nextop = strtoupper($this->scanner->lookupNextToken());
-             }
-             switch ($op) {
-                 case 'AND':
-                     $lhs = $this->factory->andConstraint($lhs, $rhs);
-                     break;
-                 case 'OR':
-                     $lhs = $this->factory->orConstraint($lhs, $rhs);
-                     break;
-                 default:
-                     // this only happens if the operator is
-                     // in the $opprec-array but there is no
-                     // "elseif"-branch here for this operator.
-                     throw new Exception("Internal error: No action is defined for operator '$op'");
-             }
-             $op = strtoupper($this->scanner->lookupNextToken());
-         }
-         return $lhs;
-     }
-     /**
-      * 6.7.12 Constraint.
-      *
-      * @return ConstraintInterface
-      */
-     protected function parsePrimaryConstraint()
-     {
-         $constraint = null;
-         $token = $this->scanner->lookupNextToken();
-         if ($this->scanner->tokenIs($token, 'NOT')) {
-             // NOT
-             $constraint = $this->parseNot();
-         } elseif ($this->scanner->tokenIs($token, '(')) {
-             // Grouping with parenthesis
-             $this->scanner->expectToken('(');
-             $constraint = $this->parseConstraint();
-             $this->scanner->expectToken(')');
-         } elseif ($this->scanner->tokenIs($token, 'CONTAINS')) {
-             // Full Text Search
-             $constraint = $this->parseFullTextSearch();
-         } elseif ($this->scanner->tokenIs($token, 'ISSAMENODE')) {
-             // SameNode
-             $constraint = $this->parseSameNode();
-         } elseif ($this->scanner->tokenIs($token, 'ISCHILDNODE')) {
-             // ChildNode
-             $constraint = $this->parseChildNode();
-         } elseif ($this->scanner->tokenIs($token, 'ISDESCENDANTNODE')) {
-             // DescendantNode
-             $constraint = $this->parseDescendantNode();
-         } else {
-             // Is it a property existence?
-             $next1 = $this->scanner->lookupNextToken(1);
-             if ($this->scanner->tokenIs($next1, 'IS')) {
-                 $constraint = $this->parsePropertyExistence();
-             } elseif ($this->scanner->tokenIs($next1, '.')) {
-                 $next2 = $this->scanner->lookupNextToken(3);
-                 if ($this->scanner->tokenIs($next2, 'IS')) {
-                     $constraint = $this->parsePropertyExistence();
-                 }
-             }
-             if ($constraint === null) {
-                 // It's not a property existence neither, then it's a comparison
-                 $constraint = $this->parseComparison();
-             }
-         }
-         // No constraint read,
-         if ($constraint === null) {
-             throw new InvalidQueryException("Syntax error: constraint expected in '{$this->sql2}'");
-         }
-         return $constraint;
-     }
-     /**
-      * 6.7.15 Not.
-      *
-      * @return NotInterface
-      */
-     protected function parseNot()
-     {
-         $this->scanner->expectToken('NOT');
-         return $this->factory->notConstraint($this->parsePrimaryConstraint());
-     }
-     /**
-      * 6.7.16 Comparison.
-      *
-      * @throws InvalidQueryException
-      *
-      * @return ComparisonInterface
-      */
-     protected function parseComparison()
-     {
-         $op1 = $this->parseDynamicOperand();
-         if (null === $op1) {
-             throw new InvalidQueryException("Syntax error: dynamic operator expected in '{$this->sql2}'");
-         }
-         $operator = $this->parseOperator();
-         $op2 = $this->parseStaticOperand();
-         return $this->factory->comparison($op1, $operator, $op2);
-     }
-     /**
-      * 6.7.17 Operator.
-      *
-      * @return string a constant from QueryObjectModelConstantsInterface
-      */
-     protected function parseOperator()
-     {
-         $token = $this->scanner->fetchNextToken();
-         switch (strtoupper($token)) {
-             case '=':
-                 return Constants::JCR_OPERATOR_EQUAL_TO;
-             case '<>':
-                 return Constants::JCR_OPERATOR_NOT_EQUAL_TO;
-             case '<':
-                 return Constants::JCR_OPERATOR_LESS_THAN;
-             case '<=':
-                 return Constants::JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO;
-             case '>':
-                 return Constants::JCR_OPERATOR_GREATER_THAN;
-             case '>=':
-                 return Constants::JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO;
-             case 'LIKE':
-                 return Constants::JCR_OPERATOR_LIKE;
-         }
-         throw new InvalidQueryException("Syntax error: operator expected in '{$this->sql2}'");
-     }
-     /**
-      * 6.7.18 PropertyExistence.
-      *
-      * @return ConstraintInterface
-      */
-     protected function parsePropertyExistence()
-     {
-         list($selectorName, $prop) = $this->parseIdentifier();
-         $this->scanner->expectToken('IS');
-         $token = $this->scanner->lookupNextToken();
-         if ($this->scanner->tokenIs($token, 'NULL')) {
-             $this->scanner->fetchNextToken();
-             return $this->factory->notConstraint($this->factory->propertyExistence($selectorName, $prop));
-         }
-         $this->scanner->expectTokens(['NOT', 'NULL']);
-         return $this->factory->propertyExistence($selectorName, $prop);
-     }
-     /**
-      * 6.7.19 FullTextSearch.
-      *
-      * @return FullTextSearchInterface
-      */
-     protected function parseFullTextSearch()
-     {
-         $this->scanner->expectTokens(['CONTAINS', '(']);
-         list($selectorName, $propertyName) = $this->parseIdentifier();
-         $this->scanner->expectToken(',');
-         $expression = $this->parseLiteralValue();
-         $this->scanner->expectToken(')');
-         return $this->factory->fullTextSearch($selectorName, $propertyName, $expression);
-     }
-     /**
-      * 6.7.20 SameNode.
-      */
-     protected function parseSameNode()
-     {
-         $this->scanner->expectTokens(['ISSAMENODE', '(']);
-         if ($this->scanner->tokenIs($this->scanner->lookupNextToken(1), ',')) {
-             $selectorName = $this->scanner->fetchNextToken();
-             $this->scanner->expectToken(',');
-             $path = $this->parsePath();
-         } else {
-             $selectorName = $this->implicitSelectorName;
-             $path = $this->parsePath();
-         }
-         $this->scanner->expectToken(')');
-         return $this->factory->sameNode($selectorName, $path);
-     }
-     /**
-      * 6.7.21 ChildNode.
-      */
-     protected function parseChildNode()
-     {
-         $this->scanner->expectTokens(['ISCHILDNODE', '(']);
-         if ($this->scanner->tokenIs($this->scanner->lookupNextToken(1), ',')) {
-             $selectorName = $this->scanner->fetchNextToken();
-             $this->scanner->expectToken(',');
-             $path = $this->parsePath();
-         } else {
-             $selectorName = $this->implicitSelectorName;
-             $path = $this->parsePath();
-         }
-         $this->scanner->expectToken(')');
-         return $this->factory->childNode($selectorName, $path);
-     }
-     /**
-      * 6.7.22 DescendantNode.
-      */
-     protected function parseDescendantNode()
-     {
-         $this->scanner->expectTokens(['ISDESCENDANTNODE', '(']);
-         if ($this->scanner->tokenIs($this->scanner->lookupNextToken(1), ',')) {
-             $selectorName = $this->scanner->fetchNextToken();
-             $this->scanner->expectToken(',');
-             $path = $this->parsePath();
-         } else {
-             $selectorName = $this->implicitSelectorName;
-             $path = $this->parsePath();
-         }
-         $this->scanner->expectToken(')');
-         return $this->factory->descendantNode($selectorName, $path);
-     }
-     /**
-      * Parse a JCR path consisting of either a simple path (a JCR name that contains
-      * only SQL-legal characters) or a path (simple path or quoted path) enclosed in
-      * square brackets. See JCR Spec Â§ 6.7.23.
-      *
-      * 6.7.23. Path
-      */
-     protected function parsePath()
-     {
-         $path = $this->parseLiteralValue();
-         if (substr($path, 0, 1) === '[' && substr($path, -1) === ']') {
-             $path = substr($path, 1, -1);
-         }
-         return $path;
-     }
-     /**
-      * Parse an SQL2 static operand
-      * 6.7.35 BindVariable
-      * 6.7.36 Prefix.
-      *
-      * @return StaticOperandInterface
-      */
-     protected function parseStaticOperand()
-     {
-         $token = $this->scanner->lookupNextToken();
-         if (substr($token, 0, 1) === '$') {
-             return $this->factory->bindVariable(substr($this->scanner->fetchNextToken(), 1));
-         }
-         return $this->factory->literal($this->parseLiteralValue());
-     }
-     /**
-      * 6.7.26 DynamicOperand
-      * 6.7.28 Length
-      * 6.7.29 NodeName
-      * 6.7.30 NodeLocalName
-      * 6.7.31 FullTextSearchScore
-      * 6.7.32 LowerCase
-      * 6.7.33 UpperCase
-      * Parse an SQL2 dynamic operand.
-      *
-      * @return DynamicOperandInterface
-      */
-     protected function parseDynamicOperand()
-     {
-         $token = $this->scanner->lookupNextToken();
-         if ($this->scanner->tokenIs($token, 'LENGTH')) {
-             $this->scanner->fetchNextToken();
-             $this->scanner->expectToken('(');
-             $val = $this->parsePropertyValue();
-             $this->scanner->expectToken(')');
-             return $this->factory->length($val);
-         }
-         if ($this->scanner->tokenIs($token, 'NAME')) {
-             $this->scanner->fetchNextToken();
-             $this->scanner->expectToken('(');
-             $token = $this->scanner->fetchNextToken();
-             if ($this->scanner->tokenIs($token, ')')) {
-                 return $this->factory->nodeName($this->implicitSelectorName);
-             }
-             $this->scanner->expectToken(')');
-             return $this->factory->nodeName($token);
-         }
-         if ($this->scanner->tokenIs($token, 'LOCALNAME')) {
-             $this->scanner->fetchNextToken();
-             $this->scanner->expectToken('(');
-             $token = $this->scanner->fetchNextToken();
-             if ($this->scanner->tokenIs($token, ')')) {
-                 return $this->factory->nodeLocalName($this->implicitSelectorName);
-             }
-             $this->scanner->expectToken(')');
-             return $this->factory->nodeLocalName($token);
-         }
-         if ($this->scanner->tokenIs($token, 'SCORE')) {
-             $this->scanner->fetchNextToken();
-             $this->scanner->expectToken('(');
-             $token = $this->scanner->fetchNextToken();
-             if ($this->scanner->tokenIs($token, ')')) {
-                 return $this->factory->fullTextSearchScore($this->implicitSelectorName);
-             }
-             $this->scanner->expectToken(')');
-             return $this->factory->fullTextSearchScore($token);
-         }
-         if ($this->scanner->tokenIs($token, 'LOWER')) {
-             $this->scanner->fetchNextToken();
-             $this->scanner->expectToken('(');
-             $op = $this->parseDynamicOperand();
-             $this->scanner->expectToken(')');
-             return $this->factory->lowerCase($op);
-         }
-         if ($this->scanner->tokenIs($token, 'UPPER')) {
-             $this->scanner->fetchNextToken();
-             $this->scanner->expectToken('(');
-             $op = $this->parseDynamicOperand();
-             $this->scanner->expectToken(')');
-             return $this->factory->upperCase($op);
-         }
-         return $this->parsePropertyValue();
-     }
-     /**
-      * 6.7.27 PropertyValue
-      * Parse an SQL2 property value.
-      *
-      * @return PropertyValueInterface
-      */
-     protected function parsePropertyValue()
-     {
-         list($selectorName, $prop) = $this->parseIdentifier();
-         return $this->factory->propertyValue($selectorName, $prop);
-     }
-     protected function parseCastLiteral($token)
-     {
-         if (!$this->scanner->tokenIs($token, 'CAST')) {
-             throw new LogicException('parseCastLiteral when not a CAST');
-         }
-         $this->scanner->expectToken('(');
-         $token = $this->scanner->fetchNextToken();
-         $quoteString = in_array($token[0], ['\'', '"'], true);
-         if ($quoteString) {
-             $quotesUsed = $token[0];
-             $token = substr($token, 1, -1);
-             // Un-escaping quotes
-             $token = str_replace('\\'.$quotesUsed, $quotesUsed, $token);
-         }
-         $this->scanner->expectToken('AS');
-         $type = $this->scanner->fetchNextToken();
-         try {
-             $typeValue = PropertyType::valueFromName($type);
-         } catch (InvalidArgumentException $e) {
-             throw new InvalidQueryException("Syntax error: attempting to cast to an invalid type '$type'");
-         }
-         $this->scanner->expectToken(')');
-         try {
-             $token = $this->valueConverter->convertType($token, $typeValue, PropertyType::STRING);
-         } catch (Exception $e) {
-             throw new InvalidQueryException("Syntax error: attempting to cast string '$token' to type '$type'");
-         }
-         return $token;
-     }
-     /**
-      * 6.7.34 Literal
-      * Parse an SQL2 literal value.
-      *
-      * @return mixed
-      */
-     protected function parseLiteralValue()
-     {
-         $token = $this->scanner->fetchNextToken();
-         if ($this->scanner->tokenIs($token, 'CAST')) {
-             return $this->parseCastLiteral($token);
-         }
-         $quoteString = in_array($token[0], ['"', "'"], true);
-         if ($quoteString) {
-             $quotesUsed = $token[0];
-             $token = substr($token, 1, -1);
-             // Unescape quotes
-             $token = str_replace('\\'.$quotesUsed, $quotesUsed, $token);
-             $token = str_replace("''", "'", $token);
-             if (preg_match('/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d+)?$/', $token)) {
-                 if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $token)) {
-                     $token .= ' 00:00:00';
-                 }
-                 $token = DateTime::createFromFormat('Y-m-d H:i:s', $token);
-             }
-         } elseif (is_numeric($token)) {
-             $token = strpos($token, '.') === false ? (int) $token : (float) $token;
-         } elseif ($token === 'true') {
-             $token = true;
-         } elseif ($token === 'false') {
-             $token = false;
-         }
-         return $token;
-     }
-     /**
-      * 6.7.37 Ordering.
-      */
-     protected function parseOrderings()
-     {
-         $orderings = [];
-         $continue = true;
-         while ($continue) {
-             $orderings[] = $this->parseOrdering();
-             if ($this->scanner->tokenIs($this->scanner->lookupNextToken(), ',')) {
-                 $this->scanner->expectToken(',');
-             } else {
-                 $continue = false;
-             }
-         }
-         return $orderings;
-     }
-     /**
-      * 6.7.38 Order.
-      *
-      * @return OrderingInterface
-      */
-     protected function parseOrdering()
-     {
-         $operand = $this->parseDynamicOperand();
-         $token = $this->scanner->lookupNextToken();
-         if ($this->scanner->tokenIs($token, 'DESC')) {
-             $this->scanner->expectToken('DESC');
-             return $this->factory->descending($operand);
-         }
-         if ($this->scanner->tokenIs($token, 'ASC') || ',' === $token || '' === $token) {
-             if ($this->scanner->tokenIs($token, 'ASC')) {
-                 $this->scanner->expectToken('ASC');
-             }
-             return $this->factory->ascending($operand);
-         }
-         throw new InvalidQueryException("Syntax error: invalid ordering in '{$this->sql2}'");
-     }
-     /**
-      * 6.7.39 Column.
-      *
-      * Scan the SQL2 columns definitions and return data arrays to convert to
-      * columns once the FROM is parsed.
-      *
-      * @return array of array
-      */
-     protected function scanColumns()
-     {
-         // Wildcard
-         if ($this->scanner->lookupNextToken() === '*') {
-             $this->scanner->fetchNextToken();
-             return [];
-         }
-         $columns = [];
-         $hasNext = true;
-         while ($hasNext) {
-             $columns[] = $this->scanColumn();
-             // Are there more columns?
-             if ($this->scanner->lookupNextToken() !== ',') {
-                 $hasNext = false;
-             } else {
-                 $this->scanner->fetchNextToken();
-             }
-         }
-         return $columns;
-     }
-     /**
-      * Build the columns from the scanned column data.
-      *
-      * @param array $data
-      *
-      * @return ColumnInterface[]
-      */
-     protected function buildColumns($data)
-     {
-         $columns = [];
-         foreach ($data as $col) {
-             $columns[] = $this->buildColumn($col);
-         }
-         return $columns;
-     }
-     /**
-      * Get the next token and make sure to remove the brackets if the token is
-      * in the [ns:name] notation.
-      *
-      * @return string
-      */
-     private function fetchTokenWithoutBrackets()
-     {
-         $token = $this->scanner->fetchNextToken();
-         if (substr($token, 0, 1) === '[' && substr($token, -1) === ']') {
-             // Remove brackets around the selector name
-             $token = substr($token, 1, -1);
-         }
-         return $token;
-     }
-     /**
-      * Parse something that is expected to be a property identifier.
-      *
-      * @param bool $checkSelector whether we need to ensure a valid selector.
-      *
-      * @return array with selectorName and propertyName. If no selectorName is
-      *               specified, defaults to $this->defaultSelectorName
-      */
-     private function parseIdentifier($checkSelector = true)
-     {
-         $token = $this->fetchTokenWithoutBrackets();
-         // selector.property
-         if ($this->scanner->lookupNextToken() === '.') {
-             $selectorName = $token;
-             $this->scanner->fetchNextToken();
-             $propertyName = $this->fetchTokenWithoutBrackets();
-         } else {
-             $selectorName = null;
-             $propertyName = $token;
-         }
-         if ($checkSelector) {
-             $selectorName = $this->ensureSelectorName($selectorName);
-         }
-         return [$selectorName, $propertyName];
-     }
-     /**
-      * Add a selector name to the known selector names.
-      *
-      * @param string $selectorName
-      *
-      * @throws InvalidQueryException
-      */
-     protected function updateImplicitSelectorName($selectorName)
-     {
-         if (null === $this->implicitSelectorName) {
-             $this->implicitSelectorName = $selectorName;
-         } else {
-             if (!is_array($this->implicitSelectorName)) {
-                 $this->implicitSelectorName = [$this->implicitSelectorName => $this->implicitSelectorName];
-             }
-             if (isset($this->implicitSelectorName[$selectorName])) {
-                 throw new InvalidQueryException("Selector $selectorName is already in use");
-             }
-             $this->implicitSelectorName[$selectorName] = $selectorName;
-         }
-     }
-     /**
-      * Ensure that the parsedName is a valid selector, or return the implicit
-      * selector if its non-ambigous.
-      *
-      * @param string|null $parsedName
-      *
-      * @throws InvalidQueryException if there was no explicit selector and
-      *                               there is more than one selector available.
-      *
-      * @return string the selector to use
-      */
-     protected function ensureSelectorName($parsedName)
-     {
-         if (null !== $parsedName) {
-             if (is_array($this->implicitSelectorName) && !isset($this->implicitSelectorName[$parsedName])
-                 || !is_array($this->implicitSelectorName) && $this->implicitSelectorName !== $parsedName
-             ) {
-                 throw new InvalidQueryException("Unknown selector $parsedName in '{$this->sql2}'");
-             }
-             return $parsedName;
-         }
-         if (is_array($this->implicitSelectorName)) {
-             throw new InvalidQueryException('Need an explicit selector name in join queries');
-         }
-         return $this->implicitSelectorName;
-     }
-     /**
-      * Scan a single SQL2 column definition and return an array of information.
-      *
-      * @return array
-      */
-     protected function scanColumn()
-     {
-         list($selectorName, $propertyName) = $this->parseIdentifier(false);
-         // AS name
-         if ($this->scanner->tokenIs($this->scanner->lookupNextToken(), 'AS')) {
-             $this->scanner->fetchNextToken();
-             $columnName = $this->scanner->fetchNextToken();
-         } else {
-             $columnName = $propertyName;
-         }
-         return [$selectorName, $propertyName, $columnName];
-     }
-     /**
-      * Build a single SQL2 column definition.
-      *
-      * @param array $data with selector name, property name and column name.
-      *
-      * @return ColumnInterface
-      */
-     protected function buildColumn(array $data)
-     {
-         list($selectorName, $propertyName, $columnName) = $data;
-         $selectorName = $this->ensureSelectorName($selectorName);
-         return $this->factory->column($selectorName, $propertyName, $columnName);
-     }
- }
-