| /** |
| * 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 { TableSessionPool } from "../../src/client/TableSessionPool"; |
| import { ColumnCategory } from "../../src/client/Session"; |
| import { TSDataType } from "../../src/utils/DataTypes"; |
| |
| describe("Table Model 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 pool: TableSessionPool; |
| let isConnected = false; |
| |
| beforeAll(async () => { |
| pool = new TableSessionPool(IOTDB_HOST, IOTDB_PORT, { |
| username: IOTDB_USER, |
| password: IOTDB_PASSWORD, |
| database: "test", |
| maxPoolSize: 5, |
| minPoolSize: 2, |
| }); |
| |
| try { |
| await pool.init(); |
| isConnected = true; |
| console.log("Connected to IoTDB for table model data types test"); |
| |
| // Cleanup from previous runs |
| try { |
| await pool.executeNonQueryStatement("DROP DATABASE test"); |
| } catch (e) { |
| // Ignore if doesn't exist |
| } |
| } 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 pool.close(); |
| } catch { |
| // Ignore cleanup errors |
| } |
| } |
| }, 60000); |
| |
| afterAll(async () => { |
| if (pool && isConnected) { |
| // Cleanup test data |
| try { |
| await pool.executeNonQueryStatement("DROP DATABASE test"); |
| } catch (error) { |
| // Ignore cleanup errors |
| } |
| await pool.close(); |
| } |
| }, 60000); |
| |
| test("Should create database and table with all data types", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| // Create database |
| await pool.executeNonQueryStatement("CREATE DATABASE test"); |
| |
| // Switch to database context |
| await pool.executeNonQueryStatement("USE test"); |
| |
| // Create table with all 10 data types in relational schema |
| // Tags: metadata for grouping/filtering (indexed) |
| // Attributes: static properties (not time-series) |
| // Fields: actual time-series measurements |
| await pool.executeNonQueryStatement( |
| "CREATE TABLE all_types_table(" + |
| "region STRING TAG, " + // Tag for filtering |
| "device_id STRING TAG, " + // Tag for device identification |
| "model STRING ATTRIBUTE, " + // Static attribute |
| "boolean_field BOOLEAN FIELD, " + // All 10 data types as fields |
| "int32_field INT32 FIELD, " + |
| "int64_field INT64 FIELD, " + |
| "float_field FLOAT FIELD, " + |
| "double_field DOUBLE FIELD, " + |
| "text_field TEXT FIELD, " + |
| "timestamp_field TIMESTAMP FIELD, " + |
| "date_field DATE FIELD, " + |
| "blob_field BLOB FIELD, " + |
| "string_field STRING FIELD) " + |
| "WITH (TTL=3600000)", |
| ); |
| |
| // Verify table created |
| const dataSet = await pool.executeQueryStatement("SHOW TABLES"); |
| const rows = []; |
| while (await dataSet.hasNext()) { |
| rows.push(dataSet.next()); |
| } |
| await dataSet.close(); |
| expect(rows.length).toBeGreaterThan(0); |
| console.log("Created table with all 10 data types in table model"); |
| }, 60000); |
| |
| test("Should insert and retrieve all data types in table model", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| const now = Date.now(); |
| |
| // Ensure we're in the correct database context |
| await pool.executeNonQueryStatement("USE test"); |
| |
| // Insert data using insertTablet - table model API |
| const tablet = { |
| tableName: "all_types_table", // Table name without database prefix |
| columnNames: [ |
| "region", // TAG |
| "device_id", // TAG |
| "model", // ATTRIBUTE |
| "boolean_field", // FIELD |
| "int32_field", // FIELD |
| "int64_field", // FIELD |
| "float_field", // FIELD |
| "double_field", // FIELD |
| "text_field", // FIELD |
| "timestamp_field", // FIELD |
| "date_field", // FIELD |
| "blob_field", // FIELD |
| "string_field", // FIELD |
| ], |
| columnTypes: [ |
| TSDataType.STRING, // region (TAG) |
| TSDataType.STRING, // device_id (TAG) |
| TSDataType.STRING, // model (ATTRIBUTE) |
| TSDataType.BOOLEAN, // boolean_field |
| TSDataType.INT32, // int32_field |
| TSDataType.INT64, // int64_field |
| TSDataType.FLOAT, // float_field |
| TSDataType.DOUBLE, // double_field |
| TSDataType.TEXT, // text_field |
| TSDataType.TIMESTAMP, // timestamp_field |
| TSDataType.DATE, // date_field |
| TSDataType.BLOB, // blob_field |
| TSDataType.STRING, // string_field |
| ], |
| columnCategories: [ |
| ColumnCategory.TAG, // region |
| ColumnCategory.TAG, // device_id |
| ColumnCategory.ATTRIBUTE, // model |
| ColumnCategory.FIELD, // boolean_field |
| ColumnCategory.FIELD, // int32_field |
| ColumnCategory.FIELD, // int64_field |
| ColumnCategory.FIELD, // float_field |
| ColumnCategory.FIELD, // double_field |
| ColumnCategory.FIELD, // text_field |
| ColumnCategory.FIELD, // timestamp_field |
| ColumnCategory.FIELD, // date_field |
| ColumnCategory.FIELD, // blob_field |
| ColumnCategory.FIELD, // string_field |
| ], |
| timestamps: [now, now + 1000, now + 2000], |
| values: [ |
| [ |
| "region1", |
| "device001", |
| "model_a", |
| true, |
| 100, |
| 1000n, |
| 1.23, |
| 4.56, |
| "text1", |
| new Date(now), |
| new Date(now), |
| Buffer.from([0x01, 0x02]), |
| "string1", |
| ], |
| [ |
| "region1", |
| "device001", |
| "model_a", |
| false, |
| 200, |
| 2000n, |
| 2.34, |
| 5.67, |
| "text2", |
| new Date(now + 1000), |
| new Date(now + 1000), |
| Buffer.from([0x03, 0x04]), |
| "string2", |
| ], |
| [ |
| "region1", |
| "device002", |
| "model_b", |
| true, |
| 300, |
| 3000n, |
| 3.45, |
| 6.78, |
| "text3", |
| new Date(now + 2000), |
| new Date(now + 2000), |
| Buffer.from([0x05, 0x06]), |
| "string3", |
| ], |
| ], |
| }; |
| |
| await pool.insertTablet(tablet); |
| console.log("Inserted data with all 10 data types in table model"); |
| |
| // Query data - table model supports WHERE clauses on tags |
| const dataSet = await pool.executeQueryStatement( |
| "SELECT * FROM all_types_table WHERE region = 'region1'", |
| ); |
| |
| expect(dataSet).toBeDefined(); |
| const rows = []; |
| while (await dataSet.hasNext()) { |
| rows.push(dataSet.next()); |
| } |
| await dataSet.close(); |
| |
| expect(rows.length).toBeGreaterThanOrEqual(3); |
| |
| console.log(`Retrieved ${rows.length} rows from table model`); |
| console.log("Sample row:", rows[0]); |
| |
| // Verify data types in results |
| const firstRow = rows[0]; |
| expect(firstRow.getFields().length).toBeGreaterThan(10); // timestamp + tags + attributes + fields |
| |
| console.log("All 10 data types successfully tested in table model"); |
| }, 60000); |
| |
| test("Should support table model queries", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| // Create another table in the same database |
| await pool.executeNonQueryStatement( |
| "CREATE TABLE test_table(" + |
| "id STRING TAG, " + |
| "value INT32 FIELD) " + |
| "WITH (TTL=3600000)", |
| ); |
| |
| // Verify table in current context |
| const dataSet = await pool.executeQueryStatement("SHOW TABLES"); |
| const rows = []; |
| while (await dataSet.hasNext()) { |
| rows.push(dataSet.next()); |
| } |
| await dataSet.close(); |
| expect(rows.length).toBeGreaterThanOrEqual(2); |
| |
| console.log("Multiple tables in same database verified"); |
| }, 60000); |
| |
| test("Should handle queries with aggregations on table model", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| // Query with COUNT aggregation |
| const countDataSet = await pool.executeQueryStatement( |
| "SELECT COUNT(int32_field), COUNT(text_field) FROM all_types_table", |
| ); |
| |
| const countRows = []; |
| while (await countDataSet.hasNext()) { |
| countRows.push(countDataSet.next()); |
| } |
| await countDataSet.close(); |
| |
| expect(countRows.length).toBeGreaterThan(0); |
| console.log("COUNT result:", countRows[0]); |
| |
| // Query with aggregation on numeric types |
| const aggDataSet = await pool.executeQueryStatement( |
| "SELECT AVG(float_field), MAX(int32_field), MIN(double_field) FROM all_types_table", |
| ); |
| |
| const aggRows = []; |
| while (await aggDataSet.hasNext()) { |
| aggRows.push(aggDataSet.next()); |
| } |
| await aggDataSet.close(); |
| |
| expect(aggRows.length).toBeGreaterThan(0); |
| console.log("Aggregation result:", aggRows[0]); |
| }, 60000); |
| |
| test("Should handle time-based queries in table model", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| const now = Date.now(); |
| const futureTime = now + 10000; |
| |
| // Query with time range |
| const dataSet = await pool.executeQueryStatement( |
| `SELECT * FROM all_types_table WHERE time >= ${now} AND time < ${futureTime}`, |
| ); |
| |
| const rows = []; |
| while (await dataSet.hasNext()) { |
| rows.push(dataSet.next()); |
| } |
| await dataSet.close(); |
| |
| console.log(`Time-based query returned ${rows.length} rows`); |
| }, 60000); |
| |
| test("Should demonstrate difference between tree and table models", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| console.log("\n=== Tree Model vs Table Model Comparison ==="); |
| |
| console.log("\nTree Model characteristics:"); |
| console.log("- Hierarchical paths: root.database.device.sensor"); |
| console.log("- Each timeseries defined separately"); |
| console.log("- deviceId in insertTablet is a full path"); |
| console.log("- Example: root.test.device1.temperature"); |
| |
| console.log("\nTable Model characteristics:"); |
| console.log("- Relational schema with tags, attributes, fields"); |
| console.log("- Table defines the schema once"); |
| console.log("- deviceId in insertTablet is the table name"); |
| console.log("- Supports WHERE clauses on tags"); |
| console.log("- Example: SELECT * FROM table WHERE tag = 'value'"); |
| |
| // Demonstrate table model query with tags |
| const dataSet = await pool.executeQueryStatement( |
| "SELECT device_id, int32_field FROM all_types_table WHERE region = 'region1'", |
| ); |
| |
| const rows = []; |
| while (await dataSet.hasNext()) { |
| rows.push(dataSet.next()); |
| } |
| await dataSet.close(); |
| |
| console.log(`\nTag-based query returned ${rows.length} rows`); |
| console.log("This type of query is specific to table model"); |
| }, 60000); |
| |
| test("Should handle batch inserts with all data types", async () => { |
| if (!isConnected) { |
| console.log("Skipping test - no IoTDB connection"); |
| return; |
| } |
| |
| const baseTime = Date.now() + 20000; |
| const batchSize = 20; |
| const timestamps: number[] = []; |
| const values: any[][] = []; |
| |
| for (let i = 0; i < batchSize; i++) { |
| timestamps.push(baseTime + i * 1000); |
| values.push([ |
| "region2", // region (TAG) |
| `device${i % 3}`, // device_id (TAG) |
| "model_batch", // model (ATTRIBUTE) |
| i % 2 === 0, // boolean_field |
| i * 10, // int32_field |
| BigInt(i * 100), // int64_field |
| i * 1.5, // float_field |
| i * 2.5, // double_field |
| `batch_${i}`, // text_field |
| new Date(baseTime + i * 1000), // timestamp_field |
| new Date(baseTime + i * 1000), // date_field |
| Buffer.from([i % 256]), // blob_field |
| `string_${i}`, // string_field |
| ]); |
| } |
| |
| const tablet = { |
| tableName: "all_types_table", |
| columnNames: [ |
| "region", |
| "device_id", |
| "model", |
| "boolean_field", |
| "int32_field", |
| "int64_field", |
| "float_field", |
| "double_field", |
| "text_field", |
| "timestamp_field", |
| "date_field", |
| "blob_field", |
| "string_field", |
| ], |
| columnTypes: [ |
| TSDataType.STRING, |
| TSDataType.STRING, |
| TSDataType.STRING, |
| TSDataType.BOOLEAN, |
| TSDataType.INT32, |
| TSDataType.INT64, |
| TSDataType.FLOAT, |
| TSDataType.DOUBLE, |
| TSDataType.TEXT, |
| TSDataType.TIMESTAMP, |
| TSDataType.DATE, |
| TSDataType.BLOB, |
| TSDataType.STRING, |
| ], |
| columnCategories: [ |
| ColumnCategory.TAG, |
| ColumnCategory.TAG, |
| ColumnCategory.ATTRIBUTE, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ColumnCategory.FIELD, |
| ], |
| timestamps, |
| values, |
| }; |
| |
| await pool.insertTablet(tablet); |
| console.log(`Inserted ${batchSize} rows in batch with all data types`); |
| |
| // Verify batch insert |
| const dataSet = await pool.executeQueryStatement( |
| `SELECT COUNT(*) FROM all_types_table WHERE time >= ${baseTime}`, |
| ); |
| |
| const rows = []; |
| while (await dataSet.hasNext()) { |
| rows.push(dataSet.next()); |
| } |
| await dataSet.close(); |
| |
| expect(rows.length).toBeGreaterThan(0); |
| console.log("Batch insert verified"); |
| }, 60000); |
| }); |