vendor/jackalope/jackalope-doctrine-dbal/src/Jackalope/Transport/DoctrineDBAL/Query/QOMWalker.php line 202
<?phpnamespace Jackalope\Transport\DoctrineDBAL\Query;use BadMethodCallException;use DateTime;use DateTimeZone;use Doctrine\DBAL\Platforms\MySQLPlatform;use Doctrine\DBAL\Platforms\PostgreSqlPlatform;use Doctrine\DBAL\Platforms\PostgreSQL94Platform;use Doctrine\DBAL\Schema\Schema;use Jackalope\NotImplementedException;use Jackalope\Query\QOM\PropertyValue;use Jackalope\Query\QOM\QueryObjectModel;use Jackalope\Transport\DoctrineDBAL\RepositorySchema;use Jackalope\Transport\DoctrineDBAL\Util\Xpath;use PHPCR\NamespaceException;use PHPCR\NodeType\NodeTypeInterface;use PHPCR\NodeType\NodeTypeManagerInterface;use PHPCR\Query\InvalidQueryException;use PHPCR\Query\QOM;use Doctrine\DBAL\Connection;use Doctrine\DBAL\Platforms\AbstractPlatform;use Doctrine\DBAL\Platforms\SqlitePlatform;/*** Converts QOM to SQL Statements for the Doctrine DBAL database backend.** @license http://www.apache.org/licenses Apache License Version 2.0, January 2004* @license http://opensource.org/licenses/MIT MIT License*/class QOMWalker{/*** @var NodeTypeManagerInterface*/private $nodeTypeManager;/*** @var array*/private $alias = [];/*** @var QOM\SelectorInterface*/private $source;/*** @var Connection*/private $conn;/*** @var AbstractPlatform*/private $platform;/*** @var array*/private $namespaces;/*** @var Schema*/private $schema;/*** @param NodeTypeManagerInterface $manager* @param Connection $conn* @param array $namespaces*/public function __construct(NodeTypeManagerInterface $manager, Connection $conn, array $namespaces = []){$this->conn = $conn;$this->nodeTypeManager = $manager;$this->platform = $conn->getDatabasePlatform();$this->namespaces = $namespaces;$this->schema = new RepositorySchema([], $this->conn);}/*** Generate a table alias** @param string $selectorName** @return string*/private function getTableAlias($selectorName){$selectorAlias = $this->getSelectorAlias($selectorName);if (!isset($this->alias[$selectorAlias])) {$this->alias[$selectorAlias] = 'n' . count($this->alias);}return $this->alias[$selectorAlias];}/*** @param string $selectorName** @return string*/private function getSelectorAlias($selectorName){if (null === $selectorName) {if (count($this->alias)) { // We have aliases, use the first$selectorAlias = array_search('n0', $this->alias);} else { // Currently no aliases, use an empty string as index$selectorAlias = '';}} elseif (strpos($selectorName, '.') === false) {$selectorAlias = $selectorName;} else {$parts = explode('.', $selectorName);$selectorAlias = reset($parts);}if (strpos($selectorAlias, '[') === 0) {$selectorAlias = substr($selectorAlias, 1, -1);}if ($this->source && $this->source->getNodeTypeName() === $selectorAlias) {$selectorAlias = '';}return $selectorAlias;}/*** @param QueryObjectModel $qom** @return string*/public function walkQOMQuery(QueryObjectModel $qom){$source = $qom->getSource();$selectors = $this->validateSource($source);$sourceSql = ' ' . $this->walkSource($source);$constraintSql = '';if ($constraint = $qom->getConstraint()) {$constraintSql = ' AND ' . $this->walkConstraint($constraint);}$orderingSql = '';if ($orderings = $qom->getOrderings()) {$orderingSql = ' ' . $this->walkOrderings($orderings);}$sql = 'SELECT ' . $this->getColumns($qom);$sql .= $sourceSql;$sql .= $constraintSql;$sql .= $orderingSql;$limit = $qom->getLimit();$offset = $qom->getOffset();if (null !== $offset && null === $limit&& ($this->platform instanceof MySQLPlatform || $this->platform instanceof SqlitePlatform)) {$limit = PHP_INT_MAX;}$sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);return [$selectors, $this->alias, $sql];}/*** @return string*/public function getColumns(QueryObjectModel $qom){// TODO we should actually build Xpath statements for each column we actually need in the result and not fetch all 'props'$sqlColumns = ['path', 'identifier', 'props'];if (count($this->alias)) {$aliasSql = [];foreach ($this->alias as $alias) {foreach ($sqlColumns as $sqlColumn) {$aliasSql[] = sprintf('%s.%s AS %s_%s', $alias, $sqlColumn, $alias, $sqlColumn);}}return implode(', ', $aliasSql);}return '*';}/*** Validates the nodeTypes in given source** @param QOM\SourceInterface $source** @return QOM\SelectorInterface[]** @throws InvalidQueryException*/protected function validateSource(QOM\SourceInterface $source){if ($source instanceof QOM\SelectorInterface) {$selectors = [$source];$this->validateSelectorSource($source);} elseif ($source instanceof QOM\JoinInterface) {$selectors = $this->validateJoinSource($source);} else {$selectors = [];}return $selectors;}/*** @param QOM\SelectorInterface $source** @throws InvalidQueryException*/protected function validateSelectorSource(QOM\SelectorInterface $source){$nodeType = $source->getNodeTypeName();if (!$this->nodeTypeManager->hasNodeType($nodeType)) {$msg = 'Selected node type does not exist: ' . $nodeType;if ($alias = $source->getSelectorName()) {$msg .= ' AS ' . $alias;}throw new InvalidQueryException($msg);}}/*** @param QOM\JoinInterface $source** @return QOM\SelectorInterface[]** @throws InvalidQueryException*/protected function validateJoinSource(QOM\JoinInterface $source){$left = $source->getLeft();$right = $source->getRight();if ($left) {$selectors = $this->validateSource($left);} else {$selectors = [];}if ($right) {// Ensure that the primary selector is firstif (QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER === $source->getJoinType()) {$selectors = array_merge($this->validateSource($right), $selectors);} else {$selectors = array_merge($selectors, $this->validateSource($right));}}return $selectors;}/*** @param QOM\SourceInterface $source** @return string** @throws NotImplementedException*/public function walkSource(QOM\SourceInterface $source){if ($source instanceof QOM\SelectorInterface) {return $this->walkSelectorSource($source);}if ($source instanceof QOM\JoinInterface) {return $this->walkJoinSource($source);}throw new NotImplementedException(sprintf("The source class '%s' is not supported", get_class($source)));}/*** @param QOM\SelectorInterface $source** @return string*/public function walkSelectorSource(QOM\SelectorInterface $source){$this->source = $source;$alias = $this->getTableAlias($source->getSelectorName());$nodeTypeClause = $this->sqlNodeTypeClause($alias, $source);$sql = "FROM phpcr_nodes $alias WHERE $alias.workspace_name = ? AND $nodeTypeClause";return $sql;}/*** @param QOM\JoinConditionInterface $right** @return string the alias on the right side of a join** @throws BadMethodCallException if the provided JoinCondition has no valid way of getting the right selector*/private function getRightJoinSelector(QOM\JoinConditionInterface $right){if ($right instanceof QOM\ChildNodeJoinConditionInterface) {return $right->getParentSelectorName();} elseif ($right instanceof QOM\DescendantNodeJoinConditionInterface) {return $right->getAncestorSelectorName();} elseif ($right instanceof QOM\SameNodeJoinConditionInterface || $right instanceof QOM\EquiJoinConditionInterface) {return $right->getSelector2Name();}throw new BadMethodCallException('Supplied join type should implement getSelector2Name() or be an instance of ChildNodeJoinConditionInterface or DescendantNodeJoinConditionInterface');}/*** @param QOM\JoinConditionInterface $right** @return string the alias on the left side of a join** @throws BadMethodCallException if the provided JoinCondition has no valid way of getting the left selector*/private function getLeftJoinSelector(QOM\JoinConditionInterface $left){if ($left instanceof QOM\ChildNodeJoinConditionInterface) {return $left->getChildSelectorName();} elseif ($left instanceof QOM\DescendantNodeJoinConditionInterface) {return $left->getAncestorSelectorName();} elseif ($left instanceof QOM\SameNodeJoinConditionInterface || $left instanceof QOM\EquiJoinConditionInterface) {return $left->getSelector1Name();}throw new BadMethodCallException('Supplied join type should implement getSelector2Name() or be an instance of ChildNodeJoinConditionInterface or DescendantNodeJoinConditionInterface');}/*** find the most left join in a tree** @param QOM\JoinInterface $source** @return QOM\JoinInterface*/private function getLeftMostJoin(QOM\JoinInterface $source){if ($source->getLeft() instanceof QOM\JoinInterface) {return $this->getLeftMostJoin($source->getLeft());}return $source;}/*** @param QOM\JoinInterface $source* @param boolean $root whether the method call is recursed for nested joins. If true, it will add a WHERE clause* that checks the workspace_name and type** @return string** @throws NotImplementedException if the right side of the join consists of another join*/public function walkJoinSource(QOM\JoinInterface $source, $root = true){$this->source = $left = $source->getLeft(); // The $left variable is used for storing the leftmost selectorif (!$source->getRight() instanceof QOM\SelectorInterface) {throw new NotImplementedException('The right side of the join should not consist of another join');}if ($source->getLeft() instanceof QOM\SelectorInterface) {$leftAlias = $this->getTableAlias($source->getLeft()->getSelectorName());$this->getTableAlias($source->getLeft()->getSelectorName());$sql = "FROM phpcr_nodes $leftAlias ";} else {$sql = $this->walkJoinSource($left, false) . ' '; // One step left, until we're at the selector$leftMostJoin = $this->getLeftMostJoin($source);$leftAlias = $this->getTableAlias($this->getLeftJoinSelector($leftMostJoin->getJoinCondition()));$left = $leftMostJoin->getLeft();}$rightAlias = $this->getTableAlias($source->getRight()->getSelectorName());$nodeTypeClause = $this->sqlNodeTypeClause($rightAlias, $source->getRight());switch ($source->getJoinType()) {case QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER:$sql .= sprintf("INNER JOIN phpcr_nodes %s ", $rightAlias);break;case QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER:$sql .= sprintf("LEFT JOIN phpcr_nodes %s ", $rightAlias);break;case QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER:$sql .= sprintf("RIGHT JOIN phpcr_nodes %s ", $rightAlias);break;}$sql .= sprintf("ON ( %s.workspace_name = %s.workspace_name AND %s ", $leftAlias, $rightAlias, $nodeTypeClause);$sql .= 'AND ' . $this->walkJoinCondition($source->getLeft(), $source->getRight(), $source->getJoinCondition()) . ' ';$sql .= ') '; // close on-clauseif ($root) { // The method call is not recursed when $root is true, so we can add a WHERE clause// TODO: revise this part for alternatives$sql .= sprintf("WHERE %s.workspace_name = ? AND %s.type IN ('%s'", $leftAlias, $leftAlias, $left->getNodeTypeName());$subTypes = $this->nodeTypeManager->getSubtypes($left->getNodeTypeName());foreach ($subTypes as $subType) {/* @var $subType NodeTypeInterface */$sql .= sprintf(", '%s'", $subType->getName());}$sql .= ')';}return $sql;}/*** @param QOM\SelectorInterface|QOM\JoinInterface $left* @param QOM\SelectorInterface $right* @param QOM\JoinConditionInterface $condition** @return string** @throws NotImplementedException if a SameNodeJoinCondtion is used.*/public function walkJoinCondition($left, QOM\SelectorInterface $right, QOM\JoinConditionInterface $condition){if ($condition instanceof QOM\ChildNodeJoinConditionInterface) {return $this->walkChildNodeJoinCondition($condition);}if ($condition instanceof QOM\DescendantNodeJoinConditionInterface) {return $this->walkDescendantNodeJoinCondition($condition);}if ($condition instanceof QOM\EquiJoinConditionInterface) {if ($left instanceof QOM\SelectorInterface) {$selectorName = $left->getSelectorName();} else {$selectorName = $this->getLeftJoinSelector($this->getLeftMostJoin($left)->getJoinCondition());}return $this->walkEquiJoinCondition($selectorName, $right->getSelectorName(), $condition);}if ($condition instanceof QOM\SameNodeJoinConditionInterface) {throw new NotImplementedException('SameNodeJoinCondtion');}}/*** @param QOM\ChildNodeJoinConditionInterface $condition** @return string*/public function walkChildNodeJoinCondition(QOM\ChildNodeJoinConditionInterface $condition){$rightAlias = $this->getTableAlias($condition->getChildSelectorName());$leftAlias = $this->getTableAlias($condition->getParentSelectorName());$concatExpression = $this->platform->getConcatExpression("$leftAlias.path", "'/%'");return sprintf("(%s.path LIKE %s AND %s.depth = %s.depth + 1) ", $rightAlias, $concatExpression, $rightAlias, $leftAlias);}/*** @param QOM\DescendantNodeJoinConditionInterface $condition** @return string*/public function walkDescendantNodeJoinCondition(QOM\DescendantNodeJoinConditionInterface $condition){$rightAlias = $this->getTableAlias($condition->getDescendantSelectorName());$leftAlias = $this->getTableAlias($condition->getAncestorSelectorName());$concatExpression = $this->platform->getConcatExpression("$leftAlias.path", "'/%'");return sprintf("%s.path LIKE %s ", $rightAlias, $concatExpression);}/*** @param QOM\EquiJoinConditionInterface $condition** @return string*/public function walkEquiJoinCondition($leftSelectorName, $rightSelectorName, QOM\EquiJoinConditionInterface $condition){return $this->walkOperand(new PropertyValue($leftSelectorName, $condition->getProperty1Name())) . ' ' .$this->walkOperator(QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO) . ' ' .$this->walkOperand(new PropertyValue($rightSelectorName, $condition->getProperty2Name()));}/*** @param \PHPCR\Query\QOM\ConstraintInterface $constraint** @return string** @throws InvalidQueryException*/public function walkConstraint(QOM\ConstraintInterface $constraint){if ($constraint instanceof QOM\AndInterface) {return $this->walkAndConstraint($constraint);}if ($constraint instanceof QOM\OrInterface) {return $this->walkOrConstraint($constraint);}if ($constraint instanceof QOM\NotInterface) {return $this->walkNotConstraint($constraint);}if ($constraint instanceof QOM\ComparisonInterface) {return $this->walkComparisonConstraint($constraint);}if ($constraint instanceof QOM\DescendantNodeInterface) {return $this->walkDescendantNodeConstraint($constraint);}if ($constraint instanceof QOM\ChildNodeInterface) {return $this->walkChildNodeConstraint($constraint);}if ($constraint instanceof QOM\PropertyExistenceInterface) {return $this->walkPropertyExistenceConstraint($constraint);}if ($constraint instanceof QOM\SameNodeInterface) {return $this->walkSameNodeConstraint($constraint);}if ($constraint instanceof QOM\FullTextSearchInterface) {return $this->walkFullTextSearchConstraint($constraint);}throw new InvalidQueryException(sprintf("Constraint %s not yet supported.", get_class($constraint)));}/*** @param QOM\SameNodeInterface $constraint** @return string*/public function walkSameNodeConstraint(QOM\SameNodeInterface $constraint){return sprintf("%s.path = '%s'",$this->getTableAlias($constraint->getSelectorName()),$constraint->getPath());}/*** @param QOM\FullTextSearchInterface $constraint** @return string*/public function walkFullTextSearchConstraint(QOM\FullTextSearchInterface $constraint){return sprintf('%s LIKE %s',$this->sqlXpathExtractValue($this->getTableAlias($constraint->getSelectorName()), $constraint->getPropertyName()),$this->conn->quote('%'.$constraint->getFullTextSearchExpression().'%'));}/*** @param QOM\PropertyExistenceInterface $constraint** @return string*/public function walkPropertyExistenceConstraint(QOM\PropertyExistenceInterface $constraint){return $this->sqlXpathValueExists($this->getTableAlias($constraint->getSelectorName()), $constraint->getPropertyName());}/*** @param QOM\DescendantNodeInterface $constraint** @return string*/public function walkDescendantNodeConstraint(QOM\DescendantNodeInterface $constraint){$ancestorPath = $constraint->getAncestorPath();if ('/' === $ancestorPath) {$ancestorPath = '';} elseif (substr($ancestorPath, -1) === '/') {throw new InvalidQueryException("Trailing slash in $ancestorPath");}return sprintf("%s.path LIKE '%s/%%'",$this->getTableAlias($constraint->getSelectorName()),addcslashes($ancestorPath, "'"));}/*** @param QOM\ChildNodeInterface $constraint** @return string*/public function walkChildNodeConstraint(QOM\ChildNodeInterface $constraint){return sprintf("%s.parent = '%s'",$this->getTableAlias($constraint->getSelectorName()),addcslashes($constraint->getParentPath(), "'"));}/*** @param QOM\AndInterface $constraint** @return string*/public function walkAndConstraint(QOM\AndInterface $constraint){return sprintf("(%s AND %s)",$this->walkConstraint($constraint->getConstraint1()),$this->walkConstraint($constraint->getConstraint2()));}/*** @param QOM\OrInterface $constraint** @return string*/public function walkOrConstraint(QOM\OrInterface $constraint){return sprintf("(%s OR %s)",$this->walkConstraint($constraint->getConstraint1()),$this->walkConstraint($constraint->getConstraint2()));}/*** @param QOM\NotInterface $constraint** @return string*/public function walkNotConstraint(QOM\NotInterface $constraint){return sprintf("NOT (%s)",$this->walkConstraint($constraint->getConstraint()));}/*** This method figures out the best way to do a comparison* When we need to compare a property with a literal value,* we need to be aware of the multivalued properties, we then require* a different xpath statement then with other comparisons** @param QOM\ComparisonInterface $constraint** @return string*/public function walkComparisonConstraint(QOM\ComparisonInterface $constraint){$operator = $this->walkOperator($constraint->getOperator());$operator1 = $constraint->getOperand1();$operator2 = $constraint->getOperand2();// Check if we have a property and a literal value (in random order)if (($operator1 instanceof QOM\PropertyValueInterface&& $operator2 instanceof QOM\LiteralInterface)|| ($operator1 instanceof QOM\LiteralInterface&& $operator2 instanceof QOM\PropertyValueInterface)|| ($operator1 instanceof QOM\NodeNameInterface&& $operator2 instanceof QOM\LiteralInterface)|| ($operator1 instanceof QOM\LiteralInterface&& $operator2 instanceof QOM\NodeNameInterface)) {// Check whether the left is the literal, at this point the other always is the literal/nodename operandif ($operator1 instanceof QOM\LiteralInterface) {$operand = $operator2;$literalOperand = $operator1;} else {$literalOperand = $operator2;$operand = $operator1;}if (is_string($literalOperand->getLiteralValue()) && '=' !== $operator && '!=' !== $operator) {return$this->walkOperand($operator1) . ' ' .$operator . ' ' .$this->walkOperand($operator2);}if ($operand instanceof QOM\NodeNameInterface) {$selectorName = $operand->getSelectorName();$alias = $this->getTableAlias($selectorName);$literal = $literalOperand->getLiteralValue();if (false !== strpos($literal, ':')) {$parts = explode(':', $literal);if (!isset($this->namespaces[$parts[0]])) {throw new NamespaceException('The namespace ' . $parts[0] . ' was not registered.');}$parts[0] = $this->namespaces[$parts[0]];$literal = implode(':', $parts);}return sprintf('%s %s %s',$this->platform->getConcatExpression(sprintf("%s.namespace", $alias),sprintf("(CASE %s.namespace WHEN '' THEN '' ELSE ':' END)", $alias),sprintf("%s.local_name", $alias)),$operator,$this->conn->quote($literal)) ;}if ('jcr:path' !== $operand->getPropertyName() && 'jcr:uuid' !== $operand->getPropertyName()) {if (is_int($literalOperand->getLiteralValue()) || is_float($literalOperand->getLiteralValue())) {return $this->walkNumComparisonConstraint($operand, $literalOperand, $operator);}if (is_bool($literalOperand->getLiteralValue())) {return $this->walkBoolComparisonConstraint($operand, $literalOperand, $operator);}return $this->walkTextComparisonConstraint($operand, $literalOperand, $operator);}}return sprintf('%s %s %s',$this->walkOperand($operator1),$operator,$this->walkOperand($operator2));}/*** @param QOM\PropertyValueInterface $propertyOperand* @param QOM\LiteralInterface $literalOperand* @param string $operator** @return string*/public function walkTextComparisonConstraint(QOM\PropertyValueInterface $propertyOperand, QOM\LiteralInterface $literalOperand, $operator){$alias = $this->getTableAlias($propertyOperand->getSelectorName() . '.' . $propertyOperand->getPropertyName());$property = $propertyOperand->getPropertyName();return $this->sqlXpathComparePropertyValue($alias, $property, $this->getLiteralValue($literalOperand), $operator);}public function walkBoolComparisonConstraint(QOM\PropertyValueInterface $propertyOperand, QOM\LiteralInterface $literalOperand, $operator){$value = true === $literalOperand->getLiteralValue() ? '1' : '0';return $this->walkOperand($propertyOperand) . ' ' . $operator . ' ' . $this->conn->quote($value);}public function walkNumComparisonConstraint(QOM\PropertyValueInterface $propertyOperand, QOM\LiteralInterface $literalOperand, $operator){$alias = $this->getTableAlias($propertyOperand->getSelectorName() . '.' . $propertyOperand->getPropertyName());$property = $propertyOperand->getPropertyName();if ($this->platform instanceof MySQLPlatform && '=' === $operator) {return sprintf('0 != FIND_IN_SET("%s", REPLACE(EXTRACTVALUE(%s.props, \'//sv:property[@sv:name=%s]/sv:value\'), " ", ","))',$literalOperand->getLiteralValue(),$alias,Xpath::escape($property));}if ('=' === $operator) {return $this->sqlXpathComparePropertyValue($alias, $property, $literalOperand->getLiteralValue(), $operator);}return sprintf('%s %s %s',$this->sqlXpathExtractNumValue($alias, $property),$operator,$literalOperand->getLiteralValue());}/*** @param string $operator** @return string*/public function walkOperator($operator){if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO) {return '=';}if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_GREATER_THAN) {return '>';}if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO) {return '>=';}if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_LESS_THAN) {return '<';}if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO) {return '<=';}if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_NOT_EQUAL_TO) {return '!=';}if ($operator === QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_LIKE) {return 'LIKE';}return $operator; // no-op for simplicity, not standard conform (but using the constants is a pain)}/*** @param QOM\OperandInterface $operand** @return string** @throws InvalidQueryException*/public function walkOperand(QOM\OperandInterface $operand){if ($operand instanceof QOM\NodeNameInterface) {$selectorName = $operand->getSelectorName();$alias = $this->getTableAlias($selectorName);return $this->platform->getConcatExpression(sprintf("%s.namespace", $alias),sprintf("(CASE %s.namespace WHEN '' THEN '' ELSE ':' END)", $alias),sprintf("%s.local_name", $alias));}if ($operand instanceof QOM\NodeLocalNameInterface) {$selectorName = $operand->getSelectorName();$alias = $this->getTableAlias($selectorName);return sprintf("%s.local_name", $alias);}if ($operand instanceof QOM\LowerCaseInterface) {return $this->platform->getLowerExpression($this->walkOperand($operand->getOperand()));}if ($operand instanceof QOM\UpperCaseInterface) {return $this->platform->getUpperExpression($this->walkOperand($operand->getOperand()));}if ($operand instanceof QOM\LiteralInterface) {return $this->conn->quote($this->getLiteralValue($operand));}if ($operand instanceof QOM\PropertyValueInterface) {$alias = $this->getTableAlias($operand->getSelectorName() . '.' . $operand->getPropertyName());$property = $operand->getPropertyName();if ($property === 'jcr:path') {return sprintf("%s.path", $alias);}if ($property === "jcr:uuid") {return sprintf("%s.identifier", $alias);}return $this->sqlXpathExtractValue($alias, $property);}if ($operand instanceof QOM\LengthInterface) {$alias = $this->getTableAlias($operand->getPropertyValue()->getSelectorName());$property = $operand->getPropertyValue()->getPropertyName();return $this->sqlXpathExtractValueAttribute($alias, $property, 'length');}throw new InvalidQueryException(sprintf("Dynamic operand %s not yet supported.", get_class($operand)));}/*** @param array $orderings** @return string*/public function walkOrderings(array $orderings){$sql = '';foreach ($orderings as $ordering) {$sql .= empty($sql) ? 'ORDER BY ' : ', ';$sql .= $this->walkOrdering($ordering);}return $sql;}/*** @param QOM\OrderingInterface $ordering** @return string*/public function walkOrdering(QOM\OrderingInterface $ordering){$direction = $ordering->getOrder();if ($direction === QOM\QueryObjectModelConstantsInterface::JCR_ORDER_ASCENDING) {$direction = 'ASC';} elseif ($direction === QOM\QueryObjectModelConstantsInterface::JCR_ORDER_DESCENDING) {$direction = 'DESC';}$sql = $this->walkOperand($ordering->getOperand());if ($ordering->getOperand() instanceof QOM\PropertyValueInterface) {$operand = $ordering->getOperand();$property = $ordering->getOperand()->getPropertyName();if ($property !== 'jcr:path' && $property !== 'jcr:uuid') {$alias = $this->getTableAlias($operand->getSelectorName() . '.' . $property);$numericalSelector = $this->sqlXpathExtractValue($alias, $property, 'numerical_props');$sql = sprintf('CAST(%s AS DECIMAL) %s, %s',$numericalSelector,$direction,$sql);}}$sql .= ' ' .$direction;return $sql;}/*** @param QOM\LiteralInterface $operand** @return string** @throws NamespaceException*/private function getLiteralValue(QOM\LiteralInterface $operand){$value = $operand->getLiteralValue();/*** Normalize Dates to UTC*/if ($value instanceof DateTime) {$valueUTC = clone($value);$valueUTC->setTimezone(new DateTimeZone('UTC'));return $valueUTC->format('c');}return $value;}/*** SQL to execute an XPATH expression checking if the property exist on the node with the given alias.** @param string $alias* @param string $property** @return string*/private function sqlXpathValueExists($alias, $property){if ($this->platform instanceof MySQLPlatform) {return sprintf("EXTRACTVALUE(%s.props, 'count(//sv:property[@sv:name=%s]/sv:value[1])') = 1", $alias, Xpath::escape($property));}if ($this->platform instanceof PostgreSQL94Platform || $this->platform instanceof PostgreSqlPlatform) {return sprintf("xpath_exists('//sv:property[@sv:name=%s]/sv:value[1]', CAST(%s.props AS xml), ".$this->sqlXpathPostgreSQLNamespaces().") = 't'", Xpath::escape($property), $alias);}if ($this->platform instanceof SqlitePlatform) {return sprintf("EXTRACTVALUE(%s.props, 'count(//sv:property[@sv:name=%s]/sv:value[1])') = 1", $alias, Xpath::escape($property));}throw new NotImplementedException(sprintf("Xpath evaluations cannot be executed with '%s' yet.", $this->platform->getName()));}/*** SQL to execute an XPATH expression extracting the property value on the node with the given alias.** @param string $alias* @param string $property** @return string*/private function sqlXpathExtractValue($alias, $property, $column = 'props'){if ($this->platform instanceof MySQLPlatform) {return sprintf("EXTRACTVALUE(%s.%s, '//sv:property[@sv:name=%s]/sv:value[1]')", $alias, $column, Xpath::escape($property));}if ($this->platform instanceof PostgreSQL94Platform || $this->platform instanceof PostgreSqlPlatform) {return sprintf("(xpath('//sv:property[@sv:name=%s]/sv:value[1]/text()', CAST(%s.%s AS xml), %s))[1]::text", Xpath::escape($property), $alias, $column, $this->sqlXpathPostgreSQLNamespaces());}if ($this->platform instanceof SqlitePlatform) {return sprintf("EXTRACTVALUE(%s.%s, '//sv:property[@sv:name=%s]/sv:value[1]')", $alias, $column, Xpath::escape($property));}throw new NotImplementedException(sprintf("Xpath evaluations cannot be executed with '%s' yet.", $this->platform->getName()));}private function sqlXpathExtractNumValue($alias, $property){if ($this->platform instanceof PostgreSQL94Platform || $this->platform instanceof PostgreSqlPlatform) {return sprintf("(xpath('//sv:property[@sv:name=%s]/sv:value[1]/text()', CAST(%s.props AS xml), %s))[1]::text::int", Xpath::escape($property), $alias, $this->sqlXpathPostgreSQLNamespaces());}return sprintf('CAST(%s AS DECIMAL)', $this->sqlXpathExtractValue($alias, $property));}private function sqlXpathExtractValueAttribute($alias, $property, $attribute, $valueIndex = 1){if ($this->platform instanceof MySQLPlatform) {return sprintf("EXTRACTVALUE(%s.props, '//sv:property[@sv:name=%s]/sv:value[%d]/@%s')", $alias, Xpath::escape($property), $valueIndex, $attribute);}if ($this->platform instanceof PostgreSQL94Platform || $this->platform instanceof PostgreSqlPlatform) {return sprintf("CAST((xpath('//sv:property[@sv:name=%s]/sv:value[%d]/@%s', CAST(%s.props AS xml), %s))[1]::text AS bigint)", Xpath::escape($property), $valueIndex, $attribute, $alias, $this->sqlXpathPostgreSQLNamespaces());}if ($this->platform instanceof SqlitePlatform) {return sprintf("EXTRACTVALUE(%s.props, '//sv:property[@sv:name=%s]/sv:value[%d]/@%s')", $alias, Xpath::escape($property), $valueIndex, $attribute);}throw new NotImplementedException(sprintf("Xpath evaluations cannot be executed with '%s' yet.", $this->platform->getName()));}/*** @param $alias* @param $property* @param $value* @param string $operator** @return string** @throws NotImplementedException if the storage backend is neither mysql* nor postgres nor sqlite*/private function sqlXpathComparePropertyValue($alias, $property, $value, $operator){$expression = null;if ($this->platform instanceof MySQLPlatform) {$expression = sprintf("EXTRACTVALUE(%s.props, 'count(//sv:property[@sv:name=%s]/sv:value[text()%%s%%s]) > 0')", $alias, Xpath::escape($property));// mysql does not escape the backslashes for us, while postgres and sqlite do$value = Xpath::escapeBackslashes($value);} elseif ($this->platform instanceof PostgreSQL94Platform || $this->platform instanceof PostgreSqlPlatform) {$expression = sprintf("xpath_exists('//sv:property[@sv:name=%s]/sv:value[text()%%s%%s]', CAST(%s.props AS xml), %s) = 't'", Xpath::escape($property), $alias, $this->sqlXpathPostgreSQLNamespaces());} elseif ($this->platform instanceof SqlitePlatform) {$expression = sprintf("EXTRACTVALUE(%s.props, 'count(//sv:property[@sv:name=%s]/sv:value[text()%%s%%s]) > 0')", $alias, Xpath::escape($property));} else {throw new NotImplementedException(sprintf("Xpath evaluations cannot be executed with '%s' yet.", $this->platform->getName()));}return sprintf($expression, $this->walkOperator($operator), Xpath::escape($value));}/*** @return string*/private function sqlXpathPostgreSQLNamespaces(){return "ARRAY[ARRAY['sv', 'http://www.jcp.org/jcr/sv/1.0']]";}/*** @param QOM\SelectorInterface $source* @param string $alias** @return string*/private function sqlNodeTypeClause($alias, QOM\SelectorInterface $source){$sql = sprintf("%s.type IN ('%s'", $alias, $source->getNodeTypeName());$subTypes = $this->nodeTypeManager->getSubtypes($source->getNodeTypeName());foreach ($subTypes as $subType) {/* @var $subType NodeTypeInterface */$sql .= sprintf(", '%s'", $subType->getName());}$sql .= ')';return $sql;}}