From bf6efdeb753d03a728f8d96508005d1c2b8a739b Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 1 Sep 2026 13:23:26 +0200 Subject: [PATCH 1/3] fix(migration): re-run pg_trgm CREATE EXTENSION on all instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version1Date20260706110000's CREATE EXTENSION line was added on 2026-07-21 (commit 15d93450a3c), after the migration had already shipped and been recorded as run on existing installations. Nextcloud runs each app+version row exactly once, so instances that migrated through the intermediate window never install pg_trgm — and every _fuzzy=true request silently degrades to unindexed ILIKE with no @self.relevance field. Confirmed on the dev-env instance: oc_migrations recorded 1Date20260706110000 as run, pg_extension had only plpgsql. Add a new migration file — a fresh app+version row — that idempotently re-runs CREATE EXTENSION IF NOT EXISTS pg_trgm with the same tolerant- failure contract as the original. Instances that already have pg_trgm installed take the "already installed" branch and log an info message. Unblocks the WOO-536 fulltext-search fuzzy path. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/Migration/Version1Date20260901000000.php | 123 +++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 lib/Migration/Version1Date20260901000000.php diff --git a/lib/Migration/Version1Date20260901000000.php b/lib/Migration/Version1Date20260901000000.php new file mode 100644 index 0000000000..5ee7b5c1f0 --- /dev/null +++ b/lib/Migration/Version1Date20260901000000.php @@ -0,0 +1,123 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @link https://OpenRegister.app + */ + +declare(strict_types=1); + +namespace OCA\OpenRegister\Migration; + +use Closure; +use Exception; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Re-runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` on PostgreSQL. + * + * @package OCA\OpenRegister\Migration + */ +class Version1Date20260901000000 extends SimpleMigrationStep +{ + /** + * Constructor. + * + * @param IDBConnection $connection Database connection + */ + public function __construct( + private readonly IDBConnection $connection + ) { + }//end __construct() + + /** + * Ensure the pg_trgm extension exists after schema changes (PostgreSQL only). + * + * @param IOutput $output Migration output + * @param Closure $schemaClosure Schema closure + * @param array $options Migration options + * + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void + { + $platform = $this->connection->getDatabasePlatform(); + + if (str_contains(get_class($platform), 'PostgreSQL') === false) { + $output->info('Skipping pg_trgm extension re-bootstrap: unsupported database platform (PostgreSQL only)'); + return; + } + + $this->ensurePgTrgmExtension(output: $output); + }//end postSchemaChange() + + /** + * Create the pg_trgm extension when missing, tolerating privilege failures. + * + * Same contract as Version1Date20260706110000::ensurePgTrgmExtension — + * `CREATE EXTENSION IF NOT EXISTS` is idempotent, and a privilege + * failure is logged rather than fatal so the migration is safe on + * instances where the extension is already present or where the + * connecting role lacks the CREATE EXTENSION privilege. + * + * @param IOutput $output Migration output + * + * @return void + */ + private function ensurePgTrgmExtension(IOutput $output): void + { + try { + $this->connection->executeStatement('CREATE EXTENSION IF NOT EXISTS pg_trgm'); + $output->info('pg_trgm extension is available (fuzzy/substring search indexes can be created)'); + return; + } catch (Exception $e) { + try { + $result = $this->connection->executeQuery( + "SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm'" + ); + if ($result->fetchOne() !== false) { + $output->info('pg_trgm extension already installed'); + return; + } + } catch (Exception $inner) { + // Fall through to the warning below. + } + + $reason = 'pg_trgm extension is not installed and could not be created ('.$e->getMessage().').'; + $impact = 'Fuzzy/substring search (_fuzzy=true, @self.relevance) stays on the unindexed ILIKE path.'; + $action = 'Run CREATE EXTENSION pg_trgm; as a superuser and re-run this migration to enable it.'; + $output->warning($reason.' '.$impact.' '.$action); + }//end try + }//end ensurePgTrgmExtension() +}//end class From c8b25d59894860eddfcb048f0d9a391b6ae090c8 Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 1 Sep 2026 13:57:32 +0200 Subject: [PATCH 2/3] fix(migration): distinguish install-vs-noop log + regenerate features (WOO-536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review #5077597717 findings: F1 (concern) — CI `quality / Features Check` was red on `docs/features.json` baseline drift (annotation: "docs/features.json is out of date — run scripts/extract-features.py to regenerate"). Regenerated via the canonical ConductionNL/.github/scripts/extract-features.py — added the "RBAC-as-Public Toggle" entry the openspec change was missing. F2 (nit) — added the `@spec openspec/changes/searchable-property-index/tasks.md#1.1` tag on both the class docblock and the postSchemaChange method docblock, matching the sibling Version1Date20260706110000. Keeps the feature-to- bootstrap traceability chain intact. F3 (nit) — happy-path log now distinguishes the no-op re-run from the actual install-by-re-run. Probes `pg_extension` before the CREATE EXTENSION call; on success emits either "already installed (no-op re-run)" (the vast majority of instances) or "installed by re-run" (the instances this PR exists to fix). Probe failure falls through — the CREATE EXTENSION statement is idempotent regardless. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/features.json | 7 ++++++ lib/Migration/Version1Date20260901000000.php | 24 +++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/features.json b/docs/features.json index 1b8f3cc215..caa82e6d44 100644 --- a/docs/features.json +++ b/docs/features.json @@ -118,6 +118,13 @@ "status": "beta", "docsUrl": "openspec/specs/object-interactions/spec.md" }, + { + "slug": "rbac-as-public-toggle", + "title": "RBAC-as-Public Toggle", + "summary": "Define the _rbac_as_public query-flag primitive in OpenRegister's MagicRbacHandler and MagicSearchHandler.", + "status": "stable", + "docsUrl": "openspec/specs/rbac-as-public-toggle/spec.md" + }, { "slug": "rbac-scopes", "title": "RBAC Scopes", diff --git a/lib/Migration/Version1Date20260901000000.php b/lib/Migration/Version1Date20260901000000.php index 5ee7b5c1f0..8f6d553cf2 100644 --- a/lib/Migration/Version1Date20260901000000.php +++ b/lib/Migration/Version1Date20260901000000.php @@ -46,6 +46,8 @@ * Re-runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` on PostgreSQL. * * @package OCA\OpenRegister\Migration + * + * @spec openspec/changes/searchable-property-index/tasks.md#1.1 */ class Version1Date20260901000000 extends SimpleMigrationStep { @@ -68,6 +70,8 @@ public function __construct( * * @return void * + * @spec openspec/changes/searchable-property-index/tasks.md#1.1 + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void @@ -98,8 +102,26 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array private function ensurePgTrgmExtension(IOutput $output): void { try { + // Probe first so the log distinguishes the no-op re-run (extension was + // already installed by the 2026-07 bootstrap or an operator) from the + // actual install that this re-run migration ships to perform. + $wasAlreadyInstalled = false; + try { + $result = $this->connection->executeQuery( + "SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm'" + ); + $wasAlreadyInstalled = ($result->fetchOne() !== false); + } catch (Exception $probeException) { + // Probe failed (e.g. permissions on pg_extension); fall through to + // the CREATE EXTENSION attempt, which is idempotent regardless. + } + $this->connection->executeStatement('CREATE EXTENSION IF NOT EXISTS pg_trgm'); - $output->info('pg_trgm extension is available (fuzzy/substring search indexes can be created)'); + if ($wasAlreadyInstalled === true) { + $output->info('pg_trgm extension already installed (no-op re-run)'); + } else { + $output->info('pg_trgm extension installed by re-run (fuzzy/substring search indexes can now be created)'); + } return; } catch (Exception $e) { try { From 4618209ead0c23e5e655f17008fa5e429bcd515e Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 1 Sep 2026 14:09:20 +0200 Subject: [PATCH 3/3] fix(quality): phpcs errors in ensurePgTrgmExtension probe branch (WOO-536) CI PR check `quality / PHP Quality (phpcs)` failed on two style errors in the pg_extension probe/log-differentiation refactor: - `$result = ...` inside the nested try had equals-sign alignment drift (14 spaces before `=` vs. 1 required). - No blank line after the closing brace of the `if ($wasAlreadyInstalled === true)` block before the `return` statement. Both auto-fixed via phpcbf. Same runtime semantics; phpcs clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/Migration/Version1Date20260901000000.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Migration/Version1Date20260901000000.php b/lib/Migration/Version1Date20260901000000.php index 8f6d553cf2..9acac47e74 100644 --- a/lib/Migration/Version1Date20260901000000.php +++ b/lib/Migration/Version1Date20260901000000.php @@ -107,7 +107,7 @@ private function ensurePgTrgmExtension(IOutput $output): void // actual install that this re-run migration ships to perform. $wasAlreadyInstalled = false; try { - $result = $this->connection->executeQuery( + $result = $this->connection->executeQuery( "SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm'" ); $wasAlreadyInstalled = ($result->fetchOne() !== false); @@ -122,6 +122,7 @@ private function ensurePgTrgmExtension(IOutput $output): void } else { $output->info('pg_trgm extension installed by re-run (fuzzy/substring search indexes can now be created)'); } + return; } catch (Exception $e) { try {