Skip to content

Commit f07182c

Browse files
committed
Add refreshMaterializedView command
1 parent be590ac commit f07182c

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

src/Command.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44

55
namespace Yiisoft\Db\Pgsql;
66

7+
use InvalidArgumentException;
8+
use LogicException;
79
use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand;
810

11+
use function sprintf;
12+
913
/**
1014
* Implements a database command that can be executed with a PDO (PHP Data Object) database connection for PostgreSQL
1115
* Server.
@@ -20,4 +24,56 @@ public function showDatabases(): array
2024

2125
return $this->setSql($sql)->queryColumn();
2226
}
27+
28+
/**
29+
* @see {https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html}
30+
*
31+
* @param string $viewName
32+
* @param bool|null $concurrently Add [ CONCURRENTLY ] to refresh command
33+
* @param bool|null $withData Add [ WITH [ NO ] DATA ] to refresh command
34+
* @return bool
35+
* @throws \Throwable
36+
* @throws \Yiisoft\Db\Exception\Exception
37+
* @throws \Yiisoft\Db\Exception\InvalidConfigException
38+
*/
39+
public function refreshMaterializedView(string $viewName, ?bool $concurrently = null, ?bool $withData = null): bool
40+
{
41+
if ($concurrently || ($concurrently === null || $withData === null)) {
42+
43+
$tableSchema = $this->db->getTableSchema($viewName);
44+
45+
if ($tableSchema) {
46+
$hasUnique = count($this->db->getSchema()->findUniqueIndexes($tableSchema)) > 0;
47+
} else {
48+
throw new InvalidArgumentException(
49+
sprintf('"%s" not found in DB', $viewName)
50+
);
51+
}
52+
53+
if ($concurrently && !$hasUnique) {
54+
throw new LogicException('CONCURRENTLY refresh is not allowed without unique index.');
55+
}
56+
57+
$concurrently = $hasUnique;
58+
}
59+
60+
$sql = 'REFRESH MATERIALIZED VIEW';
61+
62+
if ($concurrently) {
63+
64+
if ($withData === false) {
65+
throw new LogicException('CONCURRENTLY and WITH NO DATA may not be specified together.');
66+
}
67+
68+
$sql .= ' CONCURRENTLY';
69+
}
70+
71+
$sql .= ' ' . $this->db->getQuoter()->quoteTableName($viewName);
72+
73+
if (is_bool($withData)) {
74+
$sql .= ' WITH ' . ($withData ? 'DATA' : 'NO DATA');
75+
}
76+
77+
return $this->setSql($sql)->execute() === 0;
78+
}
2379
}

tests/CommandTest.php

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44

55
namespace Yiisoft\Db\Pgsql\Tests;
66

7+
use InvalidArgumentException;
8+
use LogicException;
79
use Throwable;
810
use Yiisoft\Db\Exception\Exception;
911
use Yiisoft\Db\Exception\InvalidConfigException;
1012
use Yiisoft\Db\Exception\NotSupportedException;
1113
use Yiisoft\Db\Expression\JsonExpression;
14+
use Yiisoft\Db\Pgsql\Command;
1215
use Yiisoft\Db\Pgsql\Connection;
13-
use Yiisoft\Db\Pgsql\Dsn;
1416
use Yiisoft\Db\Pgsql\Driver;
17+
use Yiisoft\Db\Pgsql\Dsn;
1518
use Yiisoft\Db\Pgsql\Tests\Support\TestTrait;
1619
use Yiisoft\Db\Tests\Common\CommonCommandTest;
1720
use Yiisoft\Db\Tests\Support\DbHelper;
@@ -330,4 +333,76 @@ public function testShowDatabases(): void
330333
$this->assertSame('pgsql:host=127.0.0.1;dbname=postgres;port=5432', $db->getDriver()->getDsn());
331334
$this->assertSame(['yiitest'], $command->showDatabases());
332335
}
336+
337+
public function testRefreshMaterializesView(): void
338+
{
339+
$db = $this->getConnection(true);
340+
/** @var Command $command */
341+
$command = $db->createCommand();
342+
343+
$this->assertTrue($command->refreshMaterializedView('mat_view_without_unique'));
344+
$this->assertTrue($command->refreshMaterializedView('mat_view_without_unique', false, true));
345+
$this->assertTrue($command->refreshMaterializedView('mat_view_without_unique', false, true));
346+
}
347+
348+
public static function materializedViewExceptionsDataProvider(): array
349+
{
350+
return [
351+
[
352+
'mat_view_without_unique',
353+
true,
354+
null,
355+
LogicException::class,
356+
'CONCURRENTLY refresh is not allowed without unique index.'
357+
],
358+
359+
[
360+
'mat_view_with_unique',
361+
true,
362+
false,
363+
LogicException::class,
364+
'CONCURRENTLY and WITH NO DATA may not be specified together.'
365+
],
366+
367+
[
368+
'mat_view_with_unique',
369+
null,
370+
false,
371+
LogicException::class,
372+
'CONCURRENTLY and WITH NO DATA may not be specified together.'
373+
],
374+
375+
[
376+
'not_exists_mat_view',
377+
null,
378+
null,
379+
InvalidArgumentException::class,
380+
'"not_exists_mat_view" not found in DB'
381+
]
382+
];
383+
}
384+
385+
/**
386+
* @dataProvider materializedViewExceptionsDataProvider
387+
* @param string $viewName
388+
* @param bool|null $concurrently
389+
* @param bool|null $withData
390+
* @param string $exception
391+
* @param string $message
392+
* @return void
393+
* @throws Exception
394+
* @throws InvalidConfigException
395+
*/
396+
public function testRefreshMaterializesViewExceptions(string $viewName, ?bool $concurrently, ?bool $withData, string $exception, string $message): void
397+
{
398+
$db = $this->getConnection(true);
399+
400+
/** @var Command $command */
401+
$command = $db->createCommand();
402+
403+
$this->expectException($exception);
404+
$this->expectExceptionMessage($message);
405+
406+
$command->refreshMaterializedView($viewName, $concurrently, $withData);
407+
}
333408
}

tests/SchemaTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,8 @@ public function testGetViewNames(): void
574574
'T_constraints_3_view',
575575
'T_constraints_4_view',
576576
'animal_view',
577+
'mat_view_with_unique',
578+
'mat_view_without_unique',
577579
],
578580
$views,
579581
);

tests/Support/Fixture/pgsql.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,10 @@ CREATE TABLE "test_composite_type"
487487
"price_array2" "currency_money_composite"[][],
488488
"range_price_col" "range_price_composite" DEFAULT '("(0,USD)","(100,USD)")'
489489
);
490+
491+
492+
DROP MATERIALIZED VIEW IF EXISTS "mat_view_without_unique";
493+
DROP MATERIALIZED VIEW IF EXISTS "mat_view_with_unique";
494+
CREATE MATERIALIZED VIEW "mat_view_without_unique" AS SELECT * FROM "test_composite_type";
495+
CREATE MATERIALIZED VIEW "mat_view_with_unique" AS SELECT * FROM "test_composite_type";
496+
CREATE UNIQUE INDEX "mat_view_with_unique_idx" ON "mat_view_with_unique" ("id");

0 commit comments

Comments
 (0)