Skip to content

Commit 2e0a0e2

Browse files
committed
Refactored to use function mapping for UPDATE
1 parent b36ad45 commit 2e0a0e2

File tree

12 files changed

+436
-165
lines changed

12 files changed

+436
-165
lines changed

features/phpcr_query_update.feature

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

2727
Scenario: Replace a multivalue index by value
28-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = 'Rockets' WHERE a.tags = 'Trains'" command
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
2930
And I save the session
3031
Then the command should not fail
3132
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "1"
3233

3334
Scenario: Set a multivalue value
34-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = ['Rockets', 'Dragons'] WHERE a.tags = 'Trains'" command
35+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = ARRAY['Rockets', 'Dragons'] WHERE a.tags = 'Trains'" command
3536
And I save the session
3637
Then the command should not fail
3738
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "0"
3839
And the node at "/cms/articles/article1" should have the property "tags" with value "Dragons" at index "1"
3940

40-
Scenario: Update single multivalue
41-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = 'Rockets' WHERE a.tags = 'Planes'" command
42-
And I save the session
43-
Then the command should not fail
44-
And I should see the following:
45-
"""
46-
1 row(s) affected
47-
"""
48-
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "0"
49-
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "2"
50-
5141
Scenario: Update single multivalue without selector
52-
Given I execute the "UPDATE [nt:unstructured] SET tags = 'Rockets' WHERE tags = 'Planes'" command
42+
Given I execute the "UPDATE [nt:unstructured] SET tags = array_replace(tags, 'Planes', 'Rockets') WHERE tags = 'Planes'" command
5343
And I save the session
5444
Then the command should not fail
5545
And I should see the following:
@@ -60,7 +50,7 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
6050
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "2"
6151

6252
Scenario: Remove single multivalue
63-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = NULL WHERE a.tags = 'Planes'" command
53+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_remove(a.tags, 'Planes') WHERE a.tags = 'Planes'" command
6454
And I save the session
6555
Then the command should not fail
6656
And I should see the following:
@@ -82,7 +72,7 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
8272
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "1"
8373

8474
Scenario: Add a multivalue property
85-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags[] = 'Kite' WHERE a.tags = 'Planes'" command
75+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_append(a.tags, 'Kite') WHERE a.tags = 'Planes'" command
8676
And I save the session
8777
Then the command should not fail
8878
And I should see the following:
@@ -114,7 +104,7 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
114104
"""
115105

116106
Scenario: Replace a multivalue property by invalid index with array (invalid)
117-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags[1] = ['Kite'] WHERE a.tags = 'Planes'" command
107+
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags[1] = ARRAY['Kite'] WHERE a.tags = 'Planes'" command
118108
Then the command should fail
119109
And I should see the following:
120110
"""
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace spec\PHPCR\Shell\Query;
4+
5+
use PhpSpec\ObjectBehavior;
6+
use Prophecy\Argument;
7+
use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
8+
use PHPCR\Query\QOM\SourceInterface;
9+
use PHPCR\Query\QueryInterface;
10+
11+
class SelectParserSpec extends ObjectBehavior
12+
{
13+
function let(
14+
QueryObjectModelFactoryInterface $qomf
15+
)
16+
{
17+
$this->beConstructedWith(
18+
$qomf
19+
);
20+
}
21+
22+
function it_is_initializable()
23+
{
24+
$this->shouldHaveType('PHPCR\Shell\Query\SelectParser');
25+
}
26+
}

spec/PHPCR/Shell/Query/UpdateParserSpec.php

Lines changed: 30 additions & 6 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
{
@@ -97,7 +99,7 @@ function it_should_parse_array_values (
9799

98100

99101
$sql = <<<EOT
100-
UPDATE [dtl:article] AS a SET a.tags = ['one', 'two', 'three']
102+
UPDATE [dtl:article] AS a SET a.tags = ARRAY['one', 'two', 'three']
101103
EOT;
102104
$res = $this->parse($sql);
103105

@@ -114,12 +116,7 @@ function it_should_parse_array_values (
114116

115117
function it_should_parse_array_addition (
116118
QueryObjectModelFactoryInterface $qomf,
117-
ChildNodeJoinConditionInterface $joinCondition,
118-
JoinInterface $join,
119119
SourceInterface $source,
120-
PropertyValueInterface $tagsValue,
121-
LiteralInterface $literalValue,
122-
ComparisonInterface $comparison,
123120
QueryInterface $query
124121
)
125122
{
@@ -142,4 +139,31 @@ function it_should_parse_array_addition (
142139
),
143140
));
144141
}
142+
143+
function it_should_parse_functions (
144+
QueryObjectModelFactoryInterface $qomf,
145+
SourceInterface $source,
146+
QueryInterface $query
147+
)
148+
{
149+
$qomf->selector('a', 'dtl:article')->willReturn($source);
150+
$qomf->createQuery($source, null)->willReturn($query);
151+
152+
153+
$sql = <<<EOT
154+
UPDATE [dtl:article] AS a SET a.tags[] = array_replace(a.tags, 'asd', 'dsa')
155+
EOT;
156+
$res = $this->parse($sql);
157+
158+
$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
159+
160+
// $res->offsetGet(1)->shouldReturn(array(
161+
// array(
162+
// 'array_op' => 'add',
163+
// 'selector' => 'a',
164+
// 'name' => 'tags',
165+
// 'value' => new FunctionOperand(new ColumnOperand('a', 'tags'), array('asd', 'dsa')),
166+
// ),
167+
// ));
168+
}
145169
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public function execute(InputInterface $input, OutputInterface $output)
6161

6262
$currentNode = $session->getNodeByPathOrIdentifier($path);
6363

64+
echo $currentNode->getIndex()."\n";
65+
6466
if (!$this->showChildren && !$this->showProperties) {
6567
$this->showChildren = true;
6668
$this->showProperties = true;

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

Lines changed: 21 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Jackalope\Query\QOM\PropertyValue;
1111
use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
1212
use PHPCR\Query\QOM\LiteralInterface;
13+
use PHPCR\Shell\Query\UpdateProcessor;
1314

1415
class QueryUpdateCommand extends Command
1516
{
@@ -24,23 +25,30 @@ protected function configure()
2425
$this->setDescription('Execute an UPDATE JCR-SQL2 query');
2526
$this->addArgument('query');
2627
$this->setHelp(<<<EOT
27-
Execute a JCR-SQL2 update query. Unlike other commands you can enter a query literally:
28+
Execute a PHPCR-Shell JCR-SQL2 update query. Tou can enter a query literally:
2829
2930
UPDATE [nt:unstructured] AS a SET title = 'foobar' WHERE a.title = 'barfoo';
3031
31-
You can update multivalue properties too:
32+
You can also manipulate multivalue fields:
3233
33-
# Add a property
34-
UPDATE [nt:unstructured] AS a SET a.tags[] = 'foo'
35-
36-
# Set values
37-
UPDATE [nt:unstructured] AS a SET a.tags = ['one', 'two', 'three', 'four']
38-
3934
# Delete index
4035
UPDATE [nt:unstructured] SET a.tags[0] = NULL
4136
42-
# Update a multivalue index by value
43-
UPDATE [nt:unstructured] SET a.tags = 'Foo' WHERE a.tags = 'Bar';
37+
# Set index
38+
UPDATE [nt:unstructured] SET a.tags[0] = 'foo'
39+
40+
And you have access to a set of functions when assigning a value:
41+
42+
# Replace the multivalue value "Planes" with "Trains"
43+
UPDATE [nt:unstructured] AS a SET a.tags[] = array_replace(a.tags, 'Planes', 'Trains')
44+
45+
# Append a multivalue
46+
UPDATE [nt:unstructured] AS a SET a.tags = array_append(a.tags, 'Rockets')
47+
48+
# Remove by value
49+
UPDATE [nt:unstructured] AS a SET a.tags = array_remove(a.tags, 'Plains')
50+
51+
Refer to the documentation for a full reference: http://phpcr.readthedocs.org/en/latest/phpcr-shell
4452
4553
You must call <info>session:save</info> to persist changes.
4654
@@ -67,130 +75,22 @@ public function execute(InputInterface $input, OutputInterface $output)
6775
$res = $updateParser->parse($sql);
6876
$query = $res->offsetGet(0);
6977
$updates = $res->offsetGet(1);
70-
$constraint = $res->offsetGet(2);
7178

7279
$start = microtime(true);
7380
$result = $query->execute();
7481
$rows = 0;
7582

83+
$updateProcessor = new UpdateProcessor();
84+
7685
foreach ($result as $row) {
7786
$rows++;
7887
foreach ($updates as $property) {
79-
$node = $row->getNode($property['selector']);
80-
81-
if ($node->hasProperty($property['name'])) {
82-
$value = $this->handleMultiValue($node, $property, $constraint);
83-
} else {
84-
$value = $property['value'];
85-
}
86-
87-
$node->setProperty($property['name'], $value);
88+
$updateProcessor->updateNode($row, $property);
8889
}
8990
}
9091

9192
$elapsed = microtime(true) - $start;
9293

9394
$output->writeln(sprintf('%s row(s) affected in %ss', $rows, number_format($elapsed, 2)));
9495
}
95-
96-
protected function handleMultiValue($node, $propertyData, $constraint)
97-
{
98-
$phpcrProperty = $node->getProperty($propertyData['name']);
99-
$value = $propertyData['value'];
100-
101-
if (false === $phpcrProperty->isMultiple()) {
102-
return $value;
103-
}
104-
105-
$currentValue = $phpcrProperty->getValue();
106-
107-
// there is an array operator ([] or [x])
108-
if (isset($propertyData['array_op'])) {
109-
$arrayOp = $propertyData['array_op'];
110-
if ($arrayOp === UpdateParser::ARRAY_OPERATION_ADD) {
111-
foreach ((array) $value as $v) {
112-
$currentValue[] = $v;
113-
}
114-
115-
return $currentValue;
116-
} elseif ($arrayOp === UpdateParser::ARRAY_OPERATION_SUB) {
117-
$arrayIndex = $propertyData['array_index'];
118-
119-
if (!isset($currentValue[$arrayIndex])) {
120-
throw new \InvalidArgumentException(sprintf(
121-
'Multivalue index "%s" does not exist in multivalue field "%s"', $arrayIndex, $propertyData['name']
122-
));
123-
}
124-
125-
if (null === $value) {
126-
unset($currentValue[$arrayIndex]);
127-
return array_values($currentValue);
128-
}
129-
130-
if (is_array($value)) {
131-
throw new \InvalidArgumentException(sprintf('Cannot set index to array value on "%s"', $propertyData['name']));
132-
}
133-
134-
$currentValue[$arrayIndex] = $value;
135-
136-
return $currentValue;
137-
}
138-
}
139-
140-
if (is_array($value)) {
141-
return $value;
142-
}
143-
144-
if ($constraint instanceof ComparisonConstraint) {
145-
146-
$op1 = $constraint->getOperand1();
147-
$op2 = $constraint->getOperand2();
148-
$operator = $constraint->getOperator();
149-
if ($op1 instanceOf PropertyValue) {
150-
if (
151-
($propertyData['selector'] === null ||
152-
$op1->getSelectorName() == $propertyData['selector']) &&
153-
$op1->getPropertyName() == $propertyData['name']
154-
)
155-
{
156-
if ($operator !== QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO) {
157-
throw new \InvalidArgumentException(sprintf(
158-
'You must use the "=" operator in the comparison when specifying a specific multiple value update'
159-
));
160-
}
161-
162-
if (!$op2 instanceof LiteralInterface) {
163-
throw new \InvalidArgumentException(sprintf(
164-
'The value of "%s" must be a literal', $propertyData['name']
165-
));
166-
}
167-
168-
$targetValue = $op2->getLiteralValue();
169-
170-
foreach ($currentValue as $k => $v) {
171-
if ($v === $targetValue) {
172-
if (null === $value) {
173-
unset($currentValue[$k]);
174-
$currentValue = array_values($currentValue);
175-
} else {
176-
$currentValue[$k] = $value;
177-
}
178-
}
179-
}
180-
181-
return $currentValue;
182-
}
183-
}
184-
}
185-
186-
// do not allow updating multivalue with scalar
187-
if (false === is_array($value) && sizeof($currentValue) > 1) {
188-
throw new \InvalidArgumentException(sprintf(
189-
'<error>Cannot update multivalue property "%s" with a scalar value.</error>',
190-
$phpcrProperty->getName()
191-
));
192-
}
193-
194-
return $value;
195-
}
19696
}

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+
}

src/PHPCR/Shell/Query/Function.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace PHPCR\Shell\Query;
4+
5+
class Function
6+
{
7+
;
8+
}

0 commit comments

Comments
 (0)