Skip to content

Commit

Permalink
Merge pull request #126 from wjzijderveld/88-multivalue-comparison
Browse files Browse the repository at this point in the history
Fixes #88 multivalue comparison
  • Loading branch information
dbu committed Jun 28, 2013
2 parents df8469b + f198670 commit e7f28fa
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 26 deletions.
134 changes: 110 additions & 24 deletions src/Jackalope/Transport/DoctrineDBAL/Query/QOMWalker.php
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,58 @@ public function walkNotConstraint(QOM\NotInterface $constraint)
}

/**
* 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)
{
return $this->walkOperand($constraint->getOperand1()) . " " .
$this->walkOperator($constraint->getOperator()) . " " .
$this->walkOperand($constraint->getOperand2());
// When we encouter equal/not-equal do some checks to catch text comparison
if ($constraint->getOperator() == QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO || $constraint->getOperator() == QOM\QueryObjectModelConstantsInterface::JCR_OPERATOR_NOT_EQUAL_TO) {
// Check if we have a property and a literal value (in random order)
if (
($constraint->getOperand1() instanceOf QOM\PropertyValueInterface
&& $constraint->getOperand2() instanceOf QOM\LiteralInterface) ||
($constraint->getOperand1() instanceOf QOM\LiteralInterface
&& $constraint->getOperand2() instanceOf QOM\PropertyValueInterface)
) {
// Check weither the left is the property, at this point
// the other always is the literal operand
if ($constraint->getOperand1() instanceOf QOM\PropertyValueInterface) {
$propertyOperand = $constraint->getOperand1();
$literalOperand = $constraint->getOperand2();
} else {
$literalOperand = $constraint->getOperand1();
$propertyOperand = $constraint->getOperand2();
}

if ('jcr:path' !== $propertyOperand->getPropertyName() && 'jcr:uuid' !== $propertyOperand->getPropertyName()) {
return $this->walkTextComparisonConstraint($propertyOperand, $literalOperand, $constraint->getOperator());
}
}
}

// None of the checks above returned a constraint, return a simple constraint
return
$this->walkOperand($constraint->getOperand1()) . " " .
$this->walkOperator($constraint->getOperator()) . " " .
$this->walkOperand($constraint->getOperand2());
}

/**
* @param QOM\ComparisonInterface $constraint
* @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);
}

/**
Expand Down Expand Up @@ -474,28 +519,8 @@ public function walkOperand(QOM\OperandInterface $operand)
return $this->platform->getUpperExpression($this->walkOperand($operand->getOperand()));
}
if ($operand instanceof QOM\LiteralInterface) {
$namespace = '';

$value = $operand->getLiteralValue();

if ($value instanceof \DateTime) {
$literal = $value->format('c');
} else {
$literal = trim($value, '"');
if (($aliasLength = strpos($literal, ':')) !== false) {
$alias = substr($literal, 0, $aliasLength);
if (!isset($this->namespaces[$alias])) {
throw new NamespaceException('the namespace ' . $alias . ' was not registered.');
}
if (!empty($this->namespaces[$alias])) {
$namespace = $this->namespaces[$alias].':';
}

$literal = substr($literal, $aliasLength + 1);
}
}

return $this->conn->quote($namespace.$literal);
return $this->conn->quote($this->getLiteralValue($operand));
}
if ($operand instanceof QOM\PropertyValueInterface) {
$alias = $this->getTableAlias($operand->getSelectorName() . '.' . $operand->getPropertyName());
Expand Down Expand Up @@ -543,6 +568,37 @@ public function walkOrdering(QOM\OrderingInterface $ordering)
(($ordering->getOrder() == QOM\QueryObjectModelConstantsInterface::JCR_ORDER_ASCENDING) ? "ASC" : "DESC");
}

/**
* @param QOM\LiteralInterface $operand
* @return string
* @throws \PHPCR\NamespaceException
*/
private function getLiteralValue(QOM\LiteralInterface $operand)
{
$namespace = '';

$value = $operand->getLiteralValue();

if ($value instanceof \DateTime) {
$literal = $value->format('c');
} else {
$literal = trim($value, '"');
if (($aliasLength = strpos($literal, ':')) !== false) {
$alias = substr($literal, 0, $aliasLength);
if (!isset($this->namespaces[$alias])) {
throw new NamespaceException('the namespace ' . $alias . ' was not registered.');
}
if (!empty($this->namespaces[$alias])) {
$namespace = $this->namespaces[$alias].':';
}

$literal = substr($literal, $aliasLength + 1);
}
}

return $namespace.$literal;
}

/**
* SQL to execute an XPATH expression checking if the property exist on the node with the given alias.
*
Expand Down Expand Up @@ -587,6 +643,36 @@ private function sqlXpathExtractValue($alias, $property)
throw new NotImplementedException("Xpath evaluations cannot be executed with '" . $this->platform->getName() . "' yet.");
}

/**
* @param $alias
* @param $property
* @param $value
* @param string $operator
* @return string
* @throws \Jackalope\NotImplementedException
*/
private function sqlXpathComparePropertyValue($alias, $property, $value, $operator)
{
$expression = null;

if ($this->platform instanceof MySqlPlatform) {
$expression = "EXTRACTVALUE($alias.props, 'count(//sv:property[@sv:name=\"" . $property . "\"]/sv:value[text()%s\"%s\"]) > 0')";
}

if ($this->platform instanceof PostgreSqlPlatform) {
$expression = "xpath_exists('//sv:property[@sv:name=\"" . $property . "\"]/sv:value[text()%s\"%s\"]', CAST($alias.props AS xml), ".$this->sqlXpathPostgreSQLNamespaces().") = 't'";
}
if ($this->platform instanceof SqlitePlatform) {
$expression = "EXTRACTVALUE($alias.props, 'count(//sv:property[@sv:name=\"" . $property . "\"]/sv:value[text()%s\"%s\"]) > 0')";
}

if (null === $expression) {
throw new NotImplementedException("Xpath evaluations cannot be executed with '" . $this->platform->getName() . "' yet.");
}

return sprintf($expression, $this->walkOperator($operator), $value);
}

/**
* @return string
*/
Expand Down
2 changes: 0 additions & 2 deletions tests/ImplementationLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ protected function __construct(Connection $connection, $fixturePath)
'Query\\QueryManagerTest::testGetQuery',
'Query\\QueryManagerTest::testGetQueryInvalid',
'Query\\QueryObjectSql2Test::testGetStoredQueryPath',
// TODO https://github.com/jackalope/jackalope-doctrine-dbal/issues/88
'Query\QuerySql2OperationsTest::testQueryMultiValuedProperty',
// TODO https://github.com/jackalope/jackalope-doctrine-dbal/issues/89 (next two tests)
'Query\QueryResultsTest::testCompareNumberFields',
'Query\QueryResultsTest::testBooleanField',
Expand Down

0 comments on commit e7f28fa

Please sign in to comment.