Skip to content

Commit

Permalink
Add refreshMaterializedView command
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerych1984 committed Jan 19, 2024
1 parent be590ac commit 5a3d4e9
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 1 deletion.
56 changes: 56 additions & 0 deletions src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

namespace Yiisoft\Db\Pgsql;

use InvalidArgumentException;
use LogicException;
use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand;

use function sprintf;

/**
* Implements a database command that can be executed with a PDO (PHP Data Object) database connection for PostgreSQL
* Server.
Expand All @@ -20,4 +24,56 @@ public function showDatabases(): array

return $this->setSql($sql)->queryColumn();
}

/**
* @see {https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html}
*
* @param string $viewName
* @param bool|null $concurrently Add [ CONCURRENTLY ] to refresh command
* @param bool|null $withData Add [ WITH [ NO ] DATA ] to refresh command
* @return void

Check failure on line 34 in src/Command.php

View workflow job for this annotation

GitHub Actions / PHP 8.0

MismatchingDocblockReturnType

src/Command.php:34:16: MismatchingDocblockReturnType: Docblock has incorrect return type 'void', should be 'bool' (see https://psalm.dev/142)

Check failure on line 34 in src/Command.php

View workflow job for this annotation

GitHub Actions / PHP 8.0

MixedInferredReturnType

src/Command.php:34:16: MixedInferredReturnType: Could not verify return type 'void' for Yiisoft\Db\Pgsql\Command::refreshMaterializedView (see https://psalm.dev/047)

Check failure on line 34 in src/Command.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MismatchingDocblockReturnType

src/Command.php:34:16: MismatchingDocblockReturnType: Docblock has incorrect return type 'void', should be 'bool' (see https://psalm.dev/142)

Check failure on line 34 in src/Command.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedInferredReturnType

src/Command.php:34:16: MixedInferredReturnType: Could not verify return type 'void' for Yiisoft\Db\Pgsql\Command::refreshMaterializedView (see https://psalm.dev/047)

Check failure on line 34 in src/Command.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MismatchingDocblockReturnType

src/Command.php:34:16: MismatchingDocblockReturnType: Docblock has incorrect return type 'void', should be 'bool' (see https://psalm.dev/142)

Check failure on line 34 in src/Command.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedInferredReturnType

src/Command.php:34:16: MixedInferredReturnType: Could not verify return type 'void' for Yiisoft\Db\Pgsql\Command::refreshMaterializedView (see https://psalm.dev/047)
* @throws \Throwable
* @throws \Yiisoft\Db\Exception\Exception
* @throws \Yiisoft\Db\Exception\InvalidConfigException
*/
public function refreshMaterializedView(string $viewName, ?bool $concurrently = null, ?bool $withData = null): bool
{
if ($concurrently || ($concurrently === null || $withData === null)) {

$tableSchema = $this->db->getTableSchema($viewName);

if ($tableSchema) {
$hasUnique = count($this->db->getSchema()->findUniqueIndexes($tableSchema)) > 0;
} else {
throw new InvalidArgumentException(
sprintf('"%s" not found in DB', $viewName)
);
}

if ($concurrently && !$hasUnique) {
throw new LogicException('CONCURRENTLY refresh is not allowed without unique index.');
}

$concurrently = $hasUnique;
}

$sql = 'REFRESH MATERIALIZED VIEW';

if ($concurrently) {

if ($withData === false) {
throw new LogicException('CONCURRENTLY and WITH NO DATA may not be specified together.');
}

$sql .= ' CONCURRENTLY';

Check warning on line 68 in src/Command.php

View check run for this annotation

Codecov / codecov/patch

src/Command.php#L68

Added line #L68 was not covered by tests
}

$sql .= ' ' . $this->db->getQuoter()->quoteTableName($viewName);

if (is_bool($withData)) {
$sql .= ' WITH ' . ($withData ? 'DATA' : 'NO DATA');
}

return $this->setSql($sql)->execute() === 0;
}
}
77 changes: 76 additions & 1 deletion tests/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@

namespace Yiisoft\Db\Pgsql\Tests;

use InvalidArgumentException;
use LogicException;
use Throwable;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\JsonExpression;
use Yiisoft\Db\Pgsql\Command;
use Yiisoft\Db\Pgsql\Connection;
use Yiisoft\Db\Pgsql\Dsn;
use Yiisoft\Db\Pgsql\Driver;
use Yiisoft\Db\Pgsql\Dsn;
use Yiisoft\Db\Pgsql\Tests\Support\TestTrait;
use Yiisoft\Db\Tests\Common\CommonCommandTest;
use Yiisoft\Db\Tests\Support\DbHelper;
Expand Down Expand Up @@ -330,4 +333,76 @@ public function testShowDatabases(): void
$this->assertSame('pgsql:host=127.0.0.1;dbname=postgres;port=5432', $db->getDriver()->getDsn());
$this->assertSame(['yiitest'], $command->showDatabases());
}

public function testRefreshMaterializesView(): void
{
$db = $this->getConnection(true);
/** @var Command $command */
$command = $db->createCommand();

$this->assertTrue($command->refreshMaterializedView('mat_view_without_unique'));
$this->assertTrue($command->refreshMaterializedView('mat_view_without_unique', false, true));
$this->assertTrue($command->refreshMaterializedView('mat_view_without_unique', false, true));
}

public static function materializedViewExceptionsDataProvider(): array
{
return [
[
'mat_view_without_unique',
true,
null,
LogicException::class,
'CONCURRENTLY refresh is not allowed without unique index.'
],

[
'mat_view_with_unique',
true,
false,
LogicException::class,
'CONCURRENTLY and WITH NO DATA may not be specified together.'
],

[
'mat_view_with_unique',
null,
false,
LogicException::class,
'CONCURRENTLY and WITH NO DATA may not be specified together.'
],

[
'not_exists_mat_view',
null,
null,
InvalidArgumentException::class,
'"not_exists_mat_view" not found in DB'
]
];
}

/**
* @dataProvider materializedViewExceptionsDataProvider
* @param string $viewName
* @param bool|null $concurrently
* @param bool|null $withData
* @param string $exception
* @param string $message
* @return void
* @throws Exception
* @throws InvalidConfigException
*/
public function testRefreshMaterializesViewExceptions(string $viewName, ?bool $concurrently, ?bool $withData, string $exception, string $message): void
{
$db = $this->getConnection(true);

/** @var Command $command */
$command = $db->createCommand();

$this->expectException($exception);
$this->expectExceptionMessage($message);

$command->refreshMaterializedView($viewName, $concurrently, $withData);
}
}
2 changes: 2 additions & 0 deletions tests/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,8 @@ public function testGetViewNames(): void
'T_constraints_3_view',
'T_constraints_4_view',
'animal_view',
'mat_view_with_unique',
'mat_view_without_unique',
],
$views,
);
Expand Down
7 changes: 7 additions & 0 deletions tests/Support/Fixture/pgsql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -487,3 +487,10 @@ CREATE TABLE "test_composite_type"
"price_array2" "currency_money_composite"[][],
"range_price_col" "range_price_composite" DEFAULT '("(0,USD)","(100,USD)")'
);


DROP MATERIALIZED VIEW IF EXISTS "mat_view_without_unique";
DROP MATERIALIZED VIEW IF EXISTS "mat_view_with_unique";
CREATE MATERIALIZED VIEW "mat_view_without_unique" AS SELECT * FROM "test_composite_type";
CREATE MATERIALIZED VIEW "mat_view_with_unique" AS SELECT * FROM "test_composite_type";
CREATE UNIQUE INDEX "mat_view_with_unique_idx" ON "mat_view_with_unique" ("id");

0 comments on commit 5a3d4e9

Please sign in to comment.