blob: fb867061c7ba3849baad0301f0202fc920b1d3f3 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Session } from '../../src/client/Session';
import { TSDataType } from '../../src/utils/DataTypes';
describe('Tablet Serialization', () => {
let session: Session;
beforeEach(() => {
// Create session instance for accessing protected methods
session = new Session({
host: 'localhost',
port: 6667,
username: 'root',
password: 'root',
});
});
describe('BitMap Serialization', () => {
test('should serialize bitmap with correct LSB-first bit packing', () => {
// Test data: 10 rows, nulls at indices [1, 4, 6, 9]
const bitMaps = [
[false, true, false, false, true, false, true, false, false, true], // Column 0
null, // Column 1: no nulls
];
const buffer = (session as any).serializeBitMaps(bitMaps, 10);
// Expected format:
// Column 0: [0x01] (has null) + [0x52, 0x02] (bitmap)
// Byte 1: bits 0-7 → binary 01010010 → 0x52 (indices 1,4,6 set)
// Byte 2: bits 8-9 → binary 00000010 → 0x02 (index 9 set)
// Column 1: [0x00] (no null)
expect(buffer.length).toBe(4); // 1 (col0 flag) + 2 (col0 bitmap) + 1 (col1 flag)
expect(buffer[0]).toBe(0x01); // Column 0 has null
expect(buffer[1]).toBe(0x52); // First byte: bits 1,4,6 set (01010010)
expect(buffer[2]).toBe(0x02); // Second byte: bit 9 set (00000010)
expect(buffer[3]).toBe(0x00); // Column 1 no null
});
test('should handle edge case: rowCount not multiple of 8', () => {
// Test with 13 rows (needs 2 bytes, last 3 bits unused)
// Nulls at indices [0, 3, 8, 10]
const bitMaps = [
[
true, // 0
false, // 1
false, // 2
true, // 3
false, // 4
false, // 5
false, // 6
false, // 7
true, // 8
false, // 9
true, // 10
false, // 11
false, // 12
],
];
const buffer = (session as any).serializeBitMaps(bitMaps, 13);
// Expected: [0x01] + [0b00001001, 0b00000101]
// Byte 1: bits 0-7 → binary 00001001 → 0x09 (bits 0,3 set)
// Byte 2: bits 8-12 → binary 00000101 → 0x05 (bits 8,10 set)
expect(buffer.length).toBe(3); // 1 (flag) + 2 (bitmap)
expect(buffer[0]).toBe(0x01); // Has null
expect(buffer[1]).toBe(0x09); // 00001001 (bits 0,3)
expect(buffer[2]).toBe(0x05); // 00000101 (bits 8,10)
});
test('should handle all nulls', () => {
const bitMaps = [
[true, true, true, true, true, true, true, true],
];
const buffer = (session as any).serializeBitMaps(bitMaps, 8);
// All 8 bits set = 0xFF
expect(buffer.length).toBe(2); // 1 (flag) + 1 (bitmap)
expect(buffer[0]).toBe(0x01); // Has null
expect(buffer[1]).toBe(0xff); // 11111111
});
test('should handle no nulls', () => {
const bitMaps = [
[false, false, false, false, false, false, false, false],
];
const buffer = (session as any).serializeBitMaps(bitMaps, 8);
// No bits set = 0x00
expect(buffer.length).toBe(2); // 1 (flag) + 1 (bitmap)
expect(buffer[0]).toBe(0x01); // Has null flag (but all false)
expect(buffer[1]).toBe(0x00); // 00000000
});
test('should handle multiple columns with mixed nulls', () => {
const bitMaps = [
[true, false, false, false], // Column 0: null at index 0
null, // Column 1: no nulls
[false, false, true, false], // Column 2: null at index 2
];
const buffer = (session as any).serializeBitMaps(bitMaps, 4);
// Column 0: [0x01] + [0x01] (bit 0 set)
// Column 1: [0x00]
// Column 2: [0x01] + [0x04] (bit 2 set)
expect(buffer.length).toBe(5);
expect(buffer[0]).toBe(0x01); // Col 0 has null
expect(buffer[1]).toBe(0x01); // Col 0 bitmap: bit 0
expect(buffer[2]).toBe(0x00); // Col 1 no null
expect(buffer[3]).toBe(0x01); // Col 2 has null
expect(buffer[4]).toBe(0x04); // Col 2 bitmap: bit 2
});
test('should handle empty bitmap array', () => {
const bitMaps: (boolean[] | null)[] = [];
const buffer = (session as any).serializeBitMaps(bitMaps, 10);
// No columns = empty buffer
expect(buffer.length).toBe(0);
});
});
describe('Column Serialization - Optimized TEXT/STRING/BLOB', () => {
test('should serialize TEXT column efficiently', () => {
const values = ['hello', 'world', 'test'];
const buffer = (session as any).serializeColumn(values, TSDataType.TEXT);
// Calculate expected size
const expectedSize =
4 + Buffer.from('hello', 'utf8').length + // length + data
4 + Buffer.from('world', 'utf8').length +
4 + Buffer.from('test', 'utf8').length;
expect(buffer.length).toBe(expectedSize);
// Verify structure (length + data for each string)
let offset = 0;
expect(buffer.readInt32BE(offset)).toBe(5); // 'hello' length
offset += 4;
expect(buffer.toString('utf8', offset, offset + 5)).toBe('hello');
offset += 5;
expect(buffer.readInt32BE(offset)).toBe(5); // 'world' length
offset += 4;
expect(buffer.toString('utf8', offset, offset + 5)).toBe('world');
offset += 5;
expect(buffer.readInt32BE(offset)).toBe(4); // 'test' length
offset += 4;
expect(buffer.toString('utf8', offset, offset + 4)).toBe('test');
});
test('should serialize STRING column efficiently', () => {
const values = ['abc', '123', 'xyz'];
const buffer = (session as any).serializeColumn(values, TSDataType.STRING);
const expectedSize =
4 + 3 + // 'abc'
4 + 3 + // '123'
4 + 3; // 'xyz'
expect(buffer.length).toBe(expectedSize);
});
test('should handle empty strings in TEXT', () => {
const values = ['', 'data', ''];
const buffer = (session as any).serializeColumn(values, TSDataType.TEXT);
let offset = 0;
expect(buffer.readInt32BE(offset)).toBe(0); // Empty string length
offset += 4;
expect(buffer.readInt32BE(offset)).toBe(4); // 'data' length
offset += 4;
expect(buffer.toString('utf8', offset, offset + 4)).toBe('data');
offset += 4;
expect(buffer.readInt32BE(offset)).toBe(0); // Empty string length
});
test('should handle null values in TEXT', () => {
const values = ['hello', null, 'world'];
const buffer = (session as any).serializeColumn(values, TSDataType.TEXT);
let offset = 0;
expect(buffer.readInt32BE(offset)).toBe(5); // 'hello'
offset += 4 + 5;
expect(buffer.readInt32BE(offset)).toBe(0); // null → empty string
offset += 4;
expect(buffer.readInt32BE(offset)).toBe(5); // 'world'
});
test('should handle UTF-8 multibyte characters in TEXT', () => {
const values = ['你好', '世界', 'test'];
const buffer = (session as any).serializeColumn(values, TSDataType.TEXT);
let offset = 0;
const bytes1 = Buffer.from('你好', 'utf8');
expect(buffer.readInt32BE(offset)).toBe(bytes1.length); // Should be 6 bytes
offset += 4;
expect(buffer.toString('utf8', offset, offset + bytes1.length)).toBe('你好');
offset += bytes1.length;
const bytes2 = Buffer.from('世界', 'utf8');
expect(buffer.readInt32BE(offset)).toBe(bytes2.length);
offset += 4;
expect(buffer.toString('utf8', offset, offset + bytes2.length)).toBe('世界');
});
test('should serialize BLOB column efficiently', () => {
const values = [
Buffer.from([0x01, 0x02, 0x03]),
Buffer.from([0xff, 0xfe]),
Buffer.from([0xaa, 0xbb, 0xcc, 0xdd]),
];
const buffer = (session as any).serializeColumn(values, TSDataType.BLOB);
const expectedSize =
4 + 3 + // First blob
4 + 2 + // Second blob
4 + 4; // Third blob
expect(buffer.length).toBe(expectedSize);
let offset = 0;
expect(buffer.readInt32BE(offset)).toBe(3);
offset += 4;
expect(buffer[offset]).toBe(0x01);
expect(buffer[offset + 1]).toBe(0x02);
expect(buffer[offset + 2]).toBe(0x03);
offset += 3;
expect(buffer.readInt32BE(offset)).toBe(2);
offset += 4;
expect(buffer[offset]).toBe(0xff);
expect(buffer[offset + 1]).toBe(0xfe);
});
test('should handle empty BLOBs', () => {
const values = [Buffer.alloc(0), Buffer.from([0x01]), Buffer.alloc(0)];
const buffer = (session as any).serializeColumn(values, TSDataType.BLOB);
let offset = 0;
expect(buffer.readInt32BE(offset)).toBe(0); // Empty blob
offset += 4;
expect(buffer.readInt32BE(offset)).toBe(1); // One byte
offset += 4;
expect(buffer[offset]).toBe(0x01);
offset += 1;
expect(buffer.readInt32BE(offset)).toBe(0); // Empty blob
});
test('should handle null values in BLOB', () => {
const values = [Buffer.from([0x01]), null, Buffer.from([0x02])];
const buffer = (session as any).serializeColumn(values, TSDataType.BLOB);
let offset = 0;
expect(buffer.readInt32BE(offset)).toBe(1);
offset += 4 + 1;
expect(buffer.readInt32BE(offset)).toBe(0); // null → empty blob
offset += 4;
expect(buffer.readInt32BE(offset)).toBe(1);
});
});
describe('Column Serialization - Other Data Types', () => {
test('should serialize BOOLEAN column', () => {
const values = [true, false, true, false];
const buffer = (session as any).serializeColumn(values, TSDataType.BOOLEAN);
expect(buffer.length).toBe(4);
expect(buffer[0]).toBe(1);
expect(buffer[1]).toBe(0);
expect(buffer[2]).toBe(1);
expect(buffer[3]).toBe(0);
});
test('should serialize INT32 column', () => {
const values = [100, -200, 0, 2147483647];
const buffer = (session as any).serializeColumn(values, TSDataType.INT32);
expect(buffer.length).toBe(16); // 4 values * 4 bytes
expect(buffer.readInt32BE(0)).toBe(100);
expect(buffer.readInt32BE(4)).toBe(-200);
expect(buffer.readInt32BE(8)).toBe(0);
expect(buffer.readInt32BE(12)).toBe(2147483647);
});
test('should serialize INT64 column', () => {
const values = [BigInt(1000), BigInt(-2000), BigInt(0)];
const buffer = (session as any).serializeColumn(values, TSDataType.INT64);
expect(buffer.length).toBe(24); // 3 values * 8 bytes
expect(buffer.readBigInt64BE(0)).toBe(BigInt(1000));
expect(buffer.readBigInt64BE(8)).toBe(BigInt(-2000));
expect(buffer.readBigInt64BE(16)).toBe(BigInt(0));
});
test('should serialize FLOAT column', () => {
const values = [1.5, -2.5, 0.0, 3.14159];
const buffer = (session as any).serializeColumn(values, TSDataType.FLOAT);
expect(buffer.length).toBe(16); // 4 values * 4 bytes
expect(buffer.readFloatBE(0)).toBeCloseTo(1.5, 5);
expect(buffer.readFloatBE(4)).toBeCloseTo(-2.5, 5);
expect(buffer.readFloatBE(8)).toBe(0.0);
expect(buffer.readFloatBE(12)).toBeCloseTo(3.14159, 5);
});
test('should serialize DOUBLE column', () => {
const values = [1.234567890123, -9.876543210987, 0.0];
const buffer = (session as any).serializeColumn(values, TSDataType.DOUBLE);
expect(buffer.length).toBe(24); // 3 values * 8 bytes
expect(buffer.readDoubleBE(0)).toBeCloseTo(1.234567890123, 10);
expect(buffer.readDoubleBE(8)).toBeCloseTo(-9.876543210987, 10);
expect(buffer.readDoubleBE(16)).toBe(0.0);
});
test('should serialize DATE column', () => {
const date1 = new Date('2024-01-01');
const date2 = new Date('2024-12-31');
const values = [date1, date2, 0];
const buffer = (session as any).serializeColumn(values, TSDataType.DATE);
expect(buffer.length).toBe(12); // 3 values * 4 bytes
const days1 = Math.floor(date1.getTime() / (24 * 60 * 60 * 1000));
const days2 = Math.floor(date2.getTime() / (24 * 60 * 60 * 1000));
expect(buffer.readInt32BE(0)).toBe(days1);
expect(buffer.readInt32BE(4)).toBe(days2);
expect(buffer.readInt32BE(8)).toBe(0);
});
test('should serialize TIMESTAMP column', () => {
const ts1 = Date.now();
const ts2 = ts1 + 1000;
const values = [ts1, ts2, 0];
const buffer = (session as any).serializeColumn(values, TSDataType.TIMESTAMP);
expect(buffer.length).toBe(24); // 3 values * 8 bytes
// TIMESTAMP now uses big-endian format for consistency with other types
expect(buffer.readBigInt64BE(0)).toBe(BigInt(ts1));
expect(buffer.readBigInt64BE(8)).toBe(BigInt(ts2));
expect(buffer.readBigInt64BE(16)).toBe(BigInt(0));
});
});
});