Repository navigation
Fixes #37180: align entity_extension.extension with its table's collation on MySQL - #37184
Conversation
# Conflicts: # openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v208/Migration.java # openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v210/Migration.java
Code Review ✅ Approved 1 closed / 1 findings🔴 High risk · Aligning MySQL entity_extension collation rewrites the table and changes key comparisons, potentially blocking writes or halting upgrades on case collisions. Adds a MySQL data migration step, ✅ 1 closed✅ Quality: No decision record for the entity_extension.extension collation rule
VerificationRules No rules evaluated Functional validation Not enabledAuto-approval Not enabledFor more verification details, comment OptionsDisplay: compact → Showing compact verification results. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
…37162) * fix(sql): bind names in the prefix-rename statements The FQN prefix renames, the tag-usage prefix rewrites and the thread-link rename bind every value; only the table name stays a template attribute. A guard test fails on any DAO statement that is a bare template attribute. Tag lists count usage with the bound per-tag query. ListFilter.escapeForMySqlRegexReplacement stays, deprecated, for extensions. * fix(auth): resolve our own tokens to the account they name A token verified against OpenMetadata's own signing key, on a request or a WebSocket connection, resolves to the user name it was minted for instead of the configured principal claims, whose email local part can belong to another account. SecurityUtil.getUserName uses the principal name whole. * fix(sessions): logout retires the request's token; password changes end sign-ins Logout denylists the verified bearer token of the request, not a token named in the body. Denylist entries are keyed by a digest of the token's signed part, hold only who signed out and when, and are capped. A new password, from a reset link, the user, an admin or the reset-password command, ends the user's refresh tokens, live sessions and MCP grants; personal access tokens and bot tokens stay. * fix(sessions): replace a refresh token in one update A refresh replaces its refresh token with the new one in a single UPDATE keyed by the old token, for basic and LDAP sign-ins. Only the request whose update changed the row gets new tokens; a token deleted while the request ran is not renewed. * fix(sso): a SAML login completes in the browser that started it The login keeps its AuthnRequest id, and the ACS accepts only a response whose signed assertion answers it. The ACS no longer signs anyone in: it attaches the account to the pending login and redirects to /api/v1/saml/complete on the login's own origin with a one-time code. Completing the login needs both the browser's session cookie and that code, read from the session store. * fix(input): bound parsing of names, patches and descriptions The email pattern matches in linear time and accepts the same addresses. The message parser's entity-link pattern cannot backtrack across unclosed links. JSON Patch bodies are read with Jackson limits and an operation cap, and a patch on the document root is refused. Description placeholders are restored in one pass, in text positions only. * fix(users): keep account restore, deletion and role changes with admins A PUT naming the caller's own account refuses a deleted self with 404 and treats an unknown self as a sign-up, allowed only when self sign-up is. Deleting your own account needs an admin. A role list that differs from the stored one, in either direction, is an admin change, and a non-admin PUT keeps the stored admin and bot flags and, when omitted, the stored roles. A non-admin CSV row fails for a stored admin or bot. * fix(policies): reviewers, disabled and owner-team policies, RBAC writes Rules of a disabled policy no longer apply. An owning team's policies give their allow rules to its members only. A reviewer is matched by user id. Startup no longer rewrites seeded policies. Impersonated requests cannot write policies or roles, and BotNonAdminImpersonationPolicy denies bot targets, added to stored copies by the 2.0.8 migration. A soft-deleted team or role keeps the policy conditions that name it; only a hard delete rewrites them. * fix(patch-authz): authorize every location a patch touches A JSON Patch is authorized at every location it touches, the from location of a move or copy included. Narrow edit operations apply only at real fields of the entity; tag changes are compared with their location, and an operation beside a tag change keeps that change's tag operation. The user PATCH guards and the domain check read the same locations. A patch's copies are bounded in size, and a date field refuses an object or array. * fix(users): decide team membership and self-delete on the stored result; refuse impersonated team grants A user's team changes are decided on the patched result, whatever shape the PATCH operations take, and joining or leaving a closed team needs an admin; a non-admin PUT that omits teams keeps the stored ones. The by-name self-delete check resolves the user and compares ids. Team writes that change default roles or policies, and team CSV imports, refuse impersonated requests. * fix(ui): offer Join and Leave team only where the user may change the membership The team page shows Join and Leave only on a joinable team, or to an admin, as the server allows joining and leaving a closed team only for admins. * fix(create-put): authorize creates and PUTs against the stored entity - On create, hasDomain() needs a domain of the stored container and every domain the request names. - A root parent reference without an id is loaded by name and a missing one fails the request; TagMapper resolves the classification by name. - A tag's parent must belong to the tag's classification. - PUT contexts see soft-deleted entities, as the write does. - A system tag's fully qualified name cannot change through its parent or classification either. ADR:2026-11-03-a-create-is-authorized-against-its-stored-container * fix(delete): a hard delete removes only rows of the deleted entity - The FQN-prefix cleanup of field relationships runs only for taggable types and for types, whose custom property definitions live under their name; tag labels are removed by FQN prefix only for taggable types. - Custom property definitions are removed only when their type is deleted. - The tag-FQN label delete without a source is removed; tags and glossary terms keep removing their own source-bound labels. ADR:2026-11-03-a-hard-delete-removes-only-rows-of-the-deleted-entity * fix(reads): read handlers authorize like the entity reads they mirror - Tasks read by id, and their versions, also need ViewBasic on the entity they are about. - Activity by entity link matches the link's entity type as well as its FQN. - Team hierarchy, task counts, domain hierarchy and the type custom-property reads need ViewBasic on their type; the team hierarchy still serves joinable teams to someone signing up. The role list uses the standard listing. - The global profiler setting is always authorized on tables. ADR:2026-11-03-read-handlers-authorize-like-the-entity-reads-they-mirror * test(mcp): give create_entity's root parent a repository in the tool test CreateResourceContext now loads the root parent a create names (a glossary term's glossary) and no longer swallows a failed load, so the test's static Entity stub has to answer for that parent's repository. * test(sessions): fit the session ITs to 2.0's authenticator and tokens 2.0's BasicAuthenticator reads the authorizer configuration at init, so PasswordChangeIT gives it one. 2.0 tokens carry no jti, so the logout test mints its two tokens with different expiries; two tokens minted in the same second would otherwise be the same token. * docs(decisions): name only the patch guards 2.0 has The 2.0 record no longer mentions the connection and sourceConfig copy guard, context-file sharing or conversations, which 2.0 does not have. * test(migrations): mock the collation step in the v208 entry-point test #37186 added EntityExtensionCollationAlignment to the 2.0.8 MySQL data migration without the matching change to WorkflowRedeployEntryPointTest, so the test's mocked Handle reached the step's query. This is the test change main made in #37184.
Fixes #37180
Why
Until #37167, the 2.0.3 MySQL migration switched
entity_extension.extensiontoCHARACTER SET ascii COLLATE ascii_bin. PR #37181 stops that for upgrades that have not run 2.0.3 yet. Databases that already ran it (upgraded to 2.0.3 to 2.0.7 or 2.1.0-rc1) still have anasciicolumn that differs from every other text column in the table: it rejects non-ASCII keys and compares keys case-sensitively. Two states of the same column also mean every future change to it has to handle both.What changes
A new MySQL data migration step,
EntityExtensionCollationAlignment, puts the column back on its table's default character set and collation:information_schema, not a fixed collation. OpenMetadata never set a collation, so tables carry the server default of the day they were created:utf8mb4_0900_ai_cion MySQL 8, but older databases can haveutf8mb4_general_ci. Forcing one collation would copy tables that are already consistent.Fooandfoo) would become duplicates and the copy would fail withERROR 1062, possibly hours in. The step looks for them first and, if any exist, stops with an error that names them and says what to do, leaving the database unchanged. The check reads only the small secondary index, which also holdsidandextension: 1.7 s on a 1M-row, 4 GB table.It runs from the 2.0.8 Java migration, and again from 2.1.0 (with
runOnce, like the other 2.0.8 steps there), so it also reaches databases already on 2.1 pre-release builds: a changed 2.1.0 migration class is pending again for them.It runs from Java rather than
schemaChanges.sqlbecause it has to stop with a clear message, per ADR:2026-10-22-guarded-index-ddl-runs-from-a-java-data-migration. The 2.0.8 SQL file is not edited to point here, because changing the comment above an existing statement changes its recorded text and makes it run again on databases that already ran 2.0.8. Postgres is unchanged.Decision record
ADR:2026-11-06-entity-extension-uses-its-tables-collation states the rule: on MySQL,
entity_extension.extensionuses its table's default collation, and no migration pins it to another one.Tests
EntityExtensionCollationAlignmentTestruns the step on a real MySQL 8.0 container. An unchanged InnoDB table id means MySQL did not copy the table.asciicolumn, table onutf8mb4_0900_ai_ciasciicolumn, table onutf8mb4_general_ciutf8mb4_general_ciFooandfooon the same entityFooandfooon different entitiesReal upgrades with the migration runner, using the 2.1.0-rc1 release image with this step and PR #37181 added, on databases built by running the real migrations:
validateascii_bin)utf8mb4_0900_ai_ci; a second run changes nothingFooandfooon one entityascii_bin, 2.1.0 recorded)utf8mb4_0900_ai_ci, 256)