feat(plugin-mongodb): decode legacy binary UUIDs with a per-connection byte order - #2089
Merged
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
Closes #2086.
MongoDB stores UUIDs as BSON binary. TablePro showed them as hex.
Root cause
MongoDBConnection.unwrapExtendedJsonread$binary.base64and never$binary.subType, so the byte that says which byte order applies was thrown away before any consumer could see it. Two things followed from that:0x2443254aeb03d08c...UUID("...")for any 16-byte value, regardless of subtype, so a legacy UUID was shown with the wrong byte order and an MD5 hash was shown as a UUIDThe fix carries the subtype through the pipeline as
MongoDBBinaryValueand decides rendering from it.Behaviour
Subtype 4 is unambiguous and always decodes to
UUID("...").Subtype 3 is the older format that the Java, C# and Python drivers each wrote with a different byte order, and nothing in the stored bytes says which one produced them. It stays
BinData(3, "...")until you pick a byte order in Legacy UUID Encoding on the connection, then reads asLegacyJavaUUID("..."),LegacyCSharpUUID("...")orLegacyPythonUUID("...").Refusing to guess is what the BSON Binary UUID spec requires of drivers under
unspecified, and it is what Compass 1.49 and DataGrip both default to. The names match mongosh 2.6 and Compass 1.49, so a copied value pastes into either. The Studio 3T spellings (JUUID,CSUUID,NUUID,PYUUID,LUUID) are accepted as input.The setting is per connection, not global, because byte order is a property of the application that wrote the collection. It is also a real connection-string option, so pasting a URL with
uuidRepresentation=javaLegacysets it and Copy URL emits it. It is never forwarded to libmongoc, which does not support the option and would only log a warning.Design
Decoding is decided once per column, never per value. A decoded cell is
.textand an undecoded one is.bytes, andCellDisplayFormatterruns blob formatting over a.textcell whenever its column type is BLOB, so a single UUID decoded inside a column the app still typesBLOBwould render as0x4c65676163....BsonDocumentFlattener.columnKindsdecides for the whole column, andtypeNamereturns exactly"BLOB"for undecoded binary becauseColumnTypeClassifierkeys on that name.Once a column decodes, both edit guards (
isBlobTypeandasBytes != nil) fall together, so every write path parses the wrapper back to$binary: the statement generator'sjsonValueandidValueJson, the query builder'sjsonValueand its=,!=andINarms, andMQLExportHelpers.mqlJsonValue. An_idfilter left as wrapper text would match zero documents while the UI reported success.Two duplicated code paths that would have made exports disagree with the grid are now one:
streamCellValueandbsonTypeToStreamStringin the streaming path were copies of the flattener andbsonTypeToString.The
Int32BSON type codes are replaced by aBsonValueKindenum. Sentinel integers for the UUID cases would have needed a warning about Swift binding a bare identifier in acasepattern instead of comparing against it.Also fixed
_idor it does not run.generateDeleteused to fall back to a filter built from the other columns, which dropped every value it could not stringify (all binary) and thendeleteOned the first partial match, so a document with a binary_idcould delete a different document.generateUpdatealready skipped with a warning; both do now.Tests
209 tests in 17 suites pass. Byte orders are asserted against the BSON Binary UUID spec's own test-plan vectors, mongosh's e2e vectors, and the UUID from the issue. Coverage includes the 16-byte guard, non-UUID subtypes, round-tripping every representation through its own wrapper, the column-gating rule in both directions, and the four write paths.
TableProTestsdid not actually cover any of this before.BsonDocumentFlattenerTeststested a hand-copied duplicate of the flattener whoseflattenalready returned a different type from production, andMongoDBExtendedJsonTestsis wrapped in#if canImport(CLibMongoc), which is never true in the test target. The duplicate is deleted and the real file is compiled into the test target; the byte-order logic lives inTableProPluginKitso it is testable with no gate.ABI and release
The PluginKit diff is purely additive, so no
currentPluginKitVersionbump and no bulk re-release. MongoDB is registry-only, so the driver half reaches users through aplugin-mongodb-v1.0.38tag; the URL round-trip and the connection-form mirror ship with the app.Deliberately not in this PR
"subType": "00"for genuinely non-UUID binary. Carrying a subtype for arbitrary blobs needs a case onPluginCellValue, which is@frozen, so it is an ABI break and its own change.ObjectId(...)andISODate(...)included, so it is not specific to UUIDs. Teaching it onlyUUID(...)would be a special case; general support is a separate feature.$binary. The legacy tag is per-connection state the sheet does not have, and a guessed tag would contradict the grid.https://claude.ai/code/session_01628orR3jwc3ATfcFCLVUMB