The GUID type byte layout in dotnet is
- 4 byte int (typically little endian but can be big endian depending on cpu)
- 2 byte short (typically little endian but can be big endian depending on cpu)
- 2 byte short (typically little endian but can be big endian depending on cpu)
- 8 bytes stored as-is
i.e. the raw bytes you provide in a constructor might be rearranged, and reading the raw bytes of a GUID out on the other side might not give you what you put in.
Secondly, even though the bytes might be rearranged internally, externally, the GUID behaves as if the bytes weren't rearranged i.e., Guid.ToString will print the bytes as you provided them, and not as they are laid out in memory.
What this means is that when treating a GUID as a dumb container for 16 bytes, you must rearrange the bytes going in and out yourself, otherwise the meaning of the GUID changes between the time it's written and when it's read. Guid.ToString and libraries like EF Core and Npgsql take care of this for you.
Take an example of this guid -> e6a48153-cb00-8fab-aa68-fc4387bf8f01.
| Current/New behaviour | GUID.ToString() (sent to e.g. Npgsql/EF Core) | Guid in-memory layout | Guid.ToUInt128() | UInt128 in-memory layout (sent to TB) |
|---|---|---|---|---|
| New | e6a48153-cb00-8fab-aa68-fc4387bf8f01 | 53-81-A4-E6-00-CB-AB-8F-AA-68-FC-43-87-BF-8F-01 | 2075611103632272357516034594534696166 | E6-A4-81-53-CB-00-8F-AB-AA-68-FC-43-87-BF-8F-01 |
| Current | e6a48153-cb00-8fab-aa68-fc4387bf8f01 | 53-81-A4-E6-00-CB-AB-8F-AA-68-FC-43-87-BF-8F-01 | 2075611103632272355506525592271225171 | 53-81-A4-E6-00-CB-AB-8F-AA-68-FC-43-87-BF-8F-01 |
Notice how with the current behaviour the last column does not match the second, while it does with the new - which meant that queries via the tigerbeetle repl using uuids/guids that were saved in a separate database were failing with a NotFound error
Comment on lines +121 to +140
| // "00000001-0001-4000-AA00-000000000000" | ||
| var idSourcedExternally = Guid.Parse("00000001-0001-4000-AA00-000000000000"); // e.g. guid from other database | ||
| var bytesStoredInTB = new byte[] | ||
| {0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x40, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
|
|
||
| // Parse TB result (TB client reads by overlaying dotnet types on raw bytes) | ||
| UInt128 parsed = UInt128.Zero; | ||
| bytesStoredInTB.CopyTo(new Span<byte>(&parsed, 16)); | ||
|
|
||
| // Round-trip via GUID | ||
| var roundTripped = parsed.ToGuid().ToUInt128(); | ||
|
|
||
| // Send bytes to TB (TB client send raw bytes of dotnet types over the wire) | ||
| var roundTrippedBytes = new Span<byte>(&roundTripped, 16); | ||
|
|
||
| var externalIdUInt128 = idSourcedExternally.ToUInt128(); | ||
| var externalIdBytes = new Span<byte>(&externalIdUInt128, 16); | ||
|
|
||
| Assert.IsTrue(roundTrippedBytes.SequenceEqual(bytesStoredInTB)); | ||
| Assert.IsTrue(externalIdBytes.SequenceEqual(bytesStoredInTB)); |
Merged