Skip to content

Commit 93de594

Browse files
committed
Merge pull request #226 from jackalope/numberical_sorting
Numerical sorting
2 parents 9cd5e8a + 38ca930 commit 93de594

File tree

6 files changed

+381
-88
lines changed

6 files changed

+381
-88
lines changed

src/Jackalope/Transport/DoctrineDBAL/Client.php

+103-38
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ public function __construct(FactoryInterface $factory, Connection $conn)
207207
private function registerSqliteFunctions(PDOConnection $sqliteConnection)
208208
{
209209
$sqliteConnection->sqliteCreateFunction('EXTRACTVALUE', function ($string, $expression) {
210+
if (null === $string) {
211+
return null;
212+
}
213+
210214
$dom = new \DOMDocument('1.0', 'UTF-8');
211215
$dom->loadXML($string);
212216
$xpath = new \DOMXPath($dom);
@@ -571,10 +575,20 @@ public function copyNode($srcAbsPath, $dstAbsPath, $srcWorkspace = null)
571575
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
572576
$newPath = str_replace($srcAbsPath, $dstAbsPath, $row['path']);
573577

574-
$dom = new \DOMDocument('1.0', 'UTF-8');
575-
$dom->loadXML($row['props']);
578+
$stringDom = new \DOMDocument('1.0', 'UTF-8');
579+
$stringDom->loadXML($row['props']);
580+
581+
$numericalDom = null;
576582

577-
$propsData = array('dom' => $dom);
583+
if ($row['numerical_props']) {
584+
$numericalDom = new \DOMDocument('1.0', 'UTF-8');
585+
$numericalDom->loadXML($row['numerical_props']);
586+
}
587+
588+
$propsData = array(
589+
'stringDom' => $stringDom,
590+
'numericalDom' => $numericalDom
591+
);
578592
// when copying a node, the copy is always a new node. set $isNewNode to true
579593
$newNodeId = $this->syncNode(null, $newPath, $row['type'], true, array(), $propsData);
580594

@@ -646,25 +660,27 @@ private function syncNode($uuid, $path, $type, $isNewNode, $props = array(), $pr
646660

647661
$qb = $this->conn->createQueryBuilder();
648662

649-
$qb->select(':identifier, :type, :path, :local_name, :namespace, :parent, :workspace_name, :props, :depth, COALESCE(MAX(n.sort_order), 0) + 1')
663+
$qb->select(':identifier, :type, :path, :local_name, :namespace, :parent, :workspace_name, :props, :numerical_props, :depth, COALESCE(MAX(n.sort_order), 0) + 1')
650664
->from('phpcr_nodes', 'n')
651665
->where('n.parent = :parent_a');
652666

653667
$sql = $qb->getSql();
654668

655669
try {
656-
$insert = "INSERT INTO phpcr_nodes (identifier, type, path, local_name, namespace, parent, workspace_name, props, depth, sort_order) " . $sql;
657-
$this->conn->executeUpdate($insert, array(
658-
'identifier' => $uuid,
659-
'type' => $type,
660-
'path' => $path,
661-
'local_name' => $localName,
662-
'namespace' => $namespace,
663-
'parent' => PathHelper::getParentPath($path),
670+
$insert = "INSERT INTO phpcr_nodes (identifier, type, path, local_name, namespace, parent, workspace_name, props, numerical_props, depth, sort_order) " . $sql;
671+
672+
$this->conn->executeUpdate($insert, $data = array(
673+
'identifier' => $uuid,
674+
'type' => $type,
675+
'path' => $path,
676+
'local_name' => $localName,
677+
'namespace' => $namespace,
678+
'parent' => PathHelper::getParentPath($path),
664679
'workspace_name' => $this->workspaceName,
665-
'props' => $propsData['dom']->saveXML(),
666-
'depth' => PathHelper::getPathDepth($path),
667-
'parent_a' => PathHelper::getParentPath($path),
680+
'props' => $propsData['stringDom']->saveXML(),
681+
'numerical_props' => $propsData['numericalDom'] ? $propsData['numericalDom']->saveXML() : null,
682+
'depth' => PathHelper::getPathDepth($path),
683+
'parent_a' => PathHelper::getParentPath($path),
668684
));
669685
} catch(\Exception $e) {
670686
if ($e instanceof \PDOException || $e instanceof DBALException) {
@@ -684,7 +700,13 @@ private function syncNode($uuid, $path, $type, $isNewNode, $props = array(), $pr
684700
if (!$nodeId) {
685701
throw new RepositoryException("nodeId for $path not found");
686702
}
687-
$this->conn->update('phpcr_nodes', array('props' => $propsData['dom']->saveXML()), array('id' => $nodeId));
703+
704+
$this->conn->update('phpcr_nodes', array(
705+
'props' => $propsData['stringDom']->saveXML(),
706+
'numerical_props' => $propsData['numericalDom'] ? $propsData['numericalDom']->saveXML() : null,
707+
),
708+
array('id' => $nodeId)
709+
);
688710
}
689711

690712
$this->nodeIdentifiers[$path] = $uuid;
@@ -853,6 +875,7 @@ public static function xmlToProps($xml, ValueConverter $valueConverter, $filter
853875
}
854876

855877
$values = array();
878+
856879
$type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type'));
857880
foreach ($propertyNode->childNodes as $valueNode) {
858881
switch ($type) {
@@ -912,7 +935,11 @@ public static function xmlToProps($xml, ValueConverter $valueConverter, $filter
912935
* @param array $properties
913936
* @param boolean $inlineBinaries
914937
*
915-
* @return array ('dom' => $dom, 'binaryData' => streams, 'references' => array('type' => INT, 'values' => array(UUIDs)))
938+
* @return array (
939+
* 'stringDom' => $stringDom,
940+
* 'numericalDom' => $numericalDom',
941+
* 'binaryData' => streams,
942+
* 'references' => array('type' => INT, 'values' => array(UUIDs)))
916943
*/
917944
private function propsToXML($properties, $inlineBinaries = false)
918945
{
@@ -925,20 +952,16 @@ private function propsToXML($properties, $inlineBinaries = false)
925952
'rep' => "internal",
926953
);
927954

928-
$dom = new \DOMDocument('1.0', 'UTF-8');
929-
$rootNode = $dom->createElement('sv:node');
930-
foreach ($namespaces as $namespace => $uri) {
931-
$rootNode->setAttribute('xmlns:' . $namespace, $uri);
932-
}
933-
$dom->appendChild($rootNode);
955+
$doms = array(
956+
'stringDom' => array(),
957+
'numericalDom' => array(),
958+
);
934959

935960
$binaryData = $references = array();
961+
936962
foreach ($properties as $property) {
937-
/* @var $property Property */
938-
$propertyNode = $dom->createElement('sv:property');
939-
$propertyNode->setAttribute('sv:name', $property->getName());
940-
$propertyNode->setAttribute('sv:type', PropertyType::nameFromValue($property->getType()));
941-
$propertyNode->setAttribute('sv:multi-valued', $property->isMultiple() ? '1' : '0');
963+
964+
$targetDoms = array('stringDom');
942965

943966
switch ($property->getType()) {
944967
case PropertyType::WEAKREFERENCE:
@@ -955,12 +978,14 @@ private function propsToXML($properties, $inlineBinaries = false)
955978
break;
956979
case PropertyType::DECIMAL:
957980
$values = $property->getDecimal();
981+
$targetDoms[] = 'numericalDom';
958982
break;
959983
case PropertyType::BOOLEAN:
960984
$values = array_map('intval', (array) $property->getBoolean());
961985
break;
962986
case PropertyType::LONG:
963987
$values = $property->getLong();
988+
$targetDoms[] = 'numericalDom';
964989
break;
965990
case PropertyType::BINARY:
966991
if ($property->isNew() || $property->isModified()) {
@@ -998,26 +1023,66 @@ private function propsToXML($properties, $inlineBinaries = false)
9981023
break;
9991024
case PropertyType::DOUBLE:
10001025
$values = $property->getDouble();
1026+
$targetDoms[] = 'numericalDom';
10011027
break;
10021028
default:
10031029
throw new RepositoryException('unknown type '.$property->getType());
10041030
}
10051031

1006-
$lengths = (array) $property->getLength();
1007-
foreach ((array) $values as $key => $value) {
1008-
$element = $propertyNode->appendChild($dom->createElement('sv:value'));
1009-
$element->appendChild($dom->createTextNode($value));
1010-
if (isset($lengths[$key])) {
1011-
$lengthAttribute = $dom->createAttribute('length');
1012-
$lengthAttribute->value = $lengths[$key];
1013-
$element->appendChild($lengthAttribute);
1032+
foreach ($targetDoms as $targetDom) {
1033+
$doms[$targetDom][] = array(
1034+
'name' => $property->getName(),
1035+
'type' => PropertyType::nameFromValue($property->getType()),
1036+
'multiple' => $property->isMultiple(),
1037+
'lengths' => (array) $property->getLength(),
1038+
'values' => $values,
1039+
);
1040+
}
1041+
}
1042+
1043+
$ret = array(
1044+
'stringDom' => null,
1045+
'numericalDom' => null,
1046+
'binaryData' => $binaryData,
1047+
'references' => $references
1048+
);
1049+
1050+
foreach ($doms as $targetDom => $properties) {
1051+
1052+
$dom = new \DOMDocument('1.0', 'UTF-8');
1053+
$rootNode = $dom->createElement('sv:node');
1054+
foreach ($namespaces as $namespace => $uri) {
1055+
$rootNode->setAttribute('xmlns:' . $namespace, $uri);
1056+
}
1057+
$dom->appendChild($rootNode);
1058+
1059+
foreach ($properties as $property) {
1060+
1061+
/* @var $property Property */
1062+
$propertyNode = $dom->createElement('sv:property');
1063+
$propertyNode->setAttribute('sv:name', $property['name']);
1064+
$propertyNode->setAttribute('sv:type', $property['type']);
1065+
$propertyNode->setAttribute('sv:multi-valued', $property['multiple'] ? '1' : '0');
1066+
$lengths = (array) $property['lengths'];
1067+
foreach ((array) $property['values'] as $key => $value) {
1068+
$element = $propertyNode->appendChild($dom->createElement('sv:value'));
1069+
$element->appendChild($dom->createTextNode($value));
1070+
if (isset($lengths[$key])) {
1071+
$lengthAttribute = $dom->createAttribute('length');
1072+
$lengthAttribute->value = $lengths[$key];
1073+
$element->appendChild($lengthAttribute);
1074+
}
10141075
}
1076+
1077+
$rootNode->appendChild($propertyNode);
10151078
}
10161079

1017-
$rootNode->appendChild($propertyNode);
1080+
if (count($properties)) {
1081+
$ret[$targetDom] = $dom;
1082+
}
10181083
}
10191084

1020-
return array('dom' => $dom, 'binaryData' => $binaryData, 'references' => $references);
1085+
return $ret;
10211086
}
10221087

10231088
/**

src/Jackalope/Transport/DoctrineDBAL/Query/QOMWalker.php

+24-26
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,26 @@ public function walkOrdering(QOM\OrderingInterface $ordering)
743743
$direction = 'DESC';
744744
}
745745

746-
return $this->walkOperand($ordering->getOperand()) . " " . $direction;
746+
$sql = $this->walkOperand($ordering->getOperand());
747+
748+
if ($ordering->getOperand() instanceof QOM\PropertyValueInterface) {
749+
$operand = $ordering->getOperand();
750+
$property = $ordering->getOperand()->getPropertyName();
751+
if ($property !== 'jcr:path' && $property !== 'jcr:uuid') {
752+
$alias = $this->getTableAlias($operand->getSelectorName() . '.' . $property);
753+
754+
$numericalSelector = $this->sqlXpathExtractValue($alias, $property, 'numerical_props');
755+
756+
$sql = sprintf('CAST(%s AS DECIMAL), %s',
757+
$numericalSelector,
758+
$sql
759+
);
760+
}
761+
}
762+
763+
$sql .= ' ' .$direction;
764+
765+
return $sql;
747766
}
748767

749768
/**
@@ -791,16 +810,16 @@ private function sqlXpathValueExists($alias, $property)
791810
*
792811
* @return string
793812
*/
794-
private function sqlXpathExtractValue($alias, $property)
813+
private function sqlXpathExtractValue($alias, $property, $column = 'props')
795814
{
796815
if ($this->platform instanceof MySqlPlatform) {
797-
return "EXTRACTVALUE($alias.props, '//sv:property[@sv:name=\"" . $property . "\"]/sv:value[1]')";
816+
return "EXTRACTVALUE($alias.$column, '//sv:property[@sv:name=\"" . $property . "\"]/sv:value[1]')";
798817
}
799818
if ($this->platform instanceof PostgreSqlPlatform) {
800-
return "(xpath('//sv:property[@sv:name=\"" . $property . "\"]/sv:value[1]/text()', CAST($alias.props AS xml), ".$this->sqlXpathPostgreSQLNamespaces()."))[1]::text";
819+
return "(xpath('//sv:property[@sv:name=\"" . $property . "\"]/sv:value[1]/text()', CAST($alias.$column AS xml), ".$this->sqlXpathPostgreSQLNamespaces()."))[1]::text";
801820
}
802821
if ($this->platform instanceof SqlitePlatform) {
803-
return "EXTRACTVALUE($alias.props, '//sv:property[@sv:name=\"" . $property . "\"]/sv:value[1]')";
822+
return "EXTRACTVALUE($alias.$column, '//sv:property[@sv:name=\"" . $property . "\"]/sv:value[1]')";
804823
}
805824

806825
throw new NotImplementedException("Xpath evaluations cannot be executed with '" . $this->platform->getName() . "' yet.");
@@ -868,27 +887,6 @@ private function sqlXpathPostgreSQLNamespaces()
868887
return "ARRAY[ARRAY['sv', 'http://www.jcp.org/jcr/sv/1.0']]";
869888
}
870889

871-
/**
872-
* Returns the SQL part to select the given property
873-
*
874-
* @param string $alias
875-
* @param string $propertyName
876-
*
877-
* @return string
878-
*/
879-
private function sqlProperty($alias, $propertyName)
880-
{
881-
if ('jcr:uuid' === $propertyName) {
882-
return "$alias.identifier";
883-
}
884-
885-
if ('jcr:path' === $propertyName) {
886-
return "$alias.path";
887-
}
888-
889-
return $this->sqlXpathExtractValue($alias, $propertyName);
890-
}
891-
892890
/**
893891
* @param QOM\SelectorInterface $source
894892
* @param string $alias

src/Jackalope/Transport/DoctrineDBAL/RepositorySchema.php

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ protected function addNodesTable()
9292
$nodes->addColumn('identifier', 'string');
9393
$nodes->addColumn('type', 'string');
9494
$nodes->addColumn('props', 'text');
95+
$nodes->addColumn('numerical_props', 'text', array('notnull' => false));
9596
$nodes->addColumn('depth', 'integer');
9697
$nodes->addColumn('sort_order', 'integer', array('notnull' => false));
9798
$nodes->setPrimaryKey(array('id'));

0 commit comments

Comments
 (0)