Skip to content

[query] Full support for manipulating multivalue properties #90

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 4 commits into from
Oct 18, 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ dev-master

### Features

- [query] Full support for manipulating multivalue properties

alpha-5
-------

### Features

- [shell] Added "shell:clear" command to support clearing the console output
- [general] The shell supports being embedded as a dependency
- [node:edit] New command `node:edit` enables editing of entire node
Expand Down
88 changes: 82 additions & 6 deletions features/phpcr_query_update.feature
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,97 @@ Feature: Execute a a raw UPDATE query in JCR_SQL2
| UPDATE nt:unstructured AS a SET title = 'DTL' WHERE localname() = 'article1' | /cms/articles/article1 | title | DTL |
| UPDATE nt:unstructured AS a SET title = 'DTL', foobar='barfoo' WHERE localname() = 'article1' | /cms/articles/article1 | foobar | barfoo |

Scenario: Update multivalue index by value
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = 'Rockets' WHERE a.tags = 'Trains'" command
Scenario: Replace a multivalue index by value
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_replace(a.tags, 'Trains', 'Rockets') WHERE a.tags = 'Trains'" command
Then the command should not fail
And I save the session
Then the command should not fail
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "1"

Scenario: Set a multivalue value
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array('Rockets', 'Dragons') WHERE a.tags = 'Trains'" command
And I save the session
Then the command should not fail
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Dragons" at index "1"

Scenario: Update single multivalue without selector
Given I execute the "UPDATE [nt:unstructured] SET tags = array_replace(tags, 'Planes', 'Rockets') WHERE tags = 'Planes'" command
And I save the session
Then the command should not fail
And I should see the following:
"""
Cannot update property "tags". Updating multi-value nodes with more than one element not currently supported
1 row(s) affected
"""
And the node at "/cms/articles/article1" should have the property "tags" with value "Rockets" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "2"

Scenario: Update single multivalue
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tag = 'Rockets' WHERE a.tags = 'Planes'" command
Scenario: Remove single multivalue
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_remove(a.tags, 'Planes') WHERE a.tags = 'Planes'" command
And I save the session
Then the command should not fail
And I should see the following:
"""
1 row(s) affected
"""
And the node at "/cms/articles/article1" should have the property "tag" with value "Rockets" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Trains" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "1"

Scenario: Remove single multivalue by index
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, 0, NULL) WHERE a.tags = 'Planes'" command
And I save the session
Then the command should not fail
And I should see the following:
"""
1 row(s) affected
"""
And the node at "/cms/articles/article1" should have the property "tags" with value "Trains" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "1"

Scenario: Add a multivalue property
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_append(a.tags, 'Kite') WHERE a.tags = 'Planes'" command
And I save the session
Then the command should not fail
And I should see the following:
"""
1 row(s) affected
"""
And the node at "/cms/articles/article1" should have the property "tags" with value "Planes" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Automobiles" at index "2"
And the node at "/cms/articles/article1" should have the property "tags" with value "Kite" at index "3"

Scenario: Replace a multivalue property by index
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
And I save the session
Then the command should not fail
And I should see the following:
"""
1 row(s) affected
"""
And the node at "/cms/articles/article1" should have the property "tags" with value "Planes" at index "0"
And the node at "/cms/articles/article1" should have the property "tags" with value "Kite" at index "1"
And the node at "/cms/articles/article1" should have the property "tags" with value "foobar" at index "2"

Scenario: Replace a multivalue property by invalid index
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, 10, 'Kite') WHERE a.tags = 'Planes'" command
Then the command should fail
And I should see the following:
"""
Multivalue index "10" does not exist
"""

Scenario: Attempt to update a numerically named property (must use a selector)
Given I execute the "UPDATE [nt:unstructured] AS a SET a.tags = array_set(a.tags, a.10, 'Kite') WHERE a.tags = 'Planes'" command
Then the command should fail
And I should see the following:
"""
[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_set(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
"""
24 changes: 22 additions & 2 deletions spec/PHPCR/Shell/Query/UpdateParserSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use PHPCR\Query\QOM\LiteralInterface;
use PHPCR\Query\QOM\ComparisonInterface;
use PHPCR\Query\QueryInterface;
use PHPCR\Shell\Query\FunctionOperand;
use PHPCR\Shell\Query\ColumnOperand;

class UpdateParserSpec extends ObjectBehavior
{
Expand Down Expand Up @@ -66,16 +68,34 @@ function it_should_provide_a_qom_object_for_selecting(

$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
$res->offsetGet(1)->shouldReturn(array(
'parent.foo' => array(
array(
'selector' => 'parent',
'name' => 'foo',
'value' => 'PHPCR\\FOO\\Bar',
),
'parent.bar' => array(
array(
'selector' => 'parent',
'name' => 'bar',
'value' => 'foo',
),
));
}

function it_should_parse_functions (
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 SET a.tags = array_replace(a.tags, 'asd', 'dsa')
EOT;
$res = $this->parse($sql);

$res->offsetGet(0)->shouldHaveType('PHPCR\Query\QueryInterface');
}
}
2 changes: 1 addition & 1 deletion src/PHPCR/Shell/Console/Application/SessionApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class SessionApplication extends BaseApplication
{
const APP_NAME = 'PHPCRSH';
const APP_VERSION = '1.0.0-alpha4';
const APP_VERSION = '1.0.0-alpha5';

protected $shellApplication;

Expand Down
66 changes: 40 additions & 26 deletions src/PHPCR/Shell/Console/Command/Phpcr/QueryUpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,53 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use PHPCR\Shell\Query\UpdateParser;
use Jackalope\Query\QOM\ComparisonConstraint;
use Jackalope\Query\QOM\PropertyValue;
use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
use PHPCR\Query\QOM\LiteralInterface;
use PHPCR\Shell\Query\UpdateProcessor;

class QueryUpdateCommand extends Command
{
/**
* @var OutputInterface
*/
protected $output;

protected function configure()
{
$this->setName('update');
$this->setDescription('Execute an UPDATE JCR-SQL2 query');
$this->addArgument('query');
$this->setHelp(<<<EOT
Execute a JCR-SQL2 update query. Unlike other commands you can enter a query literally:
Execute a PHPCR-Shell JCR-SQL2 update query. You can enter a query literally:

UPDATE [nt:unstructured] AS a SET title = 'foobar' WHERE a.title = 'barfoo';

You can also manipulate multivalue fields:

# Delete index


And you have access to a set of functions when assigning a value:

# Delete a multivalue index
UPDATE [nt:unstructured] SET a.tags = array_set(a.tags, 0, NULL)

# Set a multivalue index
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this be overwriting or insert at that position?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overwriting

UPDATE [nt:unstructured] SET a.tags = array_set(a.tags, 0, 'foo')

# Replace the multivalue value "Planes" with "Trains"
UPDATE [nt:unstructured] AS a SET a.tags[] = array_replace(a.tags, 'Planes', 'Trains')

# Append a multivalue
UPDATE [nt:unstructured] AS a SET a.tags = array_append(a.tags, 'Rockets')

# Remove by value
UPDATE [nt:unstructured] AS a SET a.tags = array_remove(a.tags, 'Plains')

Refer to the documentation for a full reference: http://phpcr.readthedocs.org/en/latest/phpcr-shell

You must call <info>session:save</info> to persist changes.

Note that this command is not part of the JCR-SQL2 language but is implemented specifically
Expand All @@ -29,6 +63,7 @@ protected function configure()

public function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
$sql = $input->getRawCommand();

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

$updateProcessor = new UpdateProcessor();

foreach ($result as $row) {
$rows++;
foreach ($updates as $field => $property) {
$node = $row->getNode($property['selector']);

if ($node->hasProperty($property['name'])) {
$phpcrProperty = $node->getProperty($property['name']);

if ($phpcrProperty->isMultiple()) {
$currentValue = $phpcrProperty->getValue();

if (sizeof($currentValue) > 1) {
$output->writeln(sprintf(
'<error>Cannot update property "%s". Updating multi-value nodes with more than one element not currently supported</error>',
$phpcrProperty->getName()
));
$output->writeln(sprintf(
'<error>See: https://github.com/phpcr/phpcr-shell/issues/81</error>',
$phpcrProperty->getName()
));
}

$property['value'] = (array) $property['value'];
}
}

$node->setProperty($property['name'], $property['value']);
foreach ($updates as $property) {
$updateProcessor->updateNode($row, $property);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/PHPCR/Shell/Console/Helper/ResultFormatterHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ public function formatQueryResult(QueryResultInterface $result, OutputInterface
$table = new TableHelper;
$table->setHeaders(array_merge(array(
'Path',
'Index',
), $result->getColumnNames()));

foreach ($result->getRows() as $i => $row) {
$values = array_merge(array(
$row->getPath(),
$row->getNode()->getIndex(),
), $row->getValues());

foreach ($values as $columnName => &$value) {
Expand Down
31 changes: 31 additions & 0 deletions src/PHPCR/Shell/Query/ColumnOperand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace PHPCR\Shell\Query;

/**
* Simple class to represent column operands in query
* evaluations.
*
* @author Daniel Leech <daniel@dantleech.com>
*/
class ColumnOperand
{
private $selectorName;
private $propertyName;

public function __construct($selectorName, $propertyName)
{
$this->selectorName = $selectorName;
$this->propertyName = $propertyName;
}

public function getSelectorName()
{
return $this->selectorName;
}

public function getPropertyName()
{
return $this->propertyName;
}
}
Loading