Skip to content

Commit 12704a6

Browse files
committed
feat: enhance UUID handling
Signed-off-by: Vladislav Polyakov <polRk@ydb.tech>
1 parent 00e6356 commit 12704a6

File tree

2 files changed

+149
-46
lines changed

2 files changed

+149
-46
lines changed

src/__tests__/e2e/table-service/types.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,10 +441,12 @@ describe('Types', () => {
441441

442442
it('UUID value', async () => {
443443
await driver.tableClient.withSession(async (session) => {
444-
const query = `SELECT Uuid("f9d5cc3f-f1dc-4d9c-b97e-766e57ca4ccb") AS uuid_value;`;
444+
let uuid = '6E73B41C-4EDE-4D08-9CFB-B7462D9E498B'
445+
446+
const query = `SELECT Uuid("${uuid}") AS uuid_value;`;
445447

446448
const data = {
447-
uuid_value: 'f9d5cc3f-f1dc-4d9c-b97e-766e57ca4ccb',
449+
uuid_value: uuid.toLocaleLowerCase(),
448450
};
449451

450452
const response = await session.executeQuery(query);

src/uuid.ts

Lines changed: 145 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,157 @@
1-
import {Ydb} from 'ydb-sdk-proto';
2-
import * as uuid from 'uuid';
1+
import { Buffer } from 'node:buffer';
2+
3+
import { Ydb } from 'ydb-sdk-proto';
34
import Long from 'long';
45
import IValue = Ydb.IValue;
5-
import {toLong} from "./utils/to-long";
6-
7-
/**
8-
* Every UUID string value represents as hex digits displayed in five groups separated by hyphens:
9-
* - time_low - 8 digits;
10-
* - time_mid - 4 digits;
11-
* - time_hi_and_version - 4 digits;
12-
* - time_high (clock_seq_hi_and_res clock_seq_low (4) + node (12)) - 16 digits.
13-
*
14-
* Example: `00112233-4455-5677-ab89-aabbccddeeff`
15-
* - time_low: `00112233`
16-
* - time_mid: `4455`
17-
* - time_hi_and_version: `5677`
18-
* - time_high: `ab89-aabbccddeeff`
19-
*
20-
* The byte representation of UUID v2 value is first three parts in LE format and last two parts in BE format.
21-
* Example: UUID: `00112233-4455-5677-ab89-aabbccddeeff` byte representation is
22-
* `33 22 11 00 55 44 77 56 ab 99 aa bb cc dd ee ff`.
23-
*/
6+
import { toLong } from "./utils/to-long";
247

258
const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
269

27-
export function uuidToValue(value: string): IValue {
28-
if (!UUID_REGEX.test(value)) {
29-
throw new Error(`Incorrect UUID value: ${value}`);
10+
export function uuidToValue(uuid: string): IValue {
11+
if (!UUID_REGEX.test(uuid)) {
12+
throw new Error(`Incorrect UUID value: ${uuid}`);
3013
}
31-
const uuidBytes = Array.from(uuid.parse(value)) as number[];
32-
const highBytes = uuidBytes.slice(8, 16);
33-
const timeLowBytes = uuidBytes.slice(0, 4);
34-
const timeMidBytes = uuidBytes.slice(4, 6);
35-
const timeHiAndVersionBytes = uuidBytes.slice(6, 8);
36-
const lowBytes = [...timeHiAndVersionBytes, ...timeMidBytes, ...timeLowBytes];
37-
38-
return {
39-
high_128: Long.fromBytesLE(highBytes, true),
40-
low_128: Long.fromBytesBE(lowBytes, true),
41-
};
14+
15+
// Remove dashes from the UUID string
16+
const hex = uuid.replace(/-/g, '');
17+
18+
// Create a buffer from the hexadecimal string
19+
const bytes = Buffer.from(hex, 'hex');
20+
21+
// Swap byte order for the first three fields of the UUID (big-endian to little-endian)
22+
// First 4 bytes (indices 0-3)
23+
bytes[0] ^= bytes[3];
24+
bytes[3] ^= bytes[0];
25+
bytes[0] ^= bytes[3];
26+
27+
bytes[1] ^= bytes[2];
28+
bytes[2] ^= bytes[1];
29+
bytes[1] ^= bytes[2];
30+
31+
// Next 2 bytes (indices 4-5)
32+
bytes[4] ^= bytes[5];
33+
bytes[5] ^= bytes[4];
34+
bytes[4] ^= bytes[5];
35+
36+
// Another 2 bytes (indices 6-7)
37+
bytes[6] ^= bytes[7];
38+
bytes[7] ^= bytes[6];
39+
bytes[6] ^= bytes[7];
40+
41+
// Read low128 and high128 values from the buffer in little-endian format
42+
const low128 = Long.fromBytesLE(bytes.slice(0, 8) as unknown as number[], true)
43+
const high128 = Long.fromBytesLE(bytes.slice(8) as unknown as number[], true)
44+
45+
return { low_128: low128, high_128: high128 };
4246
}
4347

4448
export function uuidToNative(value: IValue): string {
45-
const high = toLong(value.high_128 as number | Long);
46-
const low = toLong(value.low_128 as number | Long);
49+
const low128 = toLong(value.low_128 as number | Long);
50+
const high128 = toLong(value.high_128 as number | Long);
51+
52+
// Create a 16-byte buffer
53+
const bytes = Buffer.alloc(16);
54+
55+
// Write low128 and high128 values to the buffer in little-endian format
56+
bytes.set(low128.toBytesLE(), 0);
57+
bytes.set(high128.toBytesLE(), 8);
58+
59+
// Swap byte order for the first three fields of the UUID (little-endian to big-endian)
60+
// First 4 bytes (indices 0-3)
61+
bytes[0] ^= bytes[3];
62+
bytes[3] ^= bytes[0];
63+
bytes[0] ^= bytes[3];
64+
65+
bytes[1] ^= bytes[2];
66+
bytes[2] ^= bytes[1];
67+
bytes[1] ^= bytes[2];
68+
69+
// Next 2 bytes (indices 4-5)
70+
bytes[4] ^= bytes[5];
71+
bytes[5] ^= bytes[4];
72+
bytes[4] ^= bytes[5];
73+
74+
// Another 2 bytes (indices 6-7)
75+
bytes[6] ^= bytes[7];
76+
bytes[7] ^= bytes[6];
77+
bytes[6] ^= bytes[7];
78+
79+
// Convert the buffer to a hexadecimal string
80+
const hex = bytes.toString('hex');
81+
82+
// Form the UUID string
83+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
84+
}
85+
86+
// @ts-ignore
87+
// For future use, when migrate to BigInt
88+
function uuidFromBigInts(low128: bigint, high128: bigint) {
89+
// Create a 16-byte buffer
90+
const bytes = Buffer.alloc(16);
91+
92+
// Write low128 and high128 values to the buffer in little-endian format
93+
bytes.writeBigUInt64LE(low128, 0);
94+
bytes.writeBigUInt64LE(high128, 8);
95+
96+
// Swap byte order for the first three fields of the UUID (little-endian to big-endian)
97+
// First 4 bytes (indices 0-3)
98+
bytes[0] ^= bytes[3];
99+
bytes[3] ^= bytes[0];
100+
bytes[0] ^= bytes[3];
101+
102+
bytes[1] ^= bytes[2];
103+
bytes[2] ^= bytes[1];
104+
bytes[1] ^= bytes[2];
105+
106+
// Next 2 bytes (indices 4-5)
107+
bytes[4] ^= bytes[5];
108+
bytes[5] ^= bytes[4];
109+
bytes[4] ^= bytes[5];
110+
111+
// Another 2 bytes (indices 6-7)
112+
bytes[6] ^= bytes[7];
113+
bytes[7] ^= bytes[6];
114+
bytes[6] ^= bytes[7];
115+
116+
// Convert the buffer to a hexadecimal string
117+
const hex = bytes.toString('hex');
118+
119+
// Form the UUID string
120+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
121+
}
122+
123+
// @ts-ignore
124+
// For future use, when migrate to BigInt
125+
function bigIntsFromUuid(uuid: string): { low128: bigint, high128: bigint } {
126+
// Remove dashes from the UUID string
127+
const hex = uuid.replace(/-/g, '');
128+
129+
// Create a buffer from the hexadecimal string
130+
const bytes = Buffer.from(hex, 'hex');
131+
132+
// Swap byte order for the first three fields of the UUID (big-endian to little-endian)
133+
// First 4 bytes (indices 0-3)
134+
bytes[0] ^= bytes[3];
135+
bytes[3] ^= bytes[0];
136+
bytes[0] ^= bytes[3];
137+
138+
bytes[1] ^= bytes[2];
139+
bytes[2] ^= bytes[1];
140+
bytes[1] ^= bytes[2];
141+
142+
// Next 2 bytes (indices 4-5)
143+
bytes[4] ^= bytes[5];
144+
bytes[5] ^= bytes[4];
145+
bytes[4] ^= bytes[5];
146+
147+
// Another 2 bytes (indices 6-7)
148+
bytes[6] ^= bytes[7];
149+
bytes[7] ^= bytes[6];
150+
bytes[6] ^= bytes[7];
47151

48-
const highBytes = high.toBytesLE();
49-
const lowBytes = low.toBytesBE();
50-
const timeLowBytes = lowBytes.slice(4, 8);
51-
const timeMidBytes = lowBytes.slice(2, 4);
52-
const timeHighAndVersionBytes = lowBytes.slice(0, 2);
53-
const uuidBytes = [...timeLowBytes, ...timeMidBytes, ...timeHighAndVersionBytes, ...highBytes];
152+
// Read low128 and high128 values from the buffer in little-endian format
153+
const low128 = bytes.readBigUInt64LE(0);
154+
const high128 = bytes.readBigUInt64LE(8);
54155

55-
return uuid.stringify(uuidBytes);
156+
return { low128, high128 };
56157
}

0 commit comments

Comments
 (0)