Skip to content

Commit 4d52a5c

Browse files
committed
Merge pull request #125 from phpcr/query_apply
Query update APPLY
2 parents d5a3d4b + 4322fe2 commit 4d52a5c

File tree

10 files changed

+142
-26
lines changed

10 files changed

+142
-26
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ dev-master
1212

1313
### Features
1414

15+
- [query:update] Added APPLY method to queries.
16+
- [query:update] APPLY `mixin_add` and `mixin_remove` functions
1517
- [node:remove] Immediately fail when trying to delete a node which has a
1618
(hard) referrer
1719
- [cli] Specify workspace with first argument

features/all/phpcr_node_edit.feature

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ Feature: Edit a node
106106
type: String
107107
value: 'FOOOOOOO'
108108
"""
109-
And I execute the "node:edit cms/products/product2" command
109+
And I execute the "node:edit cms/products/productx" command
110110
Then the command should not fail
111111
And I save the session
112112
Then the command should not fail
113-
And the property "/cms/products/product2/foobar" should have type "String" and value "FOOOOOOO"
113+
And the property "/cms/products/productx/foobar" should have type "String" and value "FOOOOOOO"
114114

115115
Scenario: Create a new node with short syntax
116116
Given I have an editor which produces the following:
@@ -120,11 +120,11 @@ Feature: Edit a node
120120
value: 'nt:unstructured'
121121
foobar: FOOOOOOO
122122
"""
123-
And I execute the "node:edit cms/products/product2" command
123+
And I execute the "node:edit cms/products/productx" command
124124
Then the command should not fail
125125
And I save the session
126126
Then the command should not fail
127-
And the property "/cms/products/product2/foobar" should have type "String" and value "FOOOOOOO"
127+
And the property "/cms/products/productx/foobar" should have type "String" and value "FOOOOOOO"
128128

129129
Scenario: Create a new node with a specified type
130130
Given I have an editor which produces the following:
@@ -136,18 +136,18 @@ Feature: Edit a node
136136
type: Binary
137137
value: foo
138138
"""
139-
And I execute the "node:edit cms/products/product2 --type=nt:resource" command
139+
And I execute the "node:edit cms/products/productx --type=nt:resource" command
140140
Then the command should not fail
141141
And I save the session
142142
Then the command should not fail
143-
And there should exist a node at "/cms/products/product2"
144-
And the primary type of "/cms/products/product2" should be "nt:resource"
143+
And there should exist a node at "/cms/products/productx"
144+
And the primary type of "/cms/products/productx" should be "nt:resource"
145145

146146
Scenario: Editor returns empty string
147147
Given I have an editor which produces the following:
148148
""""
149149
"""
150-
And I execute the "node:edit cms/products/product2 --no-interaction --type=nt:resource" command
150+
And I execute the "node:edit cms/products/productx --no-interaction --type=nt:resource" command
151151
Then the command should fail
152152

153153
Scenario: Edit a node by UUID

features/all/phpcr_query_update.feature

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,30 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
113113
[PHPCR\PathNotFoundException] Property 10
114114
"""
115115

116-
Scenario: Replace a multivalue property by invalid index with array (invalid)
117-
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_replace_at(a.tags, 0, array('Kite')) WHERE a.tags = 'Planes'" command
118-
Then the command should fail
119-
And I should see the following:
120-
"""
121-
Cannot use an array as a value in a multivalue property
122-
"""
116+
Scenario: Apply mixin_remove
117+
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_remove('mix:title') WHERE a.name = 'Product Two'" command
118+
Then the command should not fail
119+
And I save the session
120+
Then the command should not fail
121+
Then the node at "/cms/products/product2" should not have the mixin "mix:title"
122+
123+
Scenario: Apply mixin_add
124+
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_add('mix:mimeType') WHERE a.tags = 'Planes'" command
125+
Then the command should not fail
126+
And I save the session
127+
And the node at "/cms/articles/article1" should have the mixin "mix:mimeType"
128+
129+
Scenario: Apply mixin_add existing
130+
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_add('mix:title') WHERE a.name = 'Product Two'" command
131+
Then the command should not fail
132+
And I save the session
133+
Then the command should not fail
134+
Then the node at "/cms/products/product2" should have the mixin "mix:title"
135+
136+
Scenario: Apply multiple functions
137+
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_add('mix:mimeType'), mixin_add('mix:lockable') WHERE a.tags = 'Planes'" command
138+
Then the command should not fail
139+
And I save the session
140+
And the node at "/cms/articles/article1" should have the mixin "mix:mimeType"
141+
Then the node at "/cms/articles/article1" should have the mixin "mix:lockable"
142+

features/fixtures/cms.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@
5858
<sv:value>99999999-1abf-4708-bfcc-e49511754b40</sv:value>
5959
</sv:property>
6060
</sv:node>
61+
<sv:node sv:name="product2">
62+
<sv:property sv:name="jcr:primaryType" sv:type="Name">
63+
<sv:value>nt:unstructured</sv:value>
64+
</sv:property>
65+
<sv:property sv:name="jcr:mixinTypes" sv:type="name">
66+
<sv:value>mix:title</sv:value>
67+
</sv:property>
68+
<sv:property sv:name="name" sv:type="String">
69+
<sv:value>Product Two</sv:value>
70+
</sv:property>
71+
</sv:node>
6172
</sv:node>
6273

6374
<sv:node sv:name="users">

spec/PHPCR/Shell/Query/UpdateParserSpec.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,22 @@ public function it_should_parse_functions (
9494

9595
$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
9696
}
97+
98+
public function it_should_parse_apply (
99+
QueryObjectModelFactoryInterface $qomf,
100+
SourceInterface $source,
101+
QueryInterface $query
102+
)
103+
{
104+
$qomf->selector('a', 'dtl:article')->willReturn($source);
105+
$qomf->createQuery($source, null)->willReturn($query);
106+
107+
108+
$sql = <<<EOT
109+
UPDATE [dtl:article] AS a APPLY nodetype_add('nt:barbar')
110+
EOT;
111+
$res = $this->parse($sql);
112+
113+
$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
114+
}
97115
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public function execute(InputInterface $input, OutputInterface $output)
7474
$res = $updateParser->parse($sql);
7575
$query = $res->offsetGet(0);
7676
$updates = $res->offsetGet(1);
77+
$applies = $res->offsetGet(3);
7778

7879
$start = microtime(true);
7980
$result = $query->execute();
@@ -84,7 +85,11 @@ public function execute(InputInterface $input, OutputInterface $output)
8485
foreach ($result as $row) {
8586
$rows++;
8687
foreach ($updates as $property) {
87-
$updateProcessor->updateNode($row, $property);
88+
$updateProcessor->updateNodeSet($row, $property);
89+
}
90+
91+
foreach ($applies as $apply) {
92+
$updateProcessor->updateNodeApply($row, $apply);
8893
}
8994
}
9095

src/PHPCR/Shell/Query/FunctionOperand.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function execute($functionMap, $row)
6060

6161
$callable = $functionMap[$functionName];
6262
$args = $this->getArguments();
63+
array_unshift($args, $row);
6364
array_unshift($args, $this);
6465
$value = call_user_func_array($callable, $args);
6566

src/PHPCR/Shell/Query/UpdateParser.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ private function doParse($sql2)
4040
$this->sql2 = $sql2;
4141
$source = null;
4242
$constraint = null;
43+
$updates = array();
44+
$applies = array();
4345

4446
while ($this->scanner->lookupNextToken() !== '') {
4547
switch (strtoupper($this->scanner->lookupNextToken())) {
@@ -51,6 +53,10 @@ private function doParse($sql2)
5153
$this->scanner->expectToken('SET');
5254
$updates = $this->parseUpdates();
5355
break;
56+
case 'APPLY':
57+
$this->scanner->expectToken('APPLY');
58+
$applies = $this->parseApply();
59+
break;
5460
case 'WHERE':
5561
$this->scanner->expectToken('WHERE');
5662
$constraint = $this->parseConstraint();
@@ -66,7 +72,7 @@ private function doParse($sql2)
6672

6773
$query = $this->factory->createQuery($source, $constraint);
6874

69-
$res = new \ArrayObject(array($query, $updates, $constraint));
75+
$res = new \ArrayObject(array($query, $updates, $constraint, $applies));
7076

7177
return $res;
7278
}
@@ -164,6 +170,31 @@ private function parseOperand()
164170
return new ColumnOperand($columnData[0], $columnData[1]);
165171
}
166172

173+
private function parseApply()
174+
{
175+
$functions = array();
176+
177+
while (true) {
178+
$token = strtoupper($this->scanner->lookupNextToken());
179+
180+
if ($this->scanner->lookupNextToken(1) == '(') {
181+
$functionData = $this->parseFunction();
182+
183+
$functions[] = new FunctionOperand($functionData[0], $functionData[1]);
184+
}
185+
186+
$next = $this->scanner->lookupNextToken();
187+
188+
if ($next == ',') {
189+
$next = $this->scanner->fetchNextToken();
190+
} elseif (strtolower($next) == 'where' || !$next) {
191+
break;
192+
}
193+
}
194+
195+
return $functions;
196+
}
197+
167198
private function parseFunction()
168199
{
169200
$functionName = $this->scanner->fetchNextToken();

src/PHPCR/Shell/Query/UpdateProcessor.php

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPCR\Shell\Query;
44

55
use PHPCR\Query\RowInterface;
6+
use PHPCR\Shell\Query\FunctionOperand;
67

78
/**
89
* Processor for node updates
@@ -18,8 +19,22 @@ class UpdateProcessor
1819

1920
public function __construct()
2021
{
21-
$this->functionMap = array(
22-
'array_replace' => function ($operand, $v, $x, $y) {
22+
$this->functionMapApply = array(
23+
'mixin_add' => function ($operand, $row, $mixinName) {
24+
$node = $row->getNode();
25+
$node->addMixin($mixinName);
26+
},
27+
'mixin_remove' => function ($operand, $row, $mixinName) {
28+
$node = $row->getNode();
29+
30+
if ($node->isNodeType($mixinName)) {
31+
$node->removeMixin($mixinName);
32+
}
33+
}
34+
);
35+
36+
$this->functionMapSet = array(
37+
'array_replace' => function ($operand, $row, $v, $x, $y) {
2338
$operand->validateScalarArray($v);
2439
foreach ($v as $key => $value) {
2540
if ($value === $x) {
@@ -29,7 +44,7 @@ public function __construct()
2944

3045
return $v;
3146
},
32-
'array_remove' => function ($operand, $v, $x) {
47+
'array_remove' => function ($operand, $row, $v, $x) {
3348
foreach ($v as $key => $value) {
3449
if ($value === $x) {
3550
unset($v[$key]);
@@ -38,7 +53,7 @@ public function __construct()
3853

3954
return array_values($v);
4055
},
41-
'array_append' => function ($operand, $v, $x) {
56+
'array_append' => function ($operand, $row, $v, $x) {
4257
$operand->validateScalarArray($v);
4358
$v[] = $x;
4459

@@ -49,10 +64,12 @@ public function __construct()
4964

5065
// first argument is the operand
5166
array_shift($values);
67+
// second is the row
68+
array_shift($values);
5269

5370
return $values;
5471
},
55-
'array_replace_at' => function ($operand, $current, $index, $value) {
72+
'array_replace_at' => function ($operand, $row, $current, $index, $value) {
5673
if (!isset($current[$index])) {
5774
throw new \InvalidArgumentException(sprintf(
5875
'Multivalue index "%s" does not exist',
@@ -81,22 +98,32 @@ public function __construct()
8198
* @param PHPCR\Query\RowInterface
8299
* @param array
83100
*/
84-
public function updateNode(RowInterface $row, $propertyData)
101+
public function updateNodeSet(RowInterface $row, $propertyData)
85102
{
86103
$node = $row->getNode($propertyData['selector']);
87104
$value = $propertyData['value'];
88105

89106
if ($value instanceof FunctionOperand) {
90-
$value = $this->handleFunction($row, $propertyData);
107+
$value = $propertyData['value'];
108+
$value = $value->execute($this->functionMapSet, $row);
91109
}
92110

93111
$node->setProperty($propertyData['name'], $value);
94112
}
95113

114+
public function updateNodeApply(RowInterface $row, FunctionOperand $apply)
115+
{
116+
if (!$apply instanceof FunctionOperand) {
117+
throw new \InvalidArgumentException(
118+
'Was expecting a function operand but got something else'
119+
);
120+
}
121+
122+
$apply->execute($this->functionMapApply, $row);
123+
}
124+
96125
private function handleFunction($row, $propertyData)
97126
{
98-
$value = $propertyData['value'];
99-
$value = $value->execute($this->functionMap, $row);
100127

101128
return $value;
102129
}

src/PHPCR/Shell/Test/ContextBase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ public function theFixturesAreLoaded($arg1)
218218
NodeHelper::purgeWorkspace($session);
219219
$session->save();
220220

221+
221222
// shouldn't have to do this, but this seems to be a bug in jackalope
222223
$session->refresh(false);
223224

0 commit comments

Comments
 (0)