Skip to content
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
34 changes: 34 additions & 0 deletions src/OrderBy/OrderByClause.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* This file is part of the event-engine/php-postgres-document-store.
* (c) 2019-2021 prooph software GmbH <contact@prooph.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace EventEngine\DocumentStore\Postgres\OrderBy;

final class OrderByClause
{
private $clause;
private $args;

public function __construct(?string $clause, array $args = [])
{
$this->clause = $clause;
$this->args = $args;
}

public function clause(): ?string
{
return $this->clause;
}

public function args(): array
{
return $this->args;
}
}
19 changes: 19 additions & 0 deletions src/OrderBy/OrderByProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
/**
* This file is part of the event-engine/php-postgres-document-store.
* (c) 2019-2021 prooph software GmbH <contact@prooph.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace EventEngine\DocumentStore\Postgres\OrderBy;

use EventEngine\DocumentStore\OrderBy\OrderBy;

interface OrderByProcessor
{
public function process(OrderBy $orderBy): OrderByClause;
}
60 changes: 60 additions & 0 deletions src/OrderBy/PostgresOrderByProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
/**
* This file is part of the event-engine/php-postgres-document-store.
* (c) 2019-2021 prooph software GmbH <contact@prooph.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace EventEngine\DocumentStore\Postgres\OrderBy;

use EventEngine\DocumentStore;
use EventEngine\DocumentStore\OrderBy\OrderBy;

final class PostgresOrderByProcessor implements OrderByProcessor
{
/**
* @var bool
*/
private $useMetadataColumns;

public function __construct(bool $useMetadataColumns = false)
{
$this->useMetadataColumns = $useMetadataColumns;
}

public function process(OrderBy $orderBy): OrderByClause
{
[$orderByClause, $args] = $this->processOrderBy($orderBy);

return new OrderByClause($orderByClause, $args);
}

private function processOrderBy(OrderBy $orderBy): array
{
if($orderBy instanceof DocumentStore\OrderBy\AndOrder) {
[$sortA, $sortAArgs] = $this->processOrderBy($orderBy->a());
[$sortB, $sortBArgs] = $this->processOrderBy($orderBy->b());

return ["$sortA, $sortB", array_merge($sortAArgs, $sortBArgs)];
}

/** @var DocumentStore\OrderBy\Asc|DocumentStore\OrderBy\Desc $orderBy */
$direction = $orderBy instanceof DocumentStore\OrderBy\Asc ? 'ASC' : 'DESC';
$prop = $this->propToJsonPath($orderBy->prop());

return ["{$prop} $direction", []];
}

private function propToJsonPath(string $field): string
{
if($this->useMetadataColumns && strpos($field, 'metadata.') === 0) {
return str_replace('metadata.', '', $field);
}

return "doc->'" . str_replace('.', "'->'", $field) . "'";
}
}
66 changes: 35 additions & 31 deletions src/PostgresDocumentStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
use EventEngine\DocumentStore\OrderBy\OrderBy;
use EventEngine\DocumentStore\PartialSelect;
use EventEngine\DocumentStore\Postgres\Exception\RuntimeException;
use EventEngine\DocumentStore\Postgres\Filter\PostgresFilterProcessor;
use EventEngine\DocumentStore\Postgres\Filter\FilterProcessor;
use EventEngine\DocumentStore\Postgres\Filter\PostgresFilterProcessor;
use EventEngine\DocumentStore\Postgres\OrderBy\OrderByClause;
use EventEngine\DocumentStore\Postgres\OrderBy\OrderByProcessor;
use EventEngine\DocumentStore\Postgres\OrderBy\PostgresOrderByProcessor;
use EventEngine\Util\VariableType;

use function implode;
Expand All @@ -43,6 +46,11 @@ final class PostgresDocumentStore implements DocumentStore\DocumentStore
*/
private $filterProcessor;

/**
* @var OrderByProcessor
*/
private $orderByProcessor;

private $tablePrefix = 'em_ds_';

private $docIdSchema = 'UUID NOT NULL';
Expand All @@ -57,7 +65,8 @@ public function __construct(
string $docIdSchema = null,
bool $transactional = true,
bool $useMetadataColumns = false,
FilterProcessor $filterProcessor = null
FilterProcessor $filterProcessor = null,
OrderByProcessor $orderByProcessor = null
) {
$this->connection = $connection;
$this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
Expand All @@ -67,6 +76,11 @@ public function __construct(
}
$this->filterProcessor = $filterProcessor;

if (null === $orderByProcessor) {
$orderByProcessor = new PostgresOrderByProcessor($useMetadataColumns);
}
$this->orderByProcessor = $orderByProcessor;

if(null !== $tablePrefix) {
$this->tablePrefix = $tablePrefix;
}
Expand Down Expand Up @@ -441,7 +455,7 @@ public function upsertDoc(string $collectionName, string $docId, array $docOrSub
{
$doc = $this->getDoc($collectionName, $docId);

if ($doc !== null) {
if($doc !== null) {
$this->updateDoc($collectionName, $docId, $docOrSubset);
} else {
$this->addDoc($collectionName, $docId, $docOrSubset);
Expand Down Expand Up @@ -625,12 +639,16 @@ public function filterDocs(string $collectionName, Filter $filter, int $skip = n
$filterStr = $filterClause->clause();
$args = $filterClause->args();

$orderByClause = $orderBy ? $this->orderByProcessor->process($orderBy) : new OrderByClause(null, []);
$orderByStr = $orderByClause->clause();
$orderByArgs = $orderByClause->args();

$where = $filterStr ? "WHERE $filterStr" : '';

$offset = $skip !== null ? "OFFSET $skip" : '';
$limit = $limit !== null ? "LIMIT $limit" : '';

$orderBy = $orderBy ? "ORDER BY " . implode(', ', $this->orderByToSort($orderBy)) : '';
$orderBy = $orderByStr ? "ORDER BY $orderByStr" : '';

$query = <<<EOT
SELECT doc
Expand All @@ -642,7 +660,7 @@ public function filterDocs(string $collectionName, Filter $filter, int $skip = n
EOT;
$stmt = $this->connection->prepare($query);

$stmt->execute($args);
$stmt->execute(array_merge($args, $orderByArgs));

while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
yield json_decode($row['doc'], true);
Expand All @@ -658,12 +676,16 @@ public function findDocs(string $collectionName, Filter $filter, int $skip = nul
$filterStr = $filterClause->clause();
$args = $filterClause->args();

$orderByClause = $orderBy ? $this->orderByProcessor->process($orderBy) : new OrderByClause(null, []);
$orderByStr = $orderByClause->clause();
$orderByArgs = $orderByClause->args();

$where = $filterStr ? "WHERE $filterStr" : '';

$offset = $skip !== null ? "OFFSET $skip" : '';
$limit = $limit !== null ? "LIMIT $limit" : '';

$orderBy = $orderBy ? "ORDER BY " . implode(', ', $this->orderByToSort($orderBy)) : '';
$orderBy = $orderByStr ? "ORDER BY $orderByStr" : '';

$query = <<<EOT
SELECT id, doc
Expand All @@ -675,7 +697,7 @@ public function findDocs(string $collectionName, Filter $filter, int $skip = nul
EOT;
$stmt = $this->connection->prepare($query);

$stmt->execute($args);
$stmt->execute(array_merge($args, $orderByArgs));

while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
yield $row['id'] => json_decode($row['doc'], true);
Expand All @@ -688,14 +710,18 @@ public function findPartialDocs(string $collectionName, PartialSelect $partialSe
$filterStr = $filterClause->clause();
$args = $filterClause->args();

$orderByClause = $orderBy ? $this->orderByProcessor->process($orderBy) : new OrderByClause(null, []);
$orderByStr = $orderByClause->clause();
$orderByArgs = $orderByClause->args();

$select = $this->makeSelect($partialSelect);

$where = $filterStr ? "WHERE $filterStr" : '';

$offset = $skip !== null ? "OFFSET $skip" : '';
$limit = $limit !== null ? "LIMIT $limit" : '';

$orderBy = $orderBy ? "ORDER BY " . implode(', ', $this->orderByToSort($orderBy)) : '';
$orderBy = $orderByStr ? "ORDER BY $orderByStr" : '';

$query = <<<EOT
SELECT $select
Expand All @@ -708,7 +734,7 @@ public function findPartialDocs(string $collectionName, PartialSelect $partialSe

$stmt = $this->connection->prepare($query);

$stmt->execute($args);
$stmt->execute(array_merge($args, $orderByArgs));

while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
yield $row[self::PARTIAL_SELECT_DOC_ID] => $this->transformPartialDoc($partialSelect, $row);
Expand Down Expand Up @@ -870,28 +896,6 @@ private function transformPartialDoc(PartialSelect $partialSelect, array $select
return $partialDoc;
}

private function orderByToSort(DocumentStore\OrderBy\OrderBy $orderBy): array
{
$sort = [];

if($orderBy instanceof DocumentStore\OrderBy\AndOrder) {
/** @var DocumentStore\OrderBy\Asc|DocumentStore\OrderBy\Desc $orderByA */
$orderByA = $orderBy->a();
$direction = $orderByA instanceof DocumentStore\OrderBy\Asc ? 'ASC' : 'DESC';
$prop = $this->propToJsonPath($orderByA->prop());
$sort[] = "{$prop} $direction";

$sortB = $this->orderByToSort($orderBy->b());

return array_merge($sort, $sortB);
}

/** @var DocumentStore\OrderBy\Asc|DocumentStore\OrderBy\Desc $orderBy */
$direction = $orderBy instanceof DocumentStore\OrderBy\Asc ? 'ASC' : 'DESC';
$prop = $this->propToJsonPath($orderBy->prop());
return ["{$prop} $direction"];
}

private function indexToSqlCmd(Index $index, string $collectionName): string
{
if($index instanceof DocumentStore\FieldIndex) {
Expand Down
89 changes: 89 additions & 0 deletions tests/PostgresDocumentStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
use EventEngine\DocumentStore\Filter\LtFilter;
use EventEngine\DocumentStore\Filter\NotFilter;
use EventEngine\DocumentStore\Filter\OrFilter;
use EventEngine\DocumentStore\OrderBy\AndOrder;
use EventEngine\DocumentStore\OrderBy\Asc;
use EventEngine\DocumentStore\OrderBy\Desc;
use EventEngine\DocumentStore\PartialSelect;
use PHPUnit\Framework\TestCase;
use EventEngine\DocumentStore\FieldIndex;
Expand Down Expand Up @@ -813,6 +816,92 @@ public function it_counts_any_of_filter()
$this->assertSame(2, $count);
}

/**
* @test
*/
public function it_handles_order_by()
{
$collectionName = 'test_it_handles_order_by';
$this->documentStore->addCollection($collectionName);

$this->documentStore->addDoc($collectionName, Uuid::uuid4()->toString(), ['some' => ['prop' => 'foo']]);
$this->documentStore->addDoc($collectionName, Uuid::uuid4()->toString(), ['some' => ['prop' => 'bar']]);
$this->documentStore->addDoc($collectionName, Uuid::uuid4()->toString(), ['some' => ['prop' => 'bas']]);

$filteredDocs = \array_values(\iterator_to_array($this->documentStore->findDocs(
$collectionName,
new AnyFilter(),
null,
null,
Asc::fromString('some.prop')
)));

$this->assertCount(3, $filteredDocs);

$this->assertEquals(
[
['some' => ['prop' => 'bar']],
['some' => ['prop' => 'bas']],
['some' => ['prop' => 'foo']],
],
$filteredDocs
);

$filteredDocs = \array_values(\iterator_to_array($this->documentStore->findDocs(
$collectionName,
new AnyFilter(),
null,
null,
Desc::fromString('some.prop')
)));

$this->assertCount(3, $filteredDocs);

$this->assertEquals(
[
['some' => ['prop' => 'foo']],
['some' => ['prop' => 'bas']],
['some' => ['prop' => 'bar']],
],
$filteredDocs
);
}

/**
* @test
*/
public function it_handles_and_order_by()
{
$collectionName = 'test_it_handles_order_by';
$this->documentStore->addCollection($collectionName);

$this->documentStore->addDoc($collectionName, Uuid::uuid4()->toString(), ['some' => ['prop' => 'foo', 'other' => ['prop' => 'bas']]]);
$this->documentStore->addDoc($collectionName, Uuid::uuid4()->toString(), ['some' => ['prop' => 'bar', 'other' => ['prop' => 'bat']]]);
$this->documentStore->addDoc($collectionName, Uuid::uuid4()->toString(), ['some' => ['prop' => 'bar']]);

$filteredDocs = \array_values(\iterator_to_array($this->documentStore->findDocs(
$collectionName,
new AnyFilter(),
null,
null,
AndOrder::by(
Asc::fromString('some.prop'),
Desc::fromString('some.other')
)
)));

$this->assertCount(3, $filteredDocs);

$this->assertEquals(
[
['some' => ['prop' => 'bar']],
['some' => ['prop' => 'bar', 'other' => ['prop' => 'bat']]],
['some' => ['prop' => 'foo', 'other' => ['prop' => 'bas']]],
],
$filteredDocs
);
}

/**
* @test
*/
Expand Down