feat: a table filter pattern can name its schema (#252) · DBDiff/DBDiff@4cc6f97 · GitHub
Skip to content

Commit 4cc6f97

Browse files
feat: a table filter pattern can name its schema (#252)
Since `--schemas` (#247), a diff reads one schema's tables at a time, by bare name. So a schema-qualified filter matched nothing and did nothing, for example: - `--tables='app.*'`, which a user naturally writes once other schemas are compared; - `--ignore-tables=public.orders`. I found this sanity-testing SupaForge 0.0.27. SupaForge's own filter already reads a qualified pattern, so the two tools disagreed. ## What changes - **In PostgreSQL**, a pattern containing a dot is matched against the schema-qualified name: - `app.*` matches the tables in `app` only; - `public.orders` matches `orders` in `public` only. - **A pattern without a dot** matches the bare name in every schema, as before, so existing configs are unchanged. - **The same rules** apply to `--tables`, `--ignore-tables`, `tablesDataToIgnore`, and `tableScope` keys, where a qualified key wins in its own schema. - **MySQL and SQLite** are unchanged: a pattern is matched as written. ## Tested - **Unit:** five new `TableFilter` tests, 1032 in total. - **Through SupaForge on a Supabase stack with this checkout:** - `--tables='app.*'` and `--tables=accounts` both scope to `app.accounts`; - `--tables='public.*'` scopes to `public`'s tables. - **Postgres suite:** 194/194. **Corpus:** 151/151. **SQLite:** passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 4dcf74b + edf38e4 commit 4cc6f97

5 files changed

Lines changed: 73 additions & 11 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion

‎src/DB/Data/DBData.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace DBDiff\DB\Data;
22

3+
use DBDiff\DB\Support\SchemaScope;
34
use DBDiff\Params\ParamsFactory;
45
use DBDiff\Params\TableFilter;
56
use DBDiff\Diff\SetDBCollation;
@@ -24,8 +25,9 @@ function getDiff() {
2425
$sourceTables = $this->manager->getTables('source');
2526
$targetTables = $this->manager->getTables('target');
2627

27-
$sourceTables = TableFilter::filterTables($sourceTables, $params, 'data');
28-
$targetTables = TableFilter::filterTables($targetTables, $params, 'data');
28+
$schema = $this->manager->getDriver() === 'pgsql' ? SchemaScope::of($this->manager->getDB('source')) : null;
29+
$sourceTables = TableFilter::filterTables($sourceTables, $params, 'data', $schema);
30+
$targetTables = TableFilter::filterTables($targetTables, $params, 'data', $schema);
2931

3032
$commonTables = array_intersect($sourceTables, $targetTables);
3133
foreach ($commonTables as $table) {

‎src/DB/Schema/DBSchema.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace DBDiff\DB\Schema;
22

3+
use DBDiff\DB\Support\SchemaScope;
34
use Diff\Differ\ListDiffer;
45
use Diff\DiffOp\DiffOpRemove;
56
use Diff\DiffOp\DiffOpAdd;
@@ -91,8 +92,9 @@ function getDiff() {
9192
$targetTables = $this->manager->getTables('target');
9293

9394
$allTables = array_merge($sourceTables, $targetTables);
94-
$sourceTables = TableFilter::filterTables($sourceTables, $params, 'schema');
95-
$targetTables = TableFilter::filterTables($targetTables, $params, 'schema');
95+
$schema = $driver === 'pgsql' ? SchemaScope::of($this->manager->getDB('source')) : null;
96+
$sourceTables = TableFilter::filterTables($sourceTables, $params, 'schema', $schema);
97+
$targetTables = TableFilter::filterTables($targetTables, $params, 'schema', $schema);
9698

9799
$addedTables = array_values(array_diff($sourceTables, $targetTables));
98100
$deletedTables = array_values(array_diff($targetTables, $sourceTables));

‎src/Params/TableFilter.php‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,34 @@ class TableFilter
2020
* @param string[] $tables Raw table list from the adapter
2121
* @param object $params Params object (tables, tablesToIgnore, tablesDataToIgnore, tableScope)
2222
* @param string $scope 'all' = both schema+data, 'schema' = schema only, 'data' = data only
23+
* @param string|null $schema PostgreSQL: the schema the tables are in, for patterns that name one
2324
* @return string[] Filtered table list (re-indexed)
2425
*/
25-
public static function filterTables(array $tables, object $params, string $scope = 'all'): array
26+
public static function filterTables(array $tables, object $params, string $scope = 'all', ?string $schema = null): array
2627
{
28+
$any = fn(string $table, array $patterns) => array_filter(
29+
$patterns, fn(string $pattern) => self::matchesTable($table, $pattern, $schema)
30+
) !== [];
31+
2732
// 1. Include list — keep only matching tables
2833
$includeList = $params->tables ?? null;
2934
if (!empty($includeList)) {
30-
$tables = self::matchGlobs($tables, $includeList);
35+
$tables = array_filter($tables, fn(string $t) => $any($t, (array) $includeList));
3136
}
3237

3338
// 2. Exclude list — remove matching tables.
3439
// When an include list is also active, the exclude list further
3540
// narrows the included set (useful for "wp_* minus wp_wc_session*").
3641
$excludeList = $params->tablesToIgnore ?? null;
3742
if (!empty($excludeList)) {
38-
$tables = self::rejectGlobs($tables, $excludeList);
43+
$tables = array_filter($tables, fn(string $t) => !$any($t, (array) $excludeList));
3944
}
4045

4146
// 3. Data-only exclusion — when in data scope, additionally exclude
4247
if ($scope === 'data') {
4348
$dataExclude = $params->tablesDataToIgnore ?? null;
4449
if (!empty($dataExclude)) {
45-
$tables = self::rejectGlobs($tables, $dataExclude);
50+
$tables = array_filter($tables, fn(string $t) => !$any($t, (array) $dataExclude));
4651
}
4752
}
4853

@@ -51,8 +56,8 @@ public static function filterTables(array $tables, object $params, string $scope
5156
// to 'schema' only.
5257
$tableScope = $params->tableScope ?? null;
5358
if (!empty($tableScope) && $scope !== 'all') {
54-
$tables = array_filter($tables, function (string $name) use ($tableScope, $scope) {
55-
$s = $tableScope[$name] ?? 'all';
59+
$tables = array_filter($tables, function (string $name) use ($tableScope, $scope, $schema) {
60+
$s = ($schema !== null ? $tableScope["$schema.$name"] ?? null : null) ?? $tableScope[$name] ?? 'all';
5661
// 'all' → included in both schema and data
5762
// 'schema' → included in schema only
5863
// 'data' → included in data only
@@ -141,6 +146,20 @@ public static function filterRows(array $rows, array $rules): array
141146

142147
// ── Glob helpers ──────────────────────────────────────────────────────
143148

149+
/**
150+
* Whether a table pattern names this table. In PostgreSQL, where a diff
151+
* may cover several schemas, a pattern with a dot is matched against the
152+
* schema-qualified name (`app.*`, `public.orders`); one without matches
153+
* the bare name in any schema, as it always has. SupaForge's own filter
154+
* reads a pattern the same way.
155+
*/
156+
public static function matchesTable(string $table, string $pattern, ?string $schema = null): bool
157+
{
158+
return $schema !== null && str_contains($pattern, '.')
159+
? self::globMatch($pattern, "$schema.$table")
160+
: self::globMatch($pattern, $table);
161+
}
162+
144163
/**
145164
* Keep only items from $haystack that match any pattern in $patterns.
146165
*

‎tests/Unit/TableFilterTest.php‎

Lines changed: 39 additions & 0 deletions

0 commit comments

Comments
 (0)