blob: 184badba2688e4f2122c9c112bd75e0c41d7b255 [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("All Data Types E2E Tests", () => {
const IOTDB_HOST = process.env.IOTDB_HOST || "localhost";
const IOTDB_PORT = parseInt(process.env.IOTDB_PORT || "6667");
const IOTDB_USER = process.env.IOTDB_USER || "root";
const IOTDB_PASSWORD = process.env.IOTDB_PASSWORD || "root";
let session: Session;
beforeAll(async () => {
session = new Session({
host: IOTDB_HOST,
port: IOTDB_PORT,
username: IOTDB_USER,
password: IOTDB_PASSWORD,
});
try {
await session.open();
console.log("Connected to IoTDB for all data types test");
} catch (error) {
console.warn("Could not connect to IoTDB. E2E tests will be skipped.");
console.warn(
"Set IOTDB_HOST, IOTDB_PORT to run E2E tests against a real instance.",
);
try {
await session.close();
} catch {
// Ignore cleanup errors
}
}
}, 60000);
afterAll(async () => {
if (session && session.isOpen()) {
await session.close();
}
}, 60000);
test("Should create database and timeseries with all data types", async () => {
if (!session.isOpen()) {
console.log("Skipping test - no IoTDB connection");
return;
}
// Create database
try {
await session.executeNonQueryStatement("CREATE DATABASE root.test");
} catch (error: any) {
// IoTDB returns "already exist" or "has already been created"
if (
!error.message?.includes("already exist") &&
!error.message?.includes("has already been created")
) {
throw error;
}
}
// Create timeseries for each data type
const dataTypes = [
{ name: "boolean_sensor", type: "BOOLEAN", encoding: "PLAIN" },
{ name: "int32_sensor", type: "INT32", encoding: "RLE" },
{ name: "int64_sensor", type: "INT64", encoding: "RLE" },
{ name: "float_sensor", type: "FLOAT", encoding: "RLE" },
{ name: "double_sensor", type: "DOUBLE", encoding: "RLE" },
{ name: "text_sensor", type: "TEXT", encoding: "PLAIN" },
{ name: "timestamp_sensor", type: "TIMESTAMP", encoding: "PLAIN" },
{ name: "date_sensor", type: "DATE", encoding: "PLAIN" },
{ name: "blob_sensor", type: "BLOB", encoding: "PLAIN" },
{ name: "string_sensor", type: "STRING", encoding: "PLAIN" },
];
for (const dt of dataTypes) {
try {
await session.executeNonQueryStatement(
`CREATE TIMESERIES root.test.device1.${dt.name} WITH DATATYPE=${dt.type}, ENCODING=${dt.encoding}`,
);
} catch (error: any) {
// IoTDB returns "already exist" (without 's')
if (!error.message?.includes("already exist")) {
console.warn(
`Failed to create timeseries ${dt.name}: ${error.message}`,
);
}
}
}
console.log("Created timeseries for all supported data types");
}, 60000);
test("Should insert and retrieve data for all data types", async () => {
if (!session.isOpen()) {
console.log("Skipping test - no IoTDB connection");
return;
}
// Ensure database exists
try {
await session.executeNonQueryStatement("CREATE DATABASE root.test");
} catch (e: any) {
if (!e.message?.includes("already")) {
throw e;
}
}
// Ensure all timeseries exist (re-create if they were deleted)
const dataTypes = [
{ name: "boolean_sensor", type: "BOOLEAN", encoding: "PLAIN" },
{ name: "int32_sensor", type: "INT32", encoding: "RLE" },
{ name: "int64_sensor", type: "INT64", encoding: "RLE" },
{ name: "float_sensor", type: "FLOAT", encoding: "RLE" },
{ name: "double_sensor", type: "DOUBLE", encoding: "RLE" },
{ name: "text_sensor", type: "TEXT", encoding: "PLAIN" },
{ name: "timestamp_sensor", type: "TIMESTAMP", encoding: "PLAIN" },
{ name: "date_sensor", type: "DATE", encoding: "PLAIN" },
{ name: "blob_sensor", type: "BLOB", encoding: "PLAIN" },
{ name: "string_sensor", type: "STRING", encoding: "PLAIN" },
];
for (const dt of dataTypes) {
try {
await session.executeNonQueryStatement(
`CREATE TIMESERIES root.test.device1.${dt.name} WITH DATATYPE=${dt.type}, ENCODING=${dt.encoding}`,
);
} catch (error: any) {
// IoTDB returns "already exist" (without 's')
if (!error.message?.includes("already exist")) {
throw error;
}
}
}
const now = Date.now();
// Insert tablet data with all types
const tablet = {
deviceId: "root.test.all_types_device",
measurements: [
"boolean_sensor",
"int32_sensor",
"int64_sensor",
"float_sensor",
"double_sensor",
"text_sensor",
"timestamp_sensor",
"date_sensor",
"blob_sensor",
"string_sensor",
],
// Use TSDataType enum for clarity
dataTypes: [
TSDataType.BOOLEAN,
TSDataType.INT32,
TSDataType.INT64,
TSDataType.FLOAT,
TSDataType.DOUBLE,
TSDataType.TEXT,
TSDataType.TIMESTAMP,
TSDataType.DATE,
TSDataType.BLOB,
TSDataType.STRING,
],
timestamps: [now, now + 1, now + 2],
values: [
[
true,
100,
1000n,
1.23,
4.56,
"hello",
new Date(now),
new Date(now),
Buffer.from([0x01, 0x02, 0x03]),
"str1",
],
[
false,
200,
2000n,
2.34,
5.67,
"world",
new Date(now + 1),
new Date(now + 1),
Buffer.from([0x04, 0x05, 0x06]),
"str2",
],
[
true,
300,
3000n,
3.45,
6.78,
"test",
new Date(now + 2),
new Date(now + 2),
Buffer.from([0x07, 0x08, 0x09]),
"str3",
],
],
};
await session.insertTablet(tablet);
console.log("Inserted data with all data types");
// Query the data back - get latest 3 rows
const dataSet = await session.executeQueryStatement(
`SELECT * FROM root.test.all_types_device ORDER BY time DESC LIMIT 3`,
);
expect(dataSet).toBeDefined();
// Collect all rows
const rows = [];
while (await dataSet.hasNext()) {
rows.push(dataSet.next());
}
await dataSet.close();
expect(rows.length).toBeGreaterThanOrEqual(3);
console.log(`Retrieved ${rows.length} rows`);
// Now use column names to access values (user's request: "通过列名读取对应的值和类型来断言")
const firstRow = rows[0];
expect(firstRow.getFields().length).toBe(10); // 10 data columns
// Check timestamp
expect(typeof firstRow.getTimestamp()).toBe("number");
// Test getValue() with column names - verify column name mapping works correctly
const boolValue = firstRow.getValue(
"root.test.all_types_device.boolean_sensor",
);
expect(typeof boolValue).toBe("boolean");
expect(boolValue).toBe(true); // Row 2 has true
const int32Value = firstRow.getValue(
"root.test.all_types_device.int32_sensor",
);
expect(typeof int32Value).toBe("number");
expect(Number.isInteger(int32Value)).toBe(true);
expect(int32Value).toBe(300); // Row 2 has 300
const int64Value = firstRow.getValue(
"root.test.all_types_device.int64_sensor",
);
expect(typeof int64Value).toBe("bigint");
expect(int64Value).toBe(BigInt(3000)); // Row 2 has 3000n
const floatValue = firstRow.getValue(
"root.test.all_types_device.float_sensor",
);
expect(typeof floatValue).toBe("number");
expect(floatValue).toBeCloseTo(3.45, 2); // Row 2 has 3.45
const doubleValue = firstRow.getValue(
"root.test.all_types_device.double_sensor",
);
expect(typeof doubleValue).toBe("number");
expect(doubleValue).toBeCloseTo(6.78, 2); // Row 2 has 6.78
const textValue = firstRow.getValue(
"root.test.all_types_device.text_sensor",
);
expect(typeof textValue).toBe("string");
expect(textValue).toBe("test"); // Row 2 has "test"
const stringValue = firstRow.getValue(
"root.test.all_types_device.string_sensor",
);
expect(typeof stringValue).toBe("string");
expect(stringValue).toBe("str3"); // Row 2 has "str3"
// Note: BLOB, TIMESTAMP, DATE sensors also exist but have different value patterns
}, 60000);
test("Should handle null values for all data types", async () => {
if (!session.isOpen()) {
console.log("Skipping test - no IoTDB connection");
return;
}
const now = Date.now() + 10000; // Different timestamp to avoid conflicts
// Insert some null values
const tablet = {
deviceId: "root.test.device1",
measurements: [
"boolean_sensor",
"int32_sensor",
"int64_sensor",
"float_sensor",
"double_sensor",
"text_sensor",
"timestamp_sensor",
"date_sensor",
"blob_sensor",
"string_sensor",
],
dataTypes: [
TSDataType.BOOLEAN,
TSDataType.INT32,
TSDataType.INT64,
TSDataType.FLOAT,
TSDataType.DOUBLE,
TSDataType.TEXT,
TSDataType.TIMESTAMP,
TSDataType.DATE,
TSDataType.BLOB,
TSDataType.STRING,
],
timestamps: [now],
values: [
[
false,
999,
9999n,
9.99,
99.99,
"null_test",
new Date(now),
new Date(now),
Buffer.from([0xff]),
"str_null",
],
],
};
await session.insertTablet(tablet);
// Query to verify
const dataSet = await session.executeQueryStatement(
`SELECT * FROM root.test.device1 WHERE time = ${now}`,
);
const rows = [];
while (await dataSet.hasNext()) {
rows.push(dataSet.next());
}
await dataSet.close();
expect(rows.length).toBeGreaterThan(0);
console.log("Null handling test row:", rows[0]);
}, 60000);
test("Should handle multiple rows with mixed data types", async () => {
if (!session.isOpen()) {
console.log("Skipping test - no IoTDB connection");
return;
}
const baseTime = Date.now() + 20000;
const rowCount = 10;
const timestamps: number[] = [];
const values: any[][] = [];
for (let i = 0; i < rowCount; i++) {
timestamps.push(baseTime + i * 1000);
values.push([
i % 2 === 0, // boolean: alternating true/false
i * 10, // int32: 0, 10, 20, ...
BigInt(i * 100), // int64: 0, 100, 200, ...
i * 1.5, // float
i * 2.5, // double
`value_${i}`, // text
new Date(baseTime + i * 1000), // timestamp
new Date(baseTime + i * 1000), // date
Buffer.from([i % 256]), // blob
`string_${i}`, // string
]);
}
const tablet = {
deviceId: "root.test.device1",
measurements: [
"boolean_sensor",
"int32_sensor",
"int64_sensor",
"float_sensor",
"double_sensor",
"text_sensor",
"timestamp_sensor",
"date_sensor",
"blob_sensor",
"string_sensor",
],
dataTypes: [
TSDataType.BOOLEAN,
TSDataType.INT32,
TSDataType.INT64,
TSDataType.FLOAT,
TSDataType.DOUBLE,
TSDataType.TEXT,
TSDataType.TIMESTAMP,
TSDataType.DATE,
TSDataType.BLOB,
TSDataType.STRING,
],
timestamps,
values,
};
await session.insertTablet(tablet);
console.log(`Inserted ${rowCount} rows with mixed data types`);
// Query and verify
const dataSet = await session.executeQueryStatement(
`SELECT * FROM root.test.device1 WHERE time >= ${baseTime} AND time < ${baseTime + rowCount * 1000}`,
);
const rows = [];
while (await dataSet.hasNext()) {
rows.push(dataSet.next());
}
await dataSet.close();
expect(rows.length).toBeGreaterThanOrEqual(rowCount);
console.log(`Retrieved ${rows.length} rows from mixed data query`);
// Verify a few rows
for (let i = 0; i < Math.min(3, rows.length); i++) {
const row = rows[i];
console.log(`Row ${i}:`, row);
// Verify we have all columns
expect(row.getFields().length).toBe(10); // 10 data columns
}
}, 60000);
test("Should handle queries with aggregation on different data types", async () => {
if (!session.isOpen()) {
console.log("Skipping test - no IoTDB connection");
return;
}
// Query with COUNT - IoTDB requires separate queries for different aggregations
const countDataSet1 = await session.executeQueryStatement(
"SELECT COUNT(int32_sensor) FROM root.test.device1",
);
const countRows1 = [];
while (await countDataSet1.hasNext()) {
countRows1.push(countDataSet1.next());
}
await countDataSet1.close();
expect(countRows1.length).toBeGreaterThan(0);
console.log("COUNT(int32_sensor) result:", countRows1[0]);
const countDataSet2 = await session.executeQueryStatement(
"SELECT COUNT(text_sensor) FROM root.test.device1",
);
const countRows2 = [];
while (await countDataSet2.hasNext()) {
countRows2.push(countDataSet2.next());
}
await countDataSet2.close();
expect(countRows2.length).toBeGreaterThan(0);
console.log("COUNT(text_sensor) result:", countRows2[0]);
// Query with aggregation on numeric types - separate queries
const avgDataSet = await session.executeQueryStatement(
"SELECT AVG(float_sensor) FROM root.test.device1",
);
const avgRows = [];
while (await avgDataSet.hasNext()) {
avgRows.push(avgDataSet.next());
}
await avgDataSet.close();
expect(avgRows.length).toBeGreaterThan(0);
const maxDataSet = await session.executeQueryStatement(
"SELECT max_value(int32_sensor) FROM root.test.device1",
);
const maxRows = [];
while (await maxDataSet.hasNext()) {
maxRows.push(maxDataSet.next());
}
await maxDataSet.close();
expect(maxRows.length).toBeGreaterThan(0);
const minDataSet = await session.executeQueryStatement(
"SELECT min_value(double_sensor) FROM root.test.device1",
);
const minRows = [];
while (await minDataSet.hasNext()) {
minRows.push(minDataSet.next());
}
await minDataSet.close();
expect(minRows.length).toBeGreaterThan(0);
console.log(
"Aggregation results - AVG:",
avgRows[0],
"MAX:",
maxRows[0],
"MIN:",
minRows[0],
);
}, 60000);
});