Skip to content

fix(destination): stop propagating source DSN to destination database documents#180

Merged
abnegate merged 4 commits intomainfrom
fix-destination-dsn-resolver
May 8, 2026
Merged

fix(destination): stop propagating source DSN to destination database documents#180
abnegate merged 4 commits intomainfrom
fix-destination-dsn-resolver

Conversation

@abnegate
Copy link
Copy Markdown
Member

@abnegate abnegate commented May 8, 2026

Summary

  • Reintroduce an opt-in getDatabaseDSN resolver on Destinations\Appwrite.
  • Without a resolver, leave _databases.database blank so the runtime falls back to the destination project's DSN. Never write the source's DSN.
  • Apply in both the Create and Overwrite branches of createDatabase().
  • Add a unit regression locking down the new contract.

Why

PR #151 ("Refactor Appwrite migration to remove unused getDatabaseDSN callable …") removed the destination-DSN resolver on the assumption it was unused, and replaced its call site with $resource->getDatabase() — the source's DSN. Any Appwrite→Appwrite migration where the source and destination projects don't share the same DSN (cross-host within the same cloud instance, cross-region, mixed shared-tables migration) then wrote the source's host string into the destination project's _databases rows.

On Cloud this triggered the comuneo-pre-production incident: destination reads opened a pool to the source host, then derived the namespace from the destination project's sequence (because the source's value isn't in _APP_DATABASE_SHARED_TABLES), producing

SQLSTATE[42S02]: Base table or view not found: 1146
Table 'appwrite._<tenant>__metadata' doesn't exist

for every database the user touched, and silently aborted the per-table schema creates so destination data was unrecoverable from the request path.

Behavior

Caller passes a resolver? _databases.database written as
Yes (consumer can return whatever DSN is correct for the destination project, by type or otherwise) resolver's return value
No (single-host single-type setups) '' — runtime falls back to the destination project's DSN

The fallback is never the source DSN, so consumers that don't update get safer behavior than today.

Follow-up

  • Cloud needs to thread a resolver into new DestinationAppwrite(…) and bump this dependency. (Done in appwrite-labs/cloud#3979, which also adds a worker-level regression test.)
  • Cloud will ship a one-shot patch task to repair already-corrupted _*_databases.database rows from earlier migrations. (Done in appwrite-labs/cloud#3977.)

Test plan

  • composer test (Unit)
  • composer lint (Pint)
  • composer check (PHPStan level 3)
  • New regression: tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php

🤖 Generated with Claude Code

… documents

Restore an opt-in resolver for `_databases.database` on the Appwrite
destination so cross-instance migrations don't write the source's DSN
into the destination project's metadata. Without a resolver, the field
is left blank and the runtime falls back to the destination project's
DSN — correct for legacy single-DSN projects.

Why: PR #151 removed the `getDatabaseDSN` callable on the assumption it
was unused, and replaced it with `\$resource->getDatabase()` (the source
DSN). On Cloud this routed destination reads to the source's host with
the destination's project sequence as the namespace, producing
`Table 'appwrite._<tenant>__metadata' doesn't exist` for every migrated
database. Multi-type setups (documentsdb / vectorsdb) were silently
broken the same way.

Refs PR #151.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR fixes an incident where source-project DSNs were being written into the destination's _databases.database rows during Appwrite→Appwrite migrations, causing destination reads to open connections to the wrong host. It reintroduces an opt-in getDatabaseDSN resolver on Destinations\Appwrite; without one, the field is written as '' and the runtime falls back to the destination project's own DSN at read time.

  • src/Migration/Destinations/Appwrite.php: Adds nullable $getDatabaseDSN constructor parameter (backward-compatible), a resolveDestinationDsn() helper that returns '' when no resolver is set, and applies it consistently in both the Create and Overwrite branches of createDatabase() as well as in databaseSpecMatches() — the last of which also means previously-corrupted rows (non-empty source DSN vs expected '') will not be skipped and will be overwritten on the next migration run.
  • tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php: Adds three reflection-based regression tests covering the no-resolver, resolver-value, and resource-passthrough contracts of resolveDestinationDsn().

Confidence Score: 5/5

Safe to merge — the fix is surgical, backward-compatible, and directly addresses the incident root cause.

The change touches only the three write/compare sites for _databases.database and adds a clean, opt-in escape hatch. The default ('') is strictly safer than today's behavior, existing callers need no changes to get the fix, and the self-healing property of the updated databaseSpecMatches comparison means already-corrupted rows will be repaired on the next migration run. No regressions were found in the changed logic.

No files require special attention. The test file exercises the helper through reflection rather than via createDatabase() (noted in the existing PR thread), but the production code itself is correct and consistent.

Important Files Changed

Filename Overview
src/Migration/Destinations/Appwrite.php Reintroduces opt-in getDatabaseDSN resolver; resolveDestinationDsn() returns '' when unset so source DSN is never propagated to destination _databases.database rows. Both the Create and Overwrite branches of createDatabase() and databaseSpecMatches() are updated consistently.
tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php Three unit tests covering resolveDestinationDsn() via reflection: no-resolver returns '', resolver return value is used, and resolver receives the resource. The call-site wiring inside createDatabase() is not yet exercised by these tests (existing thread comment flagged this and was asked to be addressed).

Reviews (4): Last reviewed commit: "fix(destination): use resolveDestination..." | Re-trigger Greptile

Comment thread tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php
"Cross-instance migrations" was ambiguous: it read like cloud-to-cloud
or dedicated-DB migration. The actual scope is narrower — any case
where the source project's stored DSN doesn't apply to the destination
project (cross-host within the same instance, cross-region, mixed
shared-tables migration). Reword the property, constructor, and
inline write-site comments to say that explicitly.
Copilot AI review requested due to automatic review settings May 8, 2026 05:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Appwrite→Appwrite migrations that accidentally wrote the source database DSN into the destination project’s _databases.database metadata, by reintroducing an opt-in destination-DSN resolver and defaulting to writing a blank value so the runtime can fall back to the destination project’s DSN.

Changes:

  • Add an optional getDatabaseDSN resolver to Destinations\Appwrite and use it when writing _databases.database.
  • Stop writing $resource->getDatabase() (source DSN) into destination database documents in both create and overwrite paths.
  • Add a unit regression test covering resolver/no-resolver behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Migration/Destinations/Appwrite.php Introduces an opt-in destination DSN resolver and uses it when persisting _databases.database.
tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php Adds regression coverage to ensure the source DSN is never propagated and resolver behavior is honored.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php
Comment thread tests/Migration/Unit/Destinations/AppwriteDestinationDsnTest.php
Comment thread src/Migration/Destinations/Appwrite.php
…stead of source DSN

Agent-Logs-Url: https://github.com/utopia-php/migration/sessions/f7af1371-ec8c-49fd-bb9e-932ae59e7d6e

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
@abnegate abnegate merged commit 211d01b into main May 8, 2026
4 checks passed
@abnegate abnegate deleted the fix-destination-dsn-resolver branch May 8, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants