Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 46f0625

Browse files
committedMar 25, 2024·
Validate that columns referenced by converters are defined
1 parent 53f6fb0 commit 46f0625

File tree

4 files changed

+82
-1
lines changed

4 files changed

+82
-1
lines changed
 

‎src/Database/Metadata/MetadataInterface.php

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Smile\GdprDump\Database\Metadata;
66

7+
use RuntimeException;
78
use Smile\GdprDump\Database\Metadata\Definition\Constraint\ForeignKey;
89

910
interface MetadataInterface
@@ -15,6 +16,13 @@ interface MetadataInterface
1516
*/
1617
public function getTableNames(): array;
1718

19+
/**
20+
* Get the columns of the specified table.
21+
*
22+
* @throws RuntimeException
23+
*/
24+
public function getColumnNames(string $tableName): array;
25+
1826
/**
1927
* Get all foreign keys.
2028
* Each array element is an array that contains the foreign keys of a table.

‎src/Database/Metadata/MysqlMetadata.php

+27
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
namespace Smile\GdprDump\Database\Metadata;
66

77
use Doctrine\DBAL\Connection;
8+
use RuntimeException;
89
use Smile\GdprDump\Database\Metadata\Definition\Constraint\ForeignKey;
910

1011
class MysqlMetadata implements MetadataInterface
1112
{
1213
private string $schema;
1314
private ?array $tableNames = null;
15+
private ?array $columnNames = null;
1416
private ?array $foreignKeys = null;
1517

1618
public function __construct(private Connection $connection)
@@ -38,6 +40,31 @@ public function getTableNames(): array
3840
return $this->tableNames;
3941
}
4042

43+
/**
44+
* @inheritdoc
45+
*/
46+
public function getColumnNames(string $tableName): array
47+
{
48+
if ($this->columnNames === null) {
49+
$query = 'SELECT TABLE_NAME, COLUMN_NAME '
50+
. 'FROM INFORMATION_SCHEMA.COLUMNS '
51+
. 'WHERE TABLE_SCHEMA=?'
52+
. 'ORDER BY COLUMN_NAME ASC';
53+
54+
$statement = $this->connection->prepare($query);
55+
$result = $statement->executeQuery([$this->schema]);
56+
57+
$this->columnNames = [];
58+
59+
while ($row = $result->fetchAssociative()) {
60+
$this->columnNames[$row['TABLE_NAME']][] = $row['COLUMN_NAME'];
61+
}
62+
}
63+
64+
return $this->columnNames[$tableName]
65+
?? throw new RuntimeException(sprintf('The table "%s" is not defined.', $tableName));
66+
}
67+
4168
/**
4269
* @inheritdoc
4370
*/

‎src/Dumper/Config/ConfigProcessor.php

+28-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Smile\GdprDump\Config\ConfigInterface;
88
use Smile\GdprDump\Database\Metadata\MetadataInterface;
9+
use Smile\GdprDump\Dumper\Config\Validation\ValidationException;
910

1011
class ConfigProcessor
1112
{
@@ -47,7 +48,8 @@ private function processTableLists(ConfigInterface $config): void
4748
}
4849

4950
/**
50-
* Remove tables that don't exist from the "tables" parameter.
51+
* Remove tables that don't exist from the "tables" parameter,
52+
* and raise an exception if the remaining tables have converters that reference invalid columns.
5153
*/
5254
private function processTablesData(ConfigInterface $config): void
5355
{
@@ -86,6 +88,10 @@ private function resolveTablesData(array $tablesData): array
8688
$matches = $this->findTablesByName((string) $tableName);
8789

8890
foreach ($matches as $match) {
91+
// Throw an exception if a converter refers to a column that does not exist
92+
$this->validateTableColumns($tableName, $tableData);
93+
94+
// Merge table configuration
8995
if (!array_key_exists($match, $resolved)) {
9096
$resolved[$match] = [];
9197
}
@@ -118,4 +124,25 @@ private function findTablesByName(string $pattern): array
118124

119125
return $matches;
120126
}
127+
128+
/**
129+
* Raise an exception if the table data contains a converter that references an undefined column.
130+
*/
131+
private function validateTableColumns(string $tableName, array $tableData): void
132+
{
133+
if (!array_key_exists('converters', $tableData) || empty($tableData['converters'])) {
134+
return;
135+
}
136+
137+
$columns = $this->metadata->getColumnNames($tableName);
138+
139+
foreach ($tableData['converters'] as $columnName => $converterData) {
140+
$disabled = $converterData['disabled'] ?? false;
141+
142+
if (!$disabled && !in_array($columnName, $columns)) {
143+
$message = 'The table "%s" uses a converter on an undefined column "%s".';
144+
throw new ValidationException(sprintf($message, $tableName, $columnName));
145+
}
146+
}
147+
}
121148
}

‎tests/functional/Database/Metadata/MysqlMetadataTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Smile\GdprDump\Tests\Functional\Database\Metadata;
66

7+
use RuntimeException;
78
use Smile\GdprDump\Database\Metadata\MetadataInterface;
89
use Smile\GdprDump\Database\Metadata\MysqlMetadata;
910
use Smile\GdprDump\Tests\Functional\TestCase;
@@ -19,6 +20,24 @@ public function testTableNames(): void
1920
$this->assertEqualsCanonicalizing(['stores', 'customers', 'addresses', 'config'], $metadata->getTableNames());
2021
}
2122

23+
/**
24+
* Test the "getColumnNames" method.
25+
*/
26+
public function testColumnNames(): void
27+
{
28+
$columns = $this->getMetadata()->getColumnNames('stores');
29+
$this->assertEqualsCanonicalizing(['code', 'store_id', 'parent_store_id'], $columns);
30+
}
31+
32+
/**
33+
* Assert that an exception is thrown when fetching the columns of an undefined table.
34+
*/
35+
public function testErrorOnInvalidTableName(): void
36+
{
37+
$this->expectException(RuntimeException::class);
38+
$this->getMetadata()->getColumnNames('undefined');
39+
}
40+
2241
/**
2342
* Test the "getForeignKeys" method.
2443
*/

0 commit comments

Comments
 (0)
Please sign in to comment.