Skip to content

Query update APPLY #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 22, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ dev-master

### Features

- [query:update] Added APPLY method to queries.
- [query:update] APPLY `mixin_add` and `mixin_remove` functions
- [node:remove] Immediately fail when trying to delete a node which has a
(hard) referrer
- [cli] Specify workspace with first argument
Expand Down
16 changes: 8 additions & 8 deletions features/all/phpcr_node_edit.feature
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ Feature: Edit a node
type: String
value: 'FOOOOOOO'
"""
And I execute the "node:edit cms/products/product2" command
And I execute the "node:edit cms/products/productx" command
Then the command should not fail
And I save the session
Then the command should not fail
And the property "/cms/products/product2/foobar" should have type "String" and value "FOOOOOOO"
And the property "/cms/products/productx/foobar" should have type "String" and value "FOOOOOOO"

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

Scenario: Create a new node with a specified type
Given I have an editor which produces the following:
Expand All @@ -136,18 +136,18 @@ Feature: Edit a node
type: Binary
value: foo
"""
And I execute the "node:edit cms/products/product2 --type=nt:resource" command
And I execute the "node:edit cms/products/productx --type=nt:resource" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a node at "/cms/products/product2"
And the primary type of "/cms/products/product2" should be "nt:resource"
And there should exist a node at "/cms/products/productx"
And the primary type of "/cms/products/productx" should be "nt:resource"

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

Scenario: Edit a node by UUID
Expand Down
34 changes: 27 additions & 7 deletions features/all/phpcr_query_update.feature
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,30 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
[PHPCR\PathNotFoundException] Property 10
"""

Scenario: Replace a multivalue property by invalid index with array (invalid)
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
Then the command should fail
And I should see the following:
"""
Cannot use an array as a value in a multivalue property
"""
Scenario: Apply mixin_remove
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_remove('mix:title') WHERE a.name = 'Product Two'" command
Then the command should not fail
And I save the session
Then the command should not fail
Then the node at "/cms/products/product2" should not have the mixin "mix:title"

Scenario: Apply mixin_add
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_add('mix:mimeType') WHERE a.tags = 'Planes'" command
Then the command should not fail
And I save the session
And the node at "/cms/articles/article1" should have the mixin "mix:mimeType"

Scenario: Apply mixin_add existing
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_add('mix:title') WHERE a.name = 'Product Two'" command
Then the command should not fail
And I save the session
Then the command should not fail
Then the node at "/cms/products/product2" should have the mixin "mix:title"

Scenario: Apply multiple functions
Given I execute the "UPDATE [nt:unstructured] AS a APPLY mixin_add('mix:mimeType'), mixin_add('mix:lockable') WHERE a.tags = 'Planes'" command
Then the command should not fail
And I save the session
And the node at "/cms/articles/article1" should have the mixin "mix:mimeType"
Then the node at "/cms/articles/article1" should have the mixin "mix:lockable"

11 changes: 11 additions & 0 deletions features/fixtures/cms.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@
<sv:value>99999999-1abf-4708-bfcc-e49511754b40</sv:value>
</sv:property>
</sv:node>
<sv:node sv:name="product2">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>nt:unstructured</sv:value>
</sv:property>
<sv:property sv:name="jcr:mixinTypes" sv:type="name">
<sv:value>mix:title</sv:value>
</sv:property>
<sv:property sv:name="name" sv:type="String">
<sv:value>Product Two</sv:value>
</sv:property>
</sv:node>
</sv:node>

<sv:node sv:name="users">
Expand Down
18 changes: 18 additions & 0 deletions spec/PHPCR/Shell/Query/UpdateParserSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,22 @@ public function it_should_parse_functions (

$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
}

public function it_should_parse_apply (
QueryObjectModelFactoryInterface $qomf,
SourceInterface $source,
QueryInterface $query
)
{
$qomf->selector('a', 'dtl:article')->willReturn($source);
$qomf->createQuery($source, null)->willReturn($query);


$sql = <<<EOT
UPDATE [dtl:article] AS a APPLY nodetype_add('nt:barbar')
EOT;
$res = $this->parse($sql);

$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
}
}
7 changes: 6 additions & 1 deletion src/PHPCR/Shell/Console/Command/Phpcr/QueryUpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public function execute(InputInterface $input, OutputInterface $output)
$res = $updateParser->parse($sql);
$query = $res->offsetGet(0);
$updates = $res->offsetGet(1);
$applies = $res->offsetGet(3);

$start = microtime(true);
$result = $query->execute();
Expand All @@ -84,7 +85,11 @@ public function execute(InputInterface $input, OutputInterface $output)
foreach ($result as $row) {
$rows++;
foreach ($updates as $property) {
$updateProcessor->updateNode($row, $property);
$updateProcessor->updateNodeSet($row, $property);
}

foreach ($applies as $apply) {
$updateProcessor->updateNodeApply($row, $apply);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/PHPCR/Shell/Query/FunctionOperand.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function execute($functionMap, $row)

$callable = $functionMap[$functionName];
$args = $this->getArguments();
array_unshift($args, $row);
array_unshift($args, $this);
$value = call_user_func_array($callable, $args);

Expand Down
33 changes: 32 additions & 1 deletion src/PHPCR/Shell/Query/UpdateParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ private function doParse($sql2)
$this->sql2 = $sql2;
$source = null;
$constraint = null;
$updates = array();
$applies = array();

while ($this->scanner->lookupNextToken() !== '') {
switch (strtoupper($this->scanner->lookupNextToken())) {
Expand All @@ -51,6 +53,10 @@ private function doParse($sql2)
$this->scanner->expectToken('SET');
$updates = $this->parseUpdates();
break;
case 'APPLY':
$this->scanner->expectToken('APPLY');
$applies = $this->parseApply();
break;
case 'WHERE':
$this->scanner->expectToken('WHERE');
$constraint = $this->parseConstraint();
Expand All @@ -66,7 +72,7 @@ private function doParse($sql2)

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

$res = new \ArrayObject(array($query, $updates, $constraint));
$res = new \ArrayObject(array($query, $updates, $constraint, $applies));

return $res;
}
Expand Down Expand Up @@ -164,6 +170,31 @@ private function parseOperand()
return new ColumnOperand($columnData[0], $columnData[1]);
}

private function parseApply()
{
$functions = array();

while (true) {
$token = strtoupper($this->scanner->lookupNextToken());

if ($this->scanner->lookupNextToken(1) == '(') {
$functionData = $this->parseFunction();

$functions[] = new FunctionOperand($functionData[0], $functionData[1]);
}

$next = $this->scanner->lookupNextToken();

if ($next == ',') {
$next = $this->scanner->fetchNextToken();
} elseif (strtolower($next) == 'where' || !$next) {
break;
}
}

return $functions;
}

private function parseFunction()
{
$functionName = $this->scanner->fetchNextToken();
Expand Down
45 changes: 36 additions & 9 deletions src/PHPCR/Shell/Query/UpdateProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPCR\Shell\Query;

use PHPCR\Query\RowInterface;
use PHPCR\Shell\Query\FunctionOperand;

/**
* Processor for node updates
Expand All @@ -18,8 +19,22 @@ class UpdateProcessor

public function __construct()
{
$this->functionMap = array(
'array_replace' => function ($operand, $v, $x, $y) {
$this->functionMapApply = array(
'mixin_add' => function ($operand, $row, $mixinName) {
$node = $row->getNode();
$node->addMixin($mixinName);
},
'mixin_remove' => function ($operand, $row, $mixinName) {
$node = $row->getNode();

if ($node->isNodeType($mixinName)) {
$node->removeMixin($mixinName);
}
}
);

$this->functionMapSet = array(
'array_replace' => function ($operand, $row, $v, $x, $y) {
$operand->validateScalarArray($v);
foreach ($v as $key => $value) {
if ($value === $x) {
Expand All @@ -29,7 +44,7 @@ public function __construct()

return $v;
},
'array_remove' => function ($operand, $v, $x) {
'array_remove' => function ($operand, $row, $v, $x) {
foreach ($v as $key => $value) {
if ($value === $x) {
unset($v[$key]);
Expand All @@ -38,7 +53,7 @@ public function __construct()

return array_values($v);
},
'array_append' => function ($operand, $v, $x) {
'array_append' => function ($operand, $row, $v, $x) {
$operand->validateScalarArray($v);
$v[] = $x;

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

// first argument is the operand
array_shift($values);
// second is the row
array_shift($values);

return $values;
},
'array_replace_at' => function ($operand, $current, $index, $value) {
'array_replace_at' => function ($operand, $row, $current, $index, $value) {
if (!isset($current[$index])) {
throw new \InvalidArgumentException(sprintf(
'Multivalue index "%s" does not exist',
Expand Down Expand Up @@ -81,22 +98,32 @@ public function __construct()
* @param PHPCR\Query\RowInterface
* @param array
*/
public function updateNode(RowInterface $row, $propertyData)
public function updateNodeSet(RowInterface $row, $propertyData)
{
$node = $row->getNode($propertyData['selector']);
$value = $propertyData['value'];

if ($value instanceof FunctionOperand) {
$value = $this->handleFunction($row, $propertyData);
$value = $propertyData['value'];
$value = $value->execute($this->functionMapSet, $row);
}

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

public function updateNodeApply(RowInterface $row, FunctionOperand $apply)
{
if (!$apply instanceof FunctionOperand) {
throw new \InvalidArgumentException(
'Was expecting a function operand but got something else'
);
}

$apply->execute($this->functionMapApply, $row);
}

private function handleFunction($row, $propertyData)
{
$value = $propertyData['value'];
$value = $value->execute($this->functionMap, $row);

return $value;
}
Expand Down
1 change: 1 addition & 0 deletions src/PHPCR/Shell/Test/ContextBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public function theFixturesAreLoaded($arg1)
NodeHelper::purgeWorkspace($session);
$session->save();


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

Expand Down