Skip to content

Commit 110ad49

Browse files
committed
fix: postgresql schema comparator handling views
1 parent d00d301 commit 110ad49

6 files changed

Lines changed: 325 additions & 39 deletions

File tree

composer.lock

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/postgresql/src/Flow/PostgreSql/Schema/Diff/SchemaComparator.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Flow\PostgreSql\Schema\Table;
1919
use Flow\PostgreSql\Schema\View;
2020
use Flow\PostgreSql\Schema\ViewDependencyOrder;
21+
use Throwable;
2122

2223
final readonly class SchemaComparator
2324
{
@@ -36,6 +37,7 @@ public function __construct(
3637
private ExecutionOrderStrategy $materializedViewOrderStrategy = new MaterializedViewDependencyOrder(
3738
new Parser(),
3839
),
40+
private Parser $parser = new Parser(),
3941
) {}
4042

4143
public function compare(Schema $source, Schema $target): SchemaDiff
@@ -65,7 +67,7 @@ public function compare(Schema $source, Schema $target): SchemaDiff
6567
$source->views,
6668
$target->views,
6769
static fn(View $v): string => $v->name,
68-
static fn(View $a, View $b): ?ViewDiff => $a->definition === $b->definition
70+
fn(View $a, View $b): ?ViewDiff => $this->definitionsEqual($a->definition, $b->definition)
6971
&& $a->isUpdatable === $b->isUpdatable
7072
? null
7173
: new ViewDiff($a, $b),
@@ -139,6 +141,11 @@ public function compare(Schema $source, Schema $target): SchemaDiff
139141
);
140142
}
141143

144+
private function definitionsEqual(string $a, string $b): bool
145+
{
146+
return $this->normalizeDefinition($a) === $this->normalizeDefinition($b);
147+
}
148+
142149
/**
143150
* @param list<Domain> $sourceDomains
144151
* @param list<Domain> $targetDomains
@@ -187,7 +194,7 @@ function (MaterializedView $a, MaterializedView $b): ?MaterializedViewDiff {
187194
$indexChanges = $this->indexComparator->compare($a->indexes, $b->indexes);
188195

189196
if (
190-
$a->definition === $b->definition
197+
$this->definitionsEqual($a->definition, $b->definition)
191198
&& $indexChanges->added === []
192199
&& $indexChanges->removed === []
193200
&& ($indexChanges->renamed === null || $indexChanges->renamed === [])
@@ -221,4 +228,13 @@ private function diffTables(array $sourceTables, array $targetTables): ChangeSet
221228

222229
return new ChangeSet($renameResult->added, $renameResult->removed, $initial->modified, $renameResult->renamed);
223230
}
231+
232+
private function normalizeDefinition(string $definition): string
233+
{
234+
try {
235+
return $this->parser->parse($definition)->deparse();
236+
} catch (Throwable) {
237+
return $definition;
238+
}
239+
}
224240
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\PostgreSql\Tests\Integration\QueryBuilder\Database;
6+
7+
use Flow\PostgreSql\Client\Infrastructure\PgSql\PgCatalogProvider;
8+
use Flow\PostgreSql\Schema\Diff\ConstraintComparator;
9+
use Flow\PostgreSql\Schema\Diff\GreedySimilarityRenameStrategy;
10+
use Flow\PostgreSql\Schema\Diff\IndexComparator;
11+
use Flow\PostgreSql\Schema\Diff\SchemaComparator;
12+
use Flow\PostgreSql\Schema\Diff\SimilarTextStrategy;
13+
use Flow\PostgreSql\Schema\Diff\TableComparator;
14+
use Flow\PostgreSql\Schema\Diff\TableStructureComparator;
15+
use Flow\PostgreSql\Schema\MaterializedView;
16+
use Flow\PostgreSql\Schema\Schema;
17+
use Flow\PostgreSql\Schema\View;
18+
use Flow\PostgreSql\Tests\Integration\PostgreSqlTestCase;
19+
20+
use function array_filter;
21+
use function array_values;
22+
use function Flow\PostgreSql\DSL\col;
23+
use function Flow\PostgreSql\DSL\column;
24+
use function Flow\PostgreSql\DSL\column_type_serial;
25+
use function Flow\PostgreSql\DSL\column_type_varchar;
26+
use function Flow\PostgreSql\DSL\create;
27+
use function Flow\PostgreSql\DSL\schema_materialized_view;
28+
use function Flow\PostgreSql\DSL\schema_view;
29+
use function Flow\PostgreSql\DSL\select;
30+
use function Flow\PostgreSql\DSL\table;
31+
32+
final class ViewDiffDatabaseTest extends PostgreSqlTestCase
33+
{
34+
private const MATVIEW_SIMPLE = 'flow_postgres_view_diff_matview';
35+
36+
private const TABLE_SOURCE = 'flow_postgres_view_diff_source';
37+
38+
private const VIEW_SIMPLE = 'flow_postgres_view_diff_view';
39+
40+
protected function setUp(): void
41+
{
42+
parent::setUp();
43+
44+
$this
45+
->pgsqlContext()
46+
->client()
47+
->execute(
48+
create()
49+
->table(self::TABLE_SOURCE)
50+
->column(column('id', column_type_serial()))
51+
->column(column('name', column_type_varchar(100)))
52+
->toSql(),
53+
);
54+
}
55+
56+
protected function tearDown(): void
57+
{
58+
$this->pgsqlContext()->dropViewIfExists(self::VIEW_SIMPLE);
59+
$this->pgsqlContext()->dropMaterializedViewIfExists(self::MATVIEW_SIMPLE);
60+
$this->pgsqlContext()->dropTableIfExists(self::TABLE_SOURCE);
61+
62+
parent::tearDown();
63+
}
64+
65+
public function test_catalog_materialized_view_matches_single_line_definition(): void
66+
{
67+
$select = select(col('id'), col('name'))->from(table(self::TABLE_SOURCE));
68+
$this
69+
->pgsqlContext()
70+
->client()
71+
->execute(create()->materializedView(self::MATVIEW_SIMPLE)->as($select)->toSql());
72+
73+
$catalogSchema = (new PgCatalogProvider($this->pgsqlContext()->client(), ['public']))->get()->get('public');
74+
$catalogMatViews = array_values(array_filter(
75+
$catalogSchema->materializedViews,
76+
static fn(MaterializedView $mv): bool => $mv->name === self::MATVIEW_SIMPLE,
77+
));
78+
static::assertCount(1, $catalogMatViews);
79+
80+
$constraintComparator = new ConstraintComparator();
81+
$comparator = new SchemaComparator(
82+
new TableComparator(
83+
new IndexComparator(new GreedySimilarityRenameStrategy(new SimilarTextStrategy())),
84+
$constraintComparator,
85+
new GreedySimilarityRenameStrategy(new SimilarTextStrategy()),
86+
),
87+
new IndexComparator(new GreedySimilarityRenameStrategy(new SimilarTextStrategy())),
88+
$constraintComparator,
89+
new TableStructureComparator(new GreedySimilarityRenameStrategy(new SimilarTextStrategy())),
90+
);
91+
92+
$diff = $comparator->compare(
93+
new Schema('public', materializedViews: [$catalogMatViews[0]]),
94+
new Schema('public', materializedViews: [
95+
schema_materialized_view(self::MATVIEW_SIMPLE, $select->toSql()),
96+
]),
97+
);
98+
99+
static::assertSame([], $diff->modifiedMaterializedViews);
100+
}
101+
102+
public function test_catalog_view_matches_single_line_definition(): void
103+
{
104+
$select = select(col('id'), col('name'))->from(table(self::TABLE_SOURCE));
105+
$this->pgsqlContext()->client()->execute(create()->view(self::VIEW_SIMPLE)->as($select)->toSql());
106+
107+
$catalogSchema = (new PgCatalogProvider($this->pgsqlContext()->client(), ['public']))->get()->get('public');
108+
$catalogViews = array_values(array_filter(
109+
$catalogSchema->views,
110+
static fn(View $v): bool => $v->name === self::VIEW_SIMPLE,
111+
));
112+
static::assertCount(1, $catalogViews);
113+
114+
$constraintComparator = new ConstraintComparator();
115+
$comparator = new SchemaComparator(
116+
new TableComparator(
117+
new IndexComparator(new GreedySimilarityRenameStrategy(new SimilarTextStrategy())),
118+
$constraintComparator,
119+
new GreedySimilarityRenameStrategy(new SimilarTextStrategy()),
120+
),
121+
new IndexComparator(new GreedySimilarityRenameStrategy(new SimilarTextStrategy())),
122+
$constraintComparator,
123+
new TableStructureComparator(new GreedySimilarityRenameStrategy(new SimilarTextStrategy())),
124+
);
125+
126+
$diff = $comparator->compare(
127+
new Schema('public', views: [$catalogViews[0]]),
128+
new Schema('public', views: [
129+
schema_view(self::VIEW_SIMPLE, $select->toSql(), isUpdatable: $catalogViews[0]->isUpdatable),
130+
]),
131+
);
132+
133+
static::assertSame([], $diff->modifiedViews);
134+
}
135+
}

0 commit comments

Comments
 (0)