MySQL: give each tenant store its own database as schema (GH-3860) - #3862
Merged
Conversation
A MySQL schema IS a database. PostgreSQL and SQL Server nest a schema inside
the database named by the connection string, so a single configured envelope
storage schema name is harmless there -- isolation comes from the connection
string. MySQL has no such nesting, so giving every tenant store the same
SchemaName discarded the tenant's own database entirely:
MySqlTenantedMessageStore.buildTenantStoreForConnectionString/DataSource
SchemaName = _persistence.EnvelopeStorageSchemaName // same for all
Every tenant therefore shared one physical set of tables -- incoming, outgoing,
dead letters, nodes and the saga tables -- with no isolation at all. Measured
against the existing static_multi_tenancy suite, which passes: all 8 tables
lived in the one configured schema and tenant_db1/2/3 were completely empty.
Nothing errored; a host configured this way looked correct.
A tenant's database is now its schema. The main store is deliberately left
alone: it already has a database of its own, and relocating its tables would
be a far larger change than the isolation bug requires.
MIGRATION: an existing multi-tenanted MySQL deployment has all of its tenant
envelope rows in the shared schema. After upgrading, each tenant reads from
its own database instead, so any in-flight envelopes still sitting in the old
shared tables need to be drained or copied across before the upgrade.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKfy5EzLX1i149gjUb3Tfg
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3860. Completes the MySQL half of the root cause behind #3815 / #3859.
The bug
A MySQL schema is a database. PostgreSQL and SQL Server nest a schema inside the database named by the connection string, so one configured schema name is harmless there — isolation comes from the connection string. MySQL has no such nesting, so the same code discards the tenant's database entirely:
Every tenant shared one physical set of tables —
wolverine_incoming_envelopes,wolverine_outgoing_envelopes, dead letters, nodes, and the saga tables.Measured against the existing suite
Running
MySqlTests.MultiTenancy.static_multi_tenancy, which passes 3/3, and then looking at where the tables actually landed:The tenant databases were completely empty. That suite passes because it only asserts the tenancy source resolves the expected connection strings (
Describe().DatabaseName), never that anything is stored separately — which is exactly the coverage gap this PR closes.The fix
A tenant's database is now its schema, derived from its own connection string. The main store is deliberately left alone — it already has a database of its own, and relocating its tables would be a far larger change than the isolation bug requires.
Verification
each_tenant_database_owns_its_own_envelope_tables(×3 tenants)tenant_store_schema_is_the_tenants_own_databasean_envelope_stored_for_one_tenant_is_invisible_to_the_othersdotnet build wolverine.slnx -c Release -f net9.0Two notes on how that baseline was taken, because both bit me:
mysqlcollection and survive between runs, so the behavioural test measures the delta it causes rather than absolute row counts. An earlier absolute-count version passed alone and failed in the full suite.Migration
An existing multi-tenanted MySQL deployment has all of its tenant envelope rows in the shared schema. After upgrading, each tenant reads from its own database instead, so any in-flight envelopes still in the old shared tables need to be drained or copied across before upgrading. Worth a line in the release notes.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JKfy5EzLX1i149gjUb3Tfg