Skip to content

Commit f2c597b

Browse files
committed
Merge pull request #90 from phpcr/update_query_indexes
[query] Full support for manipulating multivalue properties
2 parents 56b9b91 + 9d9e560 commit f2c597b

File tree

9 files changed

+508
-61
lines changed

9 files changed

+508
-61
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ alpha-5
1919

2020
### Features
2121

22+
- [query] Full support for manipulating multivalue properties
23+
24+
alpha-5
25+
-------
26+
27+
### Features
28+
2229
- [shell] Added "shell:clear" command to support clearing the console output
2330
- [general] The shell supports being embedded as a dependency
2431
- [node:edit] New command `node:edit` enables editing of entire node

features/phpcr_query_update.feature

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,97 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
2424
| UPDATE nt:unstructured AS a SET title = 'DTL' WHERE localname() = 'article1' | /cms/articles/article1 | title | DTL |
2525
| UPDATE nt:unstructured AS a SET title = 'DTL', foobar='barfoo' WHERE localname() = 'article1' | /cms/articles/article1 | foobar | barfoo |
2626

27-
Scenario: Update multivalue index by value
28-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = 'Rockets' WHERE a.tags = 'Trains'" command
27+
Scenario: Replace a multivalue index by value
28+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_replace(a.tags, 'Trains', 'Rockets') WHERE a.tags = 'Trains'" command
29+
Then the command should not fail
30+
And I save the session
31+
Then the command should not fail
32+
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "1"
33+
34+
Scenario: Set a multivalue value
35+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array('Rockets', 'Dragons') WHERE a.tags = 'Trains'" command
36+
And I save the session
37+
Then the command should not fail
38+
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "0"
39+
And the node at "/cms/articles/article1" should have the property "tags" with value "Dragons" at index "1"
40+
41+
Scenario: Update single multivalue without selector
42+
Given I execute the "UPDATE [nt:unstructured] SET tags = array_replace(tags, 'Planes', 'Rockets') WHERE tags = 'Planes'" command
2943
And I save the session
3044
Then the command should not fail
3145
And I should see the following:
3246
"""
33-
Cannot update property "tags". Updating multi-value nodes with more than one element not currently supported
47+
1 row(s) affected
3448
"""
49+
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "0"
50+
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "2"
3551

36-
Scenario: Update single multivalue
37-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tag = 'Rockets' WHERE a.tags = 'Planes'" command
52+
Scenario: Remove single multivalue
53+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_remove(a.tags, 'Planes') WHERE a.tags = 'Planes'" command
3854
And I save the session
3955
Then the command should not fail
4056
And I should see the following:
4157
"""
4258
2 row(s) affected
4359
"""
44-
And the node at "/cms/articles/article1" should have the property "tag" with value "Rockets" at index "0"
60+
And the node at "/cms/articles/article1" should have the property "tags" with value "Trains" at index "0"
61+
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "1"
62+
63+
Scenario: Remove single multivalue by index
64+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, 0, NULL) WHERE a.tags = 'Planes'" command
65+
And I save the session
66+
Then the command should not fail
67+
And I should see the following:
68+
"""
69+
1 row(s) affected
70+
"""
71+
And the node at "/cms/articles/article1" should have the property "tags" with value "Trains" at index "0"
72+
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "1"
73+
74+
Scenario: Add a multivalue property
75+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_append(a.tags, 'Kite') WHERE a.tags = 'Planes'" command
76+
And I save the session
77+
Then the command should not fail
78+
And I should see the following:
79+
"""
80+
1 row(s) affected
81+
"""
82+
And the node at "/cms/articles/article1" should have the property "tags" with value "Planes" at index "0"
83+
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "2"
84+
And the node at "/cms/articles/article1" should have the property "tags" with value "Kite" at index "3"
85+
86+
Scenario: Replace a multivalue property by index
87+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, 1, 'Kite'), a.tags = array_set(a.tags, 2, 'foobar') WHERE a.tags = 'Planes'" command
88+
And I save the session
89+
Then the command should not fail
90+
And I should see the following:
91+
"""
92+
1 row(s) affected
93+
"""
94+
And the node at "/cms/articles/article1" should have the property "tags" with value "Planes" at index "0"
95+
And the node at "/cms/articles/article1" should have the property "tags" with value "Kite" at index "1"
96+
And the node at "/cms/articles/article1" should have the property "tags" with value "foobar" at index "2"
97+
98+
Scenario: Replace a multivalue property by invalid index
99+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, 10, 'Kite') WHERE a.tags = 'Planes'" command
100+
Then the command should fail
101+
And I should see the following:
102+
"""
103+
Multivalue index "10" does not exist
104+
"""
105+
106+
Scenario: Attempt to update a numerically named property (must use a selector)
107+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, a.10, 'Kite') WHERE a.tags = 'Planes'" command
108+
Then the command should fail
109+
And I should see the following:
110+
"""
111+
[PHPCR\PathNotFoundException] Property 10
112+
"""
113+
114+
Scenario: Replace a multivalue property by invalid index with array (invalid)
115+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, 0, array('Kite')) WHERE a.tags = 'Planes'" command
116+
Then the command should fail
117+
And I should see the following:
118+
"""
119+
Cannot use an array as a value in a multivalue property
120+
"""

spec/PHPCR/Shell/Query/UpdateParserSpec.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
use PHPCR\Query\QOM\LiteralInterface;
1414
use PHPCR\Query\QOM\ComparisonInterface;
1515
use PHPCR\Query\QueryInterface;
16+
use PHPCR\Shell\Query\FunctionOperand;
17+
use PHPCR\Shell\Query\ColumnOperand;
1618

1719
class UpdateParserSpec extends ObjectBehavior
1820
{
@@ -66,16 +68,34 @@ function it_should_provide_a_qom_object_for_selecting(
6668

6769
$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
6870
$res->offsetGet(1)->shouldReturn(array(
69-
'parent.foo' => array(
71+
array(
7072
'selector' => 'parent',
7173
'name' => 'foo',
7274
'value' => 'PHPCR\\FOO\\Bar',
7375
),
74-
'parent.bar' => array(
76+
array(
7577
'selector' => 'parent',
7678
'name' => 'bar',
7779
'value' => 'foo',
7880
),
7981
));
8082
}
83+
84+
function it_should_parse_functions (
85+
QueryObjectModelFactoryInterface $qomf,
86+
SourceInterface $source,
87+
QueryInterface $query
88+
)
89+
{
90+
$qomf->selector('a', 'dtl:article')->willReturn($source);
91+
$qomf->createQuery($source, null)->willReturn($query);
92+
93+
94+
$sql = <<<EOT
95+
UPDATE [dtl:article] AS a SET a.tags = array_replace(a.tags, 'asd', 'dsa')
96+
EOT;
97+
$res = $this->parse($sql);
98+
99+
$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
100+
}
81101
}

src/PHPCR/Shell/Console/Command/Phpcr/QueryUpdateCommand.php

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,53 @@
66
use Symfony\Component\Console\Input\InputInterface;
77
use Symfony\Component\Console\Output\OutputInterface;
88
use PHPCR\Shell\Query\UpdateParser;
9+
use Jackalope\Query\QOM\ComparisonConstraint;
10+
use Jackalope\Query\QOM\PropertyValue;
11+
use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
12+
use PHPCR\Query\QOM\LiteralInterface;
13+
use PHPCR\Shell\Query\UpdateProcessor;
914

1015
class QueryUpdateCommand extends Command
1116
{
17+
/**
18+
* @var OutputInterface
19+
*/
20+
protected $output;
21+
1222
protected function configure()
1323
{
1424
$this->setName('update');
1525
$this->setDescription('Execute an UPDATE JCR-SQL2 query');
1626
$this->addArgument('query');
1727
$this->setHelp(<<<EOT
18-
Execute a JCR-SQL2 update query. Unlike other commands you can enter a query literally:
28+
Execute a PHPCR-Shell JCR-SQL2 update query. You can enter a query literally:
1929
2030
UPDATE [nt:unstructured] AS a SET title = 'foobar' WHERE a.title = 'barfoo';
2131
32+
You can also manipulate multivalue fields:
33+
34+
# Delete index
35+
36+
37+
And you have access to a set of functions when assigning a value:
38+
39+
# Delete a multivalue index
40+
UPDATE [nt:unstructured] SET a.tags = array_set(a.tags, 0, NULL)
41+
42+
# Set a multivalue index
43+
UPDATE [nt:unstructured] SET a.tags = array_set(a.tags, 0, 'foo')
44+
45+
# Replace the multivalue value "Planes" with "Trains"
46+
UPDATE [nt:unstructured] AS a SET a.tags[] = array_replace(a.tags, 'Planes', 'Trains')
47+
48+
# Append a multivalue
49+
UPDATE [nt:unstructured] AS a SET a.tags = array_append(a.tags, 'Rockets')
50+
51+
# Remove by value
52+
UPDATE [nt:unstructured] AS a SET a.tags = array_remove(a.tags, 'Plains')
53+
54+
Refer to the documentation for a full reference: http://phpcr.readthedocs.org/en/latest/phpcr-shell
55+
2256
You must call <info>session:save</info> to persist changes.
2357
2458
Note that this command is not part of the JCR-SQL2 language but is implemented specifically
@@ -29,6 +63,7 @@ protected function configure()
2963

3064
public function execute(InputInterface $input, OutputInterface $output)
3165
{
66+
$this->output = $output;
3267
$sql = $input->getRawCommand();
3368

3469
// trim ";" for people used to MysQL
@@ -48,33 +83,12 @@ public function execute(InputInterface $input, OutputInterface $output)
4883
$result = $query->execute();
4984
$rows = 0;
5085

86+
$updateProcessor = new UpdateProcessor();
87+
5188
foreach ($result as $row) {
5289
$rows++;
53-
foreach ($updates as $field => $property) {
54-
$node = $row->getNode($property['selector']);
55-
56-
if ($node->hasProperty($property['name'])) {
57-
$phpcrProperty = $node->getProperty($property['name']);
58-
59-
if ($phpcrProperty->isMultiple()) {
60-
$currentValue = $phpcrProperty->getValue();
61-
62-
if (sizeof($currentValue) > 1) {
63-
$output->writeln(sprintf(
64-
'<error>Cannot update property "%s". Updating multi-value nodes with more than one element not currently supported</error>',
65-
$phpcrProperty->getName()
66-
));
67-
$output->writeln(sprintf(
68-
'<error>See: https://github.com/phpcr/phpcr-shell/issues/81</error>',
69-
$phpcrProperty->getName()
70-
));
71-
}
72-
73-
$property['value'] = (array) $property['value'];
74-
}
75-
}
76-
77-
$node->setProperty($property['name'], $property['value']);
90+
foreach ($updates as $property) {
91+
$updateProcessor->updateNode($row, $property);
7892
}
7993
}
8094

src/PHPCR/Shell/Console/Helper/ResultFormatterHelper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ public function formatQueryResult(QueryResultInterface $result, OutputInterface
6060
$table = new TableHelper;
6161
$table->setHeaders(array_merge(array(
6262
'Path',
63+
'Index',
6364
), $result->getColumnNames()));
6465

6566
foreach ($result->getRows() as $i => $row) {
6667
$values = array_merge(array(
6768
$row->getPath(),
69+
$row->getNode()->getIndex(),
6870
), $row->getValues());
6971

7072
foreach ($values as $columnName => &$value) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace PHPCR\Shell\Query;
4+
5+
/**
6+
* Simple class to represent column operands in query
7+
* evaluations.
8+
*
9+
* @author Daniel Leech <daniel@dantleech.com>
10+
*/
11+
class ColumnOperand
12+
{
13+
private $selectorName;
14+
private $propertyName;
15+
16+
public function __construct($selectorName, $propertyName)
17+
{
18+
$this->selectorName = $selectorName;
19+
$this->propertyName = $propertyName;
20+
}
21+
22+
public function getSelectorName()
23+
{
24+
return $this->selectorName;
25+
}
26+
27+
public function getPropertyName()
28+
{
29+
return $this->propertyName;
30+
}
31+
}

0 commit comments

Comments
 (0)