fix(magicmapper): prevent _uuid index collisions across dynamic tables#1612
Conversation
…collisions
MagicMapper::createTable emitted `UNIQUE (col)` inline without a name, so
MySQL/MariaDB auto-named the constraint/index after the column. Every
dynamic `oc_openregister_table_*` ended up with an index literally called
`_uuid`, which collides across tables and trips Nextcloud's
ensureUniqueNamesConstraints check on the next app install
(InvalidArgumentException: Index name "_uuid" ... collides with the
constraint on table ...).
- Emit `CONSTRAINT `{tableName}_{col}_uq` UNIQUE (...)` (and the PG
double-quoted variant) so the index name is table-scoped.
- Add repair migration Version1Date20260521120000 that drops the bare
`_uuid` index on every existing dynamic table. The correctly-prefixed
`{table}_uuid_idx` remains, so uniqueness and PG ON CONFLICT keep
working.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ❌ | ✅ 162/162 | |||
| npm | ✅ | ✅ 532/532 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-21 12:38 UTC
Download the full PDF report from the workflow artifacts.
WilcoLouwerse
left a comment
There was a problem hiding this comment.
APPROVE — Thorough review.
Fix is correct and well-scoped: names the inline UNIQUE constraint ({tableName}_{col}_uq) so MySQL/MariaDB no longer auto-name the index after the column, and pairs it with an idempotent repair migration that drops the bare _uuid index on existing broken installs. Verified locally:
- ✅
php -lclean on both files - ✅
phpstanclean on PR files (99 pre-existing errors all in other files) - ❌
composer phpcs— 19 errors on the new migration file (18 auto-fixable viacomposer phpcs:fix+ 1 manual on line 113) - ✅ Smoke-checked SQL — MySQL
ALTER TABLE … DROP INDEX _uuidis correct; PGpg_indexeslookup +DROP INDEX IF EXISTSis standard; column quoting flows correctly per-platform via the existing$colNamebranching at MagicMapper.php:2582-2585
No blockers. Six inline comments — 3× 🟡 (phpcs, PG asymmetry, PG path unverified per your own test plan), 3× 🟢 (hardcoded oc_ prefix, redundant unique indexes).
Nothing in the 🟡 list changes the verdict, but the phpcs failure is the project's own CI signal — worth running composer phpcs:fix before merge to keep the file green.
Address PR #1612 review comment: `findAffectedTables` matched the bare `_uuid` index exactly, while `postSchemaChange` used a wider `LIKE '\_uuid%'` pattern. A PG table that only had `_uuid1` (the on-collision auto-suffix) would never be surfaced for repair, defeating the broader matching downstream. Both queries now agree on the LIKE pattern; the strict `^_uuid(\d+)?$` regex at drop time still gates the actual DROP.
Address PR #1612 review comment about CI phpcs failures. - Use named parameter on internal call: `findAffectedTables(isPostgres: $isPostgres)` - Auto-fix 18 remaining formatting violations via `phpcbf` (blank lines around functions, equals alignment, multi-line call brace placement, `//end ...` markers, equals alignment) - Tidy continuation indent on the multi-line `$output` calls phpcbf reformatted Verified locally: `vendor/bin/phpcs` on the file now reports zero errors.
WilcoLouwerse
left a comment
There was a problem hiding this comment.
Re-review verdict: ✅ APPROVE (Quick mode)
Both actionable prior findings have been addressed by the new commits:
- 🟡 phpcs CI failure (named parameter + 18 auto-fix violations) — resolved in
d5b023f - 🟡 PG asymmetry between
findAffectedTablesandpostSchemaChange— resolved in9213278(both queries now share theLIKE '\_uuid%' ESCAPE '\\'pattern; strict^_uuid(\d+)?$regex at drop time preserves safety)
Resolved as non-blocking (not blockers; tracked as follow-ups or verification asks):
- 🟡 PG path verification — remains a useful pre-merge smoke test
- 🟢 Hardcoded
oc_prefix — cosmetic, consistent with existing precedent - 🟢 Two unique indexes on
_uuidper dynamic table — pre-existing redundancy
No new issues identified on the additional diff. CI Newman API Test Suite is failing due to a pre-existing infrastructure issue (config.platform PHP 8.1 vs composer.json requiring ^8.3) unrelated to this PR — not a required check per the Development ruleset.
Summary
MagicMapper::createTablenow emits a namedCONSTRAINT ... UNIQUE (...)so MySQL/MariaDB no longer auto-name the constraint/index after the column.Version1Date20260521120000to drop the bare_uuidindex on every existingoc_openregister_table_*.Bug
Installing any app (first reported with
doriath) failed with:Root cause:
MagicMapper::createTablebuiltCREATE TABLE ... , UNIQUE (`_uuid`)without naming the constraint. MySQL/MariaDB then used the column name (_uuid) as the index name. Every dynamic table accumulated a bare_uuidindex alongside the correctly-prefixed{table}_uuid_idxfromcreateTableIndexes. Nextcloud'sMigrationService::ensureUniqueNamesConstraintsthrows fatally on duplicate index names across tables, but only at install time (isInstalling=true) — so the broken state silently accumulated and the nextapp:enableblew up.Fix
CONSTRAINT `{tableName}_{col}_uq` UNIQUE (...), with the matching double-quoted variant for PostgreSQL. New dynamic tables get a table-scoped, collision-free constraint name.postSchemaChangequeriesINFORMATION_SCHEMA.STATISTICS(MySQL/MariaDB) orpg_indexes(PostgreSQL) foroc_openregister_table_*tables that still carry a bare_uuidindex and drops it. The correctly-prefixed{table}_uuid_idx(also unique, also on_uuid) stays in place, so column uniqueness and PGON CONFLICTare preserved.Test plan
Dropped bare _uuid index on 9 of 9 dynamic openregister table(s).INFORMATION_SCHEMA.STATISTICSshows only{table}_uuid_idxper dynamic table afterwards.occ app:enable doriathsucceeds (previously failed with the collision exception).pg_indexeslookup + per-indexDROP INDEX IF EXISTSand only matches^_uuid(\d+)?$names, but please confirm in a PG setup before merging).CONSTRAINT openregister_table_X_Y_uuid_uq UNIQUE (`_uuid`)inSHOW CREATE TABLE.