Add comprehensive documentation and refactor tests for database consolidation - Introduced `copilot-instructions.md` for AI coding agent guidance on project structure and development workflows. - Created `DATABASE_CONSOLIDATION_SUMMARY.md` detailing the purpose and implementation of database unification across tests. - Added `TEST_DATABASE_REFERENCE.md` for quick reference on database usage in tests. - Implemented `DataTypes.ts` to define TSDataType enum and utility function for data type retrieval. - Refactored `TableModelDataTypes.test.ts` to consolidate table model tests under a single database, ensuring resource efficiency and clarity. - Updated all relevant test files to use unified database names and added cleanup logic to maintain test integrity.
diff --git a/DATABASE_CONSOLIDATION_SUMMARY.md b/DATABASE_CONSOLIDATION_SUMMARY.md new file mode 100644 index 0000000..41dcae5 --- /dev/null +++ b/DATABASE_CONSOLIDATION_SUMMARY.md
@@ -0,0 +1,164 @@ +# 数据库统一修改总结 + +## 修改目的 + +因为资源有限,将所有测试用例统一使用单一数据库,以减少资源消耗。 + +## 修改内容 + +### 树模型测试 - 统一使用 `root.test` 数据库 + +**修改前:** + +- Session.test.ts: `root.ln` +- AllDataTypes.test.ts: `root.all_types_test` +- SessionPool.test.ts: `root.test_pool` +- LargeQuery.test.ts: `root.large_query_test` +- MultiNode.test.ts: `root.multinode_test` + +**修改后:** + +- 所有树模型测试统一使用 `root.test` 数据库 + +**修改的文件:** + +1. `tests/e2e/Session.test.ts` + - 将所有 `root.ln` 改为 `root.test` + - 添加 afterAll 清理逻辑 `DROP DATABASE root.test` + +2. `tests/e2e/AllDataTypes.test.ts` + - 将所有 `root.all_types_test` 改为 `root.test` + - 已有 afterAll 清理逻辑,更新为 `DROP DATABASE root.test` + +3. `tests/e2e/SessionPool.test.ts` + - 将所有 `root.test_pool` 改为 `root.test` + - 添加 afterAll 清理逻辑 `DROP DATABASE root.test` + +4. `tests/e2e/LargeQuery.test.ts` + - 将所有 `root.large_query_test` 改为 `root.test` + - 已有 afterAll 清理逻辑,更新为 `DROP DATABASE root.test` + +5. `tests/e2e/MultiNode.test.ts` + - 将所有 `root.multinode_test` 改为 `root.test` + - 已有 afterAll 清理逻辑,更新为 `DROP DATABASE root.test` + +### 表模型测试 - 统一使用 `test` 数据库 + +**修改前:** + +- TableSessionPool.test.ts: `test1`, `test2` (两个数据库,还测试数据库切换) +- TableModelDataTypes.test.ts: `table_types_test`, `test_db2` (两个数据库,还测试数据库切换) + +**修改后:** + +- 所有表模型测试统一使用 `test` 数据库 + +**修改的文件:** + +1. `tests/e2e/TableSessionPool.test.ts` + - 配置的默认数据库从 `test1` 改为 `test` + - 删除 beforeAll 中对 `test1`, `test2` 的清理 + - 修改 afterAll 只清理 `test` 数据库 + - 简化测试逻辑,删除数据库切换测试 + - 在单一数据库 `test` 中创建多个表来测试功能 + +2. `tests/e2e/TableModelDataTypes.test.ts` + - 配置的默认数据库从 `table_types_test` 改为 `test` + - 修改 beforeAll 和 afterAll 只操作 `test` 数据库 + - 删除数据库切换测试 (原来的 "Should support table model context switching") + - 改为在同一数据库中创建多个表的测试 + +## 测试清理策略 + +所有测试文件现在都遵循以下清理策略: + +1. **beforeAll**: 尝试删除测试数据库 (忽略错误) +2. **测试执行**: 创建数据库和测试数据 +3. **afterAll**: 删除测试数据库 (确保清理) + +### 树模型 (5个测试文件) + +```typescript +afterAll(async () => { + if (session / pool && isOpen / isConnected) { + try { + await executeNonQueryStatement("DROP DATABASE root.test"); + } catch (error) { + // Ignore cleanup errors + } + await close(); + } +}, 60000); +``` + +### 表模型 (2个测试文件) + +```typescript +afterAll(async () => { + if (pool && isConnected) { + try { + await pool.executeNonQueryStatement("DROP DATABASE test"); + } catch (error) { + // Ignore cleanup errors + } + await pool.close(); + } +}, 60000); +``` + +## 验证结果 + +✅ 所有修改已完成 +✅ 代码编译成功 (`npm run build`) +✅ TypeScript 类型检查通过 +✅ 所有测试文件更新完毕 + +## 影响的测试用例 + +### 树模型测试 (使用 root.test) + +- Session.test.ts: 9个测试用例 +- AllDataTypes.test.ts: 5个测试用例 +- SessionPool.test.ts: 7个测试用例 +- LargeQuery.test.ts: 6个测试用例 +- MultiNode.test.ts: 7个测试用例 + +### 表模型测试 (使用 test) + +- TableSessionPool.test.ts: 10个测试用例 +- TableModelDataTypes.test.ts: 7个测试用例 + +**总计:51个测试用例** + +## 注意事项 + +1. **并发运行**: 虽然所有树模型测试使用同一个数据库 `root.test`,但它们不应该并发运行,因为会互相干扰。建议顺序执行测试。 + +2. **表模型测试**: 同样,两个表模型测试文件使用同一个 `test` 数据库,也不应该并发运行。 + +3. **资源节约**: 通过统一数据库名称,减少了 IoTDB 的资源消耗: + - 树模型:从5个数据库减少到1个 (`root.test`) + - 表模型:从4个数据库减少到1个 (`test`) + +4. **清理保证**: 每个测试文件的 afterAll 都会尝试删除数据库,确保测试环境干净。 + +## 下一步 + +可以运行测试验证修改: + +```bash +# 运行所有 E2E 测试 +npm run test:e2e + +# 或者单独运行某个测试文件 +npm test -- tests/e2e/Session.test.ts +``` + +**注意**: 需要先启动 IoTDB 实例,并设置环境变量: + +```bash +export IOTDB_HOST=localhost +export IOTDB_PORT=6667 +export IOTDB_USER=root +export IOTDB_PASSWORD=root +```
diff --git a/TEST_DATABASE_REFERENCE.md b/TEST_DATABASE_REFERENCE.md new file mode 100644 index 0000000..42fd918 --- /dev/null +++ b/TEST_DATABASE_REFERENCE.md
@@ -0,0 +1,79 @@ +# 测试数据库使用快速参考 + +## 数据库命名规范 + +### 树模型(Tree Model) + +**数据库名称**: `root.test` + +使用该数据库的测试文件: + +- `tests/e2e/Session.test.ts` +- `tests/e2e/AllDataTypes.test.ts` +- `tests/e2e/SessionPool.test.ts` +- `tests/e2e/LargeQuery.test.ts` +- `tests/e2e/MultiNode.test.ts` + +### 表模型(Table Model) + +**数据库名称**: `test` + +使用该数据库的测试文件: + +- `tests/e2e/TableSessionPool.test.ts` +- `tests/e2e/TableModelDataTypes.test.ts` + +## 运行测试 + +### 前提条件 + +确保 IoTDB 实例正在运行,并设置环境变量: + +```bash +export IOTDB_HOST=localhost +export IOTDB_PORT=6667 +export IOTDB_USER=root +export IOTDB_PASSWORD=root +``` + +### 运行所有 E2E 测试 + +```bash +npm run test:e2e +``` + +### 运行单个测试文件 + +```bash +# 树模型测试 +npm test -- tests/e2e/Session.test.ts +npm test -- tests/e2e/AllDataTypes.test.ts +npm test -- tests/e2e/SessionPool.test.ts +npm test -- tests/e2e/LargeQuery.test.ts +npm test -- tests/e2e/MultiNode.test.ts + +# 表模型测试 +npm test -- tests/e2e/TableSessionPool.test.ts +npm test -- tests/e2e/TableModelDataTypes.test.ts +``` + +## 注意事项 + +1. **不要并发运行**: 同一模型的测试不应并发执行,因为它们共享同一个数据库 +2. **自动清理**: 每个测试文件在 `afterAll` 钩子中会自动清理数据库 +3. **资源节约**: 通过使用单一数据库,减少了 IoTDB 的资源消耗 + +## 手动清理(如果需要) + +如果测试异常中断,可能需要手动清理: + +```bash +# 连接到 IoTDB CLI +./start-cli.sh -h localhost -p 6667 -u root -pw root + +# 删除树模型测试数据库 +DROP DATABASE root.test; + +# 删除表模型测试数据库 +DROP DATABASE test; +```
diff --git a/docker-compose-1c1d.yml b/docker-compose-1c1d.yml index 86bdf73..36990b7 100644 --- a/docker-compose-1c1d.yml +++ b/docker-compose-1c1d.yml
@@ -20,14 +20,8 @@ - dn_rpc_address=iotdb-datanode - dn_internal_address=iotdb-datanode - dn_seed_config_node=iotdb-confignode:10710 - deploy: - resources: - limits: - memory: 768M - reservations: - memory: 384M - logging: - driver: "none" + - IOTDB_JMX_OPTS=-Xms768M -Xmx768M -XX:MaxDirectMemorySize=384M + - CONFIGNODE_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M iotdb-confignode: image: apache/iotdb:2.0.6-confignode @@ -45,14 +39,8 @@ - cn_internal_address=iotdb-confignode - cn_internal_port=10710 - cn_seed_config_node=iotdb-confignode:10710 - deploy: - resources: - limits: - memory: 384M - reservations: - memory: 192M - logging: - driver: "none" + - IOTDB_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M + - CONFIGNODE_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M networks: iotdb-network:
diff --git a/docker-compose-3c3d.yml b/docker-compose-3c3d.yml index 8eba2b8..314d14b 100644 --- a/docker-compose-3c3d.yml +++ b/docker-compose-3c3d.yml
@@ -18,14 +18,8 @@ - cn_seed_config_node=iotdb-confignode-1:10710 - schema_replication_factor=3 - data_replication_factor=3 - deploy: - resources: - limits: - memory: 256M - reservations: - memory: 128M - logging: - driver: "none" + - IOTDB_JMX_OPTS=-Xms256M -Xmx256M -XX:MaxDirectMemorySize=128M + - IOTDB_JMX_OPTS=-Xms256M -Xmx256M -XX:MaxDirectMemorySize=128M # ConfigNode 2 iotdb-confignode-2: @@ -49,14 +43,8 @@ - cn_seed_config_node=iotdb-confignode-1:10710 - schema_replication_factor=3 - data_replication_factor=3 - deploy: - resources: - limits: - memory: 256M - reservations: - memory: 128M - logging: - driver: "none" + - IOTDB_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M + - CONFIGNODE_JMX_OPTSS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M # ConfigNode 3 iotdb-confignode-3: @@ -82,14 +70,8 @@ - cn_seed_config_node=iotdb-confignode-1:10710 - schema_replication_factor=3 - data_replication_factor=3 - deploy: - resources: - limits: - memory: 256M - reservations: - memory: 128M - logging: - driver: "none" + - IOTDB_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M + - CONFIGNODE_JMX_OPTSS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M # DataNode 1 iotdb-datanode-1: @@ -116,15 +98,8 @@ - dn_rpc_port=6667 - schema_replication_factor=3 - data_replication_factor=3 - deploy: - resources: - limits: - memory: 512M - reservations: - memory: 256M - logging: - driver: "none" - + - IOTDB_JMX_OPTS=-Xms512M -Xmx512M -XX:MaxDirectMemorySize=256M + - CONFIGNODE_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M # DataNode 2 iotdb-datanode-2: image: apache/iotdb:2.0.6-datanode @@ -152,15 +127,8 @@ - dn_rpc_port=6667 - schema_replication_factor=3 - data_replication_factor=3 - deploy: - resources: - limits: - memory: 512M - reservations: - memory: 256M - logging: - driver: "none" - + - IOTDB_JMX_OPTS=-Xms512M -Xmx512M -XX:MaxDirectMemorySize=256M + - CONFIGNODE_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M # DataNode 3 iotdb-datanode-3: image: apache/iotdb:2.0.6-datanode @@ -190,14 +158,8 @@ - dn_rpc_port=6667 - schema_replication_factor=3 - data_replication_factor=3 - deploy: - resources: - limits: - memory: 512M - reservations: - memory: 256M - logging: - driver: "none" + - IOTDB_JMX_OPTS=-Xms512M -Xmx512M -XX:MaxDirectMemorySize=256M + - CONFIGNODE_JMX_OPTS=-Xms384M -Xmx384M -XX:MaxDirectMemorySize=192M networks: iotdb-network:
diff --git a/examples/basic-session.ts b/examples/basic-session.ts index 9a69dca..4830ae7 100644 --- a/examples/basic-session.ts +++ b/examples/basic-session.ts
@@ -1,34 +1,34 @@ /** * Basic Session Example - * + * * This example demonstrates how to use a single Session to connect to IoTDB, * execute queries, and insert data. Shows both traditional and Builder pattern approaches. */ -import { Session, ConfigBuilder } from '../src'; +import { Session, ConfigBuilder, TSDataType } from "../src"; async function main() { - console.log('=== Basic Session Example ===\n'); + console.log("=== Basic Session Example ===\n"); // Method 1: Traditional constructor (backward compatible) - console.log('Method 1: Traditional constructor'); + console.log("Method 1: Traditional constructor"); const session1 = new Session({ - host: 'localhost', + host: "localhost", port: 6667, - username: 'root', - password: 'root', + username: "root", + password: "root", }); // Method 2: Using Builder pattern (recommended) - console.log('Method 2: Using Builder pattern'); + console.log("Method 2: Using Builder pattern"); const session2 = new Session( new ConfigBuilder() - .host('localhost') + .host("localhost") .port(6667) - .username('root') - .password('root') + .username("root") + .password("root") .fetchSize(1024) - .build() + .build(), ); // For demo purposes, we'll use session1 @@ -36,65 +36,60 @@ try { // Open the session - console.log('\nOpening session...'); + console.log("\nOpening session..."); await session.open(); - console.log('Session opened successfully'); + console.log("Session opened successfully"); // Create a database - console.log('\nCreating database...'); - await session.executeNonQueryStatement('CREATE DATABASE root.example'); - console.log('Database created'); + console.log("\nCreating database..."); + await session.executeNonQueryStatement("CREATE DATABASE root.example"); + console.log("Database created"); // Create timeseries - console.log('\nCreating timeseries...'); + console.log("\nCreating timeseries..."); await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.example.device1.temperature WITH DATATYPE=FLOAT, ENCODING=RLE' + "CREATE TIMESERIES root.example.device1.temperature WITH DATATYPE=FLOAT, ENCODING=RLE", ); await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.example.device1.humidity WITH DATATYPE=FLOAT, ENCODING=RLE' + "CREATE TIMESERIES root.example.device1.humidity WITH DATATYPE=FLOAT, ENCODING=RLE", ); - console.log('Timeseries created'); + console.log("Timeseries created"); // Insert tablet data - console.log('\nInserting tablet data...'); + console.log("\nInserting tablet data..."); await session.insertTablet({ - deviceId: 'root.example.device1', - measurements: ['temperature', 'humidity'], - dataTypes: [3, 3], // FLOAT - timestamps: [ - Date.now(), - Date.now() + 1000, - Date.now() + 2000, - ], + deviceId: "root.example.device1", + measurements: ["temperature", "humidity"], + dataTypes: [TSDataType.FLOAT, TSDataType.FLOAT], + timestamps: [Date.now(), Date.now() + 1000, Date.now() + 2000], values: [ [25.5, 60.0], [26.0, 61.5], [26.5, 62.0], ], }); - console.log('Tablet data inserted'); + console.log("Tablet data inserted"); // Query data - console.log('\nQuerying data...'); + console.log("\nQuerying data..."); const result = await session.executeQueryStatement( - 'SELECT * FROM root.example.device1' + "SELECT * FROM root.example.device1", ); - console.log('Columns:', result.columns); - console.log('Data types:', result.dataTypes); - console.log('Number of rows:', result.rows.length); + console.log("Columns:", result.columns); + console.log("Data types:", result.dataTypes); + console.log("Number of rows:", result.rows.length); // Show databases - console.log('\nShowing databases...'); - const dbResult = await session.executeQueryStatement('SHOW DATABASES'); - console.log('Databases:', dbResult.rows); - + console.log("\nShowing databases..."); + const dbResult = await session.executeQueryStatement("SHOW DATABASES"); + console.log("Databases:", dbResult.rows); } catch (error) { - console.error('Error:', error); + console.error("Error:", error); } finally { // Close the session - console.log('\nClosing session...'); + console.log("\nClosing session..."); await session.close(); - console.log('Session closed'); + console.log("Session closed"); } }
diff --git a/examples/session-pool.ts b/examples/session-pool.ts index a1b7f33..201be04 100644 --- a/examples/session-pool.ts +++ b/examples/session-pool.ts
@@ -1,20 +1,20 @@ /** * SessionPool Example - * + * * This example demonstrates how to use SessionPool for efficient connection * management in high-concurrency scenarios, including explicit session management. */ -import { SessionPool, PoolConfigBuilder } from '../src'; +import { SessionPool, PoolConfigBuilder } from "../src"; async function main() { - console.log('=== SessionPool Example ===\n'); + console.log("=== SessionPool Example ===\n"); // Method 1: Traditional constructor (backward compatible) - console.log('Creating session pool using traditional constructor...'); - const pool1 = new SessionPool('localhost', 6667, { - username: 'root', - password: 'root', + console.log("Creating session pool using traditional constructor..."); + const pool1 = new SessionPool("localhost", 6667, { + username: "root", + password: "root", maxPoolSize: 10, minPoolSize: 2, maxIdleTime: 60000, @@ -22,18 +22,18 @@ }); // Method 2: Using Builder pattern (recommended) - console.log('Creating session pool using Builder pattern...'); + console.log("Creating session pool using Builder pattern..."); const pool2 = new SessionPool( new PoolConfigBuilder() - .host('localhost') + .host("localhost") .port(6667) - .username('root') - .password('root') + .username("root") + .password("root") .maxPoolSize(10) .minPoolSize(2) .maxIdleTime(60000) .waitTimeout(60000) - .build() + .build(), ); // For demo purposes, we'll use pool1 @@ -41,79 +41,77 @@ try { // Initialize the pool - console.log('\nInitializing session pool...'); + console.log("\nInitializing session pool..."); await pool.init(); - console.log('Pool initialized with', pool.getPoolSize(), 'connections'); + console.log("Pool initialized with", pool.getPoolSize(), "connections"); // Create database - console.log('\nCreating database...'); - await pool.executeNonQueryStatement('CREATE DATABASE root.pool_example'); + console.log("\nCreating database..."); + await pool.executeNonQueryStatement("CREATE DATABASE root.pool_example"); // Create timeseries - console.log('Creating timeseries...'); + console.log("Creating timeseries..."); await pool.executeNonQueryStatement( - 'CREATE TIMESERIES root.pool_example.sensor1.value WITH DATATYPE=FLOAT' + "CREATE TIMESERIES root.pool_example.sensor1.value WITH DATATYPE=FLOAT", ); // Approach 1: Using pool methods directly (automatic session management) - console.log('\n--- Approach 1: Automatic session management ---'); - console.log('Executing concurrent queries...'); + console.log("\n--- Approach 1: Automatic session management ---"); + console.log("Executing concurrent queries..."); const promises = []; - + for (let i = 0; i < 20; i++) { promises.push( - pool.executeQueryStatement('SHOW DATABASES').then(() => { + pool.executeQueryStatement("SHOW DATABASES").then(() => { console.log(`Query ${i + 1} completed`); - }) + }), ); } await Promise.all(promises); - console.log('All concurrent queries completed'); + console.log("All concurrent queries completed"); // Approach 2: Explicit session management (new API) - console.log('\n--- Approach 2: Explicit session management ---'); - console.log('Getting a session from the pool...'); + console.log("\n--- Approach 2: Explicit session management ---"); + console.log("Getting a session from the pool..."); const session = await pool.getSession(); - + try { - console.log('Executing operations with explicit session...'); - + console.log("Executing operations with explicit session..."); + // Insert data await session.insertTablet({ - deviceId: 'root.pool_example.sensor1', - measurements: ['value'], - dataTypes: [3], // FLOAT + deviceId: "root.pool_example.sensor1", + measurements: ["value"], + dataTypes: [TSDataType.FLOAT], timestamps: [Date.now()], values: [[42.5]], }); - console.log('Data inserted via explicit session'); - + console.log("Data inserted via explicit session"); + // Query data const result = await session.executeQueryStatement( - 'SELECT * FROM root.pool_example.sensor1' + "SELECT * FROM root.pool_example.sensor1", ); - console.log('Query result:', result.rows.length, 'rows'); - + console.log("Query result:", result.rows.length, "rows"); } finally { // Always release the session back to the pool pool.releaseSession(session); - console.log('Session released back to the pool'); + console.log("Session released back to the pool"); } // Check pool statistics - console.log('\nPool statistics:'); - console.log('Total connections:', pool.getPoolSize()); - console.log('Available connections:', pool.getAvailableSize()); - console.log('In-use connections:', pool.getInUseSize()); - + console.log("\nPool statistics:"); + console.log("Total connections:", pool.getPoolSize()); + console.log("Available connections:", pool.getAvailableSize()); + console.log("In-use connections:", pool.getInUseSize()); } catch (error) { - console.error('Error:', error); + console.error("Error:", error); } finally { // Close the pool - console.log('\nClosing session pool...'); + console.log("\nClosing session pool..."); await pool.close(); - console.log('Pool closed'); + console.log("Pool closed"); } }
diff --git a/examples/table-session-pool.ts b/examples/table-session-pool.ts index df09eea..1d0b7e5 100644 --- a/examples/table-session-pool.ts +++ b/examples/table-session-pool.ts
@@ -1,65 +1,61 @@ /** * TableSessionPool Example - * + * * This example demonstrates how to use TableSessionPool for table model * operations in IoTDB, including explicit session management and nodeUrls. */ -import { TableSessionPool, PoolConfigBuilder } from '../src'; +import { TableSessionPool, PoolConfigBuilder, TSDataType } from "../src"; async function main() { - console.log('=== TableSessionPool Example ===\n'); + console.log("=== TableSessionPool Example ===\n"); // Method 1: Traditional constructor (backward compatible) - console.log('Method 1: Traditional constructor'); - const pool1 = new TableSessionPool('localhost', 6667, { - username: 'root', - password: 'root', - database: 'my_database', // Set default database + console.log("Method 1: Traditional constructor"); + const pool1 = new TableSessionPool("localhost", 6667, { + username: "root", + password: "root", + database: "my_database", // Set default database maxPoolSize: 10, minPoolSize: 2, }); // Method 2: Using Builder pattern (recommended) - console.log('Method 2: Using Builder pattern'); + console.log("Method 2: Using Builder pattern"); const pool2 = new TableSessionPool( new PoolConfigBuilder() - .host('localhost') + .host("localhost") .port(6667) - .username('root') - .password('root') - .database('my_database') + .username("root") + .password("root") + .database("my_database") .maxPoolSize(10) .minPoolSize(2) - .build() + .build(), ); // Method 3: Using nodeUrls with string format (for multi-node) - console.log('Method 3: Using nodeUrls in string format'); + console.log("Method 3: Using nodeUrls in string format"); const pool3 = new TableSessionPool({ - nodeUrls: [ - 'node1:6667', - 'node2:6668', - 'node3:6669', - ], - username: 'root', - password: 'root', - database: 'my_database', + nodeUrls: ["node1:6667", "node2:6668", "node3:6669"], + username: "root", + password: "root", + database: "my_database", maxPoolSize: 10, minPoolSize: 2, }); // Method 4: Using Builder with nodeUrls - console.log('Method 4: Using Builder with nodeUrls'); + console.log("Method 4: Using Builder with nodeUrls"); const pool4 = new TableSessionPool( new PoolConfigBuilder() - .nodeUrls(['node1:6667', 'node2:6668', 'node3:6669']) - .username('root') - .password('root') - .database('my_database') + .nodeUrls(["node1:6667", "node2:6668", "node3:6669"]) + .username("root") + .password("root") + .database("my_database") .maxPoolSize(10) .minPoolSize(2) - .build() + .build(), ); // For demo purposes, we'll use pool1 @@ -67,71 +63,75 @@ try { // Initialize the pool - console.log('\nInitializing table session pool...'); + console.log("\nInitializing table session pool..."); await pool.init(); - console.log('Table pool initialized with', pool.getPoolSize(), 'connections'); + console.log( + "Table pool initialized with", + pool.getPoolSize(), + "connections", + ); // Create database if not exists - console.log('\nSetting up database...'); - await pool.executeNonQueryStatement('CREATE DATABASE IF NOT EXISTS root.table_example'); + console.log("\nSetting up database..."); + await pool.executeNonQueryStatement( + "CREATE DATABASE IF NOT EXISTS root.table_example", + ); // Approach 1: Using pool methods directly (automatic session management) - console.log('\n--- Approach 1: Automatic session management ---'); - console.log('Executing table queries...'); - const result = await pool.executeQueryStatement('SHOW DATABASES'); - console.log('Databases found:', result.rows.length); + console.log("\n--- Approach 1: Automatic session management ---"); + console.log("Executing table queries..."); + const result = await pool.executeQueryStatement("SHOW DATABASES"); + console.log("Databases found:", result.rows.length); // Insert data - console.log('Inserting data...'); + console.log("Inserting data..."); await pool.insertTablet({ - deviceId: 'root.table_example.table1', - measurements: ['column1', 'column2'], - dataTypes: [1, 3], // INT32, FLOAT + deviceId: "root.table_example.table1", + measurements: ["column1", "column2"], + dataTypes: [TSDataType.INT32, TSDataType.FLOAT], timestamps: [Date.now()], values: [[100, 25.5]], }); - console.log('Data inserted'); + console.log("Data inserted"); // Approach 2: Explicit session management - console.log('\n--- Approach 2: Explicit session management ---'); - console.log('Getting a session from the pool...'); + console.log("\n--- Approach 2: Explicit session management ---"); + console.log("Getting a session from the pool..."); const session = await pool.getSession(); - + try { - console.log('Executing operations with explicit session...'); - + console.log("Executing operations with explicit session..."); + // Query with explicit session - const queryResult = await session.executeQueryStatement('SHOW DATABASES'); - console.log('Query result:', queryResult.rows.length, 'rows'); - + const queryResult = await session.executeQueryStatement("SHOW DATABASES"); + console.log("Query result:", queryResult.rows.length, "rows"); + // Insert with explicit session await session.insertTablet({ - deviceId: 'root.table_example.table1', - measurements: ['column1', 'column2'], - dataTypes: [1, 3], // INT32, FLOAT + deviceId: "root.table_example.table1", + measurements: ["column1", "column2"], + dataTypes: [TSDataType.INT32, TSDataType.FLOAT], timestamps: [Date.now() + 1000], values: [[200, 30.5]], }); - console.log('Data inserted via explicit session'); - + console.log("Data inserted via explicit session"); } finally { // Always release the session back to the pool pool.releaseSession(session); - console.log('Session released back to the pool'); + console.log("Session released back to the pool"); } // Pool statistics - console.log('\nTable pool statistics:'); - console.log('Total connections:', pool.getPoolSize()); - console.log('Available connections:', pool.getAvailableSize()); - console.log('In-use connections:', pool.getInUseSize()); - + console.log("\nTable pool statistics:"); + console.log("Total connections:", pool.getPoolSize()); + console.log("Available connections:", pool.getAvailableSize()); + console.log("In-use connections:", pool.getInUseSize()); } catch (error) { - console.error('Error:', error); + console.error("Error:", error); } finally { - console.log('\nClosing table session pool...'); + console.log("\nClosing table session pool..."); await pool.close(); - console.log('Table pool closed'); + console.log("Table pool closed"); } }
diff --git a/jest.config.js b/jest.config.js index 8be21f7..345f31f 100644 --- a/jest.config.js +++ b/jest.config.js
@@ -11,8 +11,10 @@ ], coverageDirectory: 'coverage', verbose: true, - // Performance optimizations - maxWorkers: '50%', // Use half of available CPUs + detectOpenHandles: true, + // Run tests sequentially to avoid database conflicts + // Multiple tests share the same database names (root.test for tree model, test for table model) + maxWorkers: 1, // Run tests one at a time testTimeout: 60000, // Global timeout of 60s // Cache configuration for faster subsequent runs cache: true,
diff --git a/src/client/BaseSessionPool.ts b/src/client/BaseSessionPool.ts index 9f80ea2..9805aee 100644 --- a/src/client/BaseSessionPool.ts +++ b/src/client/BaseSessionPool.ts
@@ -17,9 +17,14 @@ * under the License. */ -import { Session, QueryResult, Tablet } from './Session'; -import { PoolConfig, DEFAULT_POOL_CONFIG, EndPoint, parseNodeUrls } from '../utils/Config'; -import { logger } from '../utils/Logger'; +import { Session, QueryResult, Tablet } from "./Session"; +import { + PoolConfig, + DEFAULT_POOL_CONFIG, + EndPoint, + parseNodeUrls, +} from "../utils/Config"; +import { logger } from "../utils/Logger"; interface PooledSession { session: Session; @@ -42,41 +47,48 @@ constructor( hostsOrConfig: string | string[] | PoolConfig, port?: number, - config?: Partial<PoolConfig> + config?: Partial<PoolConfig>, ) { // Handle different constructor signatures for backward compatibility - if (typeof hostsOrConfig === 'object' && !Array.isArray(hostsOrConfig)) { + if (typeof hostsOrConfig === "object" && !Array.isArray(hostsOrConfig)) { // New format: constructor(config: PoolConfig) const poolConfig = hostsOrConfig as PoolConfig; this.config = { ...DEFAULT_POOL_CONFIG, ...poolConfig } as PoolConfig; - + if (poolConfig.nodeUrls) { if (poolConfig.nodeUrls.length === 0) { - throw new Error('nodeUrls array cannot be empty'); + throw new Error("nodeUrls array cannot be empty"); } // Parse nodeUrls if in string format - this.endPoints = typeof poolConfig.nodeUrls[0] === 'string' - ? parseNodeUrls(poolConfig.nodeUrls as string[]) - : poolConfig.nodeUrls as EndPoint[]; + this.endPoints = + typeof poolConfig.nodeUrls[0] === "string" + ? parseNodeUrls(poolConfig.nodeUrls as string[]) + : (poolConfig.nodeUrls as EndPoint[]); } else if (poolConfig.host && poolConfig.port) { this.endPoints = [{ host: poolConfig.host, port: poolConfig.port }]; } else { - throw new Error('Either nodeUrls or host/port must be provided in config'); + throw new Error( + "Either nodeUrls or host/port must be provided in config", + ); } } else { // Old format: constructor(hosts: string | string[], port: number, config?: Partial<PoolConfig>) if (port === undefined) { - throw new Error('Port must be provided when using host-based constructor'); + throw new Error( + "Port must be provided when using host-based constructor", + ); } - + this.config = { ...DEFAULT_POOL_CONFIG, ...config, port } as PoolConfig; - - const hostList = Array.isArray(hostsOrConfig) ? hostsOrConfig : [hostsOrConfig]; + + const hostList = Array.isArray(hostsOrConfig) + ? hostsOrConfig + : [hostsOrConfig]; this.endPoints = hostList.map((host) => ({ host, port })); } logger.info( - `${this.getPoolName()} created with ${this.endPoints.length} endpoints, max pool size: ${this.config.maxPoolSize}` + `${this.getPoolName()} created with ${this.endPoints.length} endpoints, max pool size: ${this.config.maxPoolSize}`, ); } @@ -101,9 +113,9 @@ // Use unref() so it doesn't keep the process alive this.cleanupInterval = setInterval(() => { this.cleanupIdleSessions().catch((error) => { - logger.error('Error during scheduled session cleanup:', error); + logger.error("Error during scheduled session cleanup:", error); }); - }, 30000).unref(); // Check every 30 seconds + }, 30000); // Check every 30 seconds logger.info(`${this.getPoolName()} initialized with ${minSize} sessions`); } @@ -123,7 +135,8 @@ protected getNextEndPoint(): EndPoint { // Round-robin selection const endPoint = this.endPoints[this.currentEndPointIndex]; - this.currentEndPointIndex = (this.currentEndPointIndex + 1) % this.endPoints.length; + this.currentEndPointIndex = + (this.currentEndPointIndex + 1) % this.endPoints.length; return endPoint; } @@ -158,7 +171,7 @@ if (index > -1) { this.waitQueue.splice(index, 1); } - reject(new Error('Timeout waiting for available session')); + reject(new Error("Timeout waiting for available session")); }, waitTimeout); this.waitQueue.push((session: Session) => { @@ -198,7 +211,7 @@ (ps) => !ps.inUse && now - ps.lastUsed > maxIdleTime && - this.pool.length > minSize + this.pool.length > minSize, ); // Properly await async operations @@ -212,13 +225,16 @@ } logger.debug(`Removed idle session from ${this.getPoolName()}`); } catch (error) { - logger.error('Error closing idle session:', error); + logger.error("Error closing idle session:", error); } - }) + }), ); } - async executeQueryStatement(sql: string, timeoutMs: number = 60000): Promise<QueryResult> { + async executeQueryStatement( + sql: string, + timeoutMs: number = 60000, + ): Promise<QueryResult> { const session = await this.getSession(); try { return await session.executeQueryStatement(sql, timeoutMs); @@ -257,9 +273,9 @@ try { await ps.session.close(); } catch (error) { - logger.error('Error closing session:', error); + logger.error("Error closing session:", error); } - }) + }), ); this.pool = [];
diff --git a/src/index.ts b/src/index.ts index 212ebb1..79911b4 100644 --- a/src/index.ts +++ b/src/index.ts
@@ -17,10 +17,17 @@ * under the License. */ -export { Session } from './client/Session'; -export type { QueryResult, Tablet } from './client/Session'; -export { SessionPool } from './client/SessionPool'; -export { TableSessionPool } from './client/TableSessionPool'; -export type { Config, PoolConfig, SSLOptions, EndPoint } from './utils/Config'; -export { DEFAULT_CONFIG, DEFAULT_POOL_CONFIG, ConfigBuilder, PoolConfigBuilder, parseNodeUrls } from './utils/Config'; -export { logger, LogLevel } from './utils/Logger'; +export { Session } from "./client/Session"; +export type { QueryResult, Tablet } from "./client/Session"; +export { SessionPool } from "./client/SessionPool"; +export { TableSessionPool } from "./client/TableSessionPool"; +export type { Config, PoolConfig, SSLOptions, EndPoint } from "./utils/Config"; +export { + DEFAULT_CONFIG, + DEFAULT_POOL_CONFIG, + ConfigBuilder, + PoolConfigBuilder, + parseNodeUrls, +} from "./utils/Config"; +export { logger, LogLevel } from "./utils/Logger"; +export { TSDataType, getDataTypeName } from "./utils/DataTypes";
diff --git a/src/utils/DataTypes.ts b/src/utils/DataTypes.ts new file mode 100644 index 0000000..a497e8b --- /dev/null +++ b/src/utils/DataTypes.ts
@@ -0,0 +1,132 @@ +/** + * 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. + */ + +/** + * TSDataType enum for Apache IoTDB data types + * Based on Apache TSFile type definitions + * + * @see https://github.com/apache/tsfile/blob/develop/java/common/src/main/java/org/apache/tsfile/enums/TSDataType.java + */ +export enum TSDataType { + /** + * Boolean type - stored as 1 byte (1 for true, 0 for false) + * JavaScript type: boolean + */ + BOOLEAN = 0, + + /** + * 32-bit signed integer + * JavaScript type: number + * Storage size: 4 bytes + */ + INT32 = 1, + + /** + * 64-bit signed integer + * JavaScript type: bigint (recommended) or number (may lose precision) + * Storage size: 8 bytes + */ + INT64 = 2, + + /** + * 32-bit floating point number + * JavaScript type: number + * Storage size: 4 bytes + */ + FLOAT = 3, + + /** + * 64-bit floating point number + * JavaScript type: number + * Storage size: 8 bytes + */ + DOUBLE = 4, + + /** + * UTF-8 encoded text string + * JavaScript type: string + * Storage size: variable (4-byte length prefix + UTF-8 content) + */ + TEXT = 5, + + // VECTOR = 6, // Reserved - not yet implemented + // UNKNOWN = 7, // Reserved - not yet implemented + + /** + * Timestamp with millisecond precision + * JavaScript type: Date or number (milliseconds since epoch) + * Storage size: 8 bytes (stored as INT64) + */ + TIMESTAMP = 8, + + /** + * Date with day precision (no time component) + * JavaScript type: Date or number (days since epoch) + * Storage size: 4 bytes (stored as INT32) + */ + DATE = 9, + + /** + * Binary large object (BLOB) + * JavaScript type: Buffer + * Storage size: variable (4-byte length prefix + binary content) + */ + BLOB = 10, + + /** + * UTF-8 encoded string (similar to TEXT) + * JavaScript type: string + * Storage size: variable (4-byte length prefix + UTF-8 content) + */ + STRING = 11, + + // OBJECT = 12, // Reserved - not yet implemented +} + +/** + * Get the name of a TSDataType from its numeric code + * @param typeCode - The numeric type code + * @returns The type name, or 'UNKNOWN' if not recognized + */ +export function getDataTypeName(typeCode: number): string { + switch (typeCode) { + case TSDataType.BOOLEAN: + return "BOOLEAN"; + case TSDataType.INT32: + return "INT32"; + case TSDataType.INT64: + return "INT64"; + case TSDataType.FLOAT: + return "FLOAT"; + case TSDataType.DOUBLE: + return "DOUBLE"; + case TSDataType.TEXT: + return "TEXT"; + case TSDataType.TIMESTAMP: + return "TIMESTAMP"; + case TSDataType.DATE: + return "DATE"; + case TSDataType.BLOB: + return "BLOB"; + case TSDataType.STRING: + return "STRING"; + default: + return "UNKNOWN"; + } +}
diff --git a/tests/e2e/AllDataTypes.test.ts b/tests/e2e/AllDataTypes.test.ts index 640a874..111a2c0 100644 --- a/tests/e2e/AllDataTypes.test.ts +++ b/tests/e2e/AllDataTypes.test.ts
@@ -17,13 +17,14 @@ * under the License. */ -import { Session } from '../../src/client/Session'; +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'; +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; @@ -37,214 +38,363 @@ try { await session.open(); - console.log('Connected to IoTDB for all data types test'); + 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.'); + 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.", + ); } }, 60000); afterAll(async () => { if (session && session.isOpen()) { - // Cleanup test data - try { - await session.executeNonQueryStatement('DROP DATABASE root.all_types_test'); - } catch (error) { - // Ignore cleanup errors - } await session.close(); } }, 60000); - test('Should create database and timeseries with all data types', async () => { + test("Should create database and timeseries with all data types", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } // Create database try { - await session.executeNonQueryStatement('CREATE DATABASE root.all_types_test'); + await session.executeNonQueryStatement("CREATE DATABASE root.test"); } catch (error: any) { - if (!error.message?.includes('already exists')) { + // 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: "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.all_types_test.device1.${dt.name} WITH DATATYPE=${dt.type}, ENCODING=${dt.encoding}` + `CREATE TIMESERIES root.test.device1.${dt.name} WITH DATATYPE=${dt.type}, ENCODING=${dt.encoding}`, ); } catch (error: any) { - if (!error.message?.includes('already exists')) { - console.warn(`Failed to create timeseries ${dt.name}: ${error.message}`); + // 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'); + console.log("Created timeseries for all supported data types"); }, 60000); - test('Should insert and retrieve data for all data types', async () => { + test("Should insert and retrieve data for all data types", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + 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.all_types_test.device1', + deviceId: "root.test.device1", measurements: [ - 'boolean_sensor', - 'int32_sensor', - 'int64_sensor', - 'float_sensor', - 'double_sensor', - 'text_sensor' + "boolean_sensor", + "int32_sensor", + "int64_sensor", + "float_sensor", + "double_sensor", + "text_sensor", + "timestamp_sensor", + "date_sensor", + "blob_sensor", + "string_sensor", ], - // Data types from Apache TSFile: - // BOOLEAN(0), INT32(1), INT64(2), FLOAT(3), DOUBLE(4), TEXT(5) - dataTypes: [0, 1, 2, 3, 4, 5], + // 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'], - [false, 200, 2000n, 2.34, 5.67, 'world'], - [true, 300, 3000n, 3.45, 6.78, 'test'], + [ + 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'); + console.log("Inserted data with all data types"); // Query the data back const result = await session.executeQueryStatement( - 'SELECT * FROM root.all_types_test.device1' + "SELECT * FROM root.test.device1", ); expect(result).toBeDefined(); expect(result.rows).toBeDefined(); expect(result.rows.length).toBeGreaterThanOrEqual(3); - + console.log(`Retrieved ${result.rows.length} rows`); - console.log('Sample row:', result.rows[0]); - + console.log("Sample row:", result.rows[0]); + // Verify data types const firstRow = result.rows[0]; - expect(firstRow.length).toBe(7); // timestamp + 6 columns - + expect(firstRow.length).toBe(11); // timestamp + 10 columns + // Check timestamp - expect(typeof firstRow[0]).toBe('bigint'); - - // Check boolean - expect(typeof firstRow[1]).toBe('boolean'); + expect(typeof firstRow[0]).toBe("bigint"); + + // Check BOOLEAN + expect(typeof firstRow[1]).toBe("boolean"); expect(firstRow[1]).toBe(true); - + // Check INT32 - expect(typeof firstRow[2]).toBe('number'); + expect(typeof firstRow[2]).toBe("number"); expect(firstRow[2]).toBe(100); - + // Check INT64 - expect(typeof firstRow[3]).toBe('bigint'); + expect(typeof firstRow[3]).toBe("bigint"); expect(firstRow[3]).toBe(1000n); - + // Check FLOAT - expect(typeof firstRow[4]).toBe('number'); + expect(typeof firstRow[4]).toBe("number"); expect(firstRow[4]).toBeCloseTo(1.23, 2); - + // Check DOUBLE - expect(typeof firstRow[5]).toBe('number'); + expect(typeof firstRow[5]).toBe("number"); expect(firstRow[5]).toBeCloseTo(4.56, 2); - + // Check TEXT - expect(typeof firstRow[6]).toBe('string'); - expect(firstRow[6]).toBe('hello'); + expect(typeof firstRow[6]).toBe("string"); + expect(firstRow[6]).toBe("hello"); + + // Check TIMESTAMP + expect(firstRow[7]).toBeInstanceOf(Date); + + // Check DATE + expect(firstRow[8]).toBeInstanceOf(Date); + + // Check BLOB + expect(Buffer.isBuffer(firstRow[9])).toBe(true); + expect(firstRow[9]).toEqual(Buffer.from([0x01, 0x02, 0x03])); + + // Check STRING + expect(typeof firstRow[10]).toBe("string"); + expect(firstRow[10]).toBe("str1"); }, 60000); - test('Should handle null values for all data types', async () => { + test("Should handle null values for all data types", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + 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.all_types_test.device1', + deviceId: "root.test.device1", measurements: [ - 'boolean_sensor', - 'int32_sensor', - 'int64_sensor', - 'float_sensor', - 'double_sensor', - 'text_sensor' + "boolean_sensor", + "int32_sensor", + "int64_sensor", + "float_sensor", + "double_sensor", + "text_sensor", + "timestamp_sensor", + "date_sensor", + "blob_sensor", + "string_sensor", ], - dataTypes: [0, 1, 2, 3, 4, 5], + 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'], + [ + 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 result = await session.executeQueryStatement( - `SELECT * FROM root.all_types_test.device1 WHERE time = ${now}` + `SELECT * FROM root.test.device1 WHERE time = ${now}`, ); expect(result.rows.length).toBeGreaterThan(0); - console.log('Null handling test row:', result.rows[0]); + console.log("Null handling test row:", result.rows[0]); }, 60000); - test('Should handle multiple rows with mixed data types', async () => { + test("Should handle multiple rows with mixed data types", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + 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 + 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.all_types_test.device1', + deviceId: "root.test.device1", measurements: [ - 'boolean_sensor', - 'int32_sensor', - 'int64_sensor', - 'float_sensor', - 'double_sensor', - 'text_sensor' + "boolean_sensor", + "int32_sensor", + "int64_sensor", + "float_sensor", + "double_sensor", + "text_sensor", + "timestamp_sensor", + "date_sensor", + "blob_sensor", + "string_sensor", ], - dataTypes: [0, 1, 2, 3, 4, 5], + dataTypes: [ + TSDataType.BOOLEAN, + TSDataType.INT32, + TSDataType.INT64, + TSDataType.FLOAT, + TSDataType.DOUBLE, + TSDataType.TEXT, + TSDataType.TIMESTAMP, + TSDataType.DATE, + TSDataType.BLOB, + TSDataType.STRING, + ], timestamps, values, }; @@ -254,42 +404,69 @@ // Query and verify const result = await session.executeQueryStatement( - `SELECT * FROM root.all_types_test.device1 WHERE time >= ${baseTime} AND time < ${baseTime + rowCount * 1000}` + `SELECT * FROM root.test.device1 WHERE time >= ${baseTime} AND time < ${baseTime + rowCount * 1000}`, ); expect(result.rows.length).toBeGreaterThanOrEqual(rowCount); console.log(`Retrieved ${result.rows.length} rows from mixed data query`); - + // Verify a few rows for (let i = 0; i < Math.min(3, result.rows.length); i++) { const row = result.rows[i]; console.log(`Row ${i}:`, row); - + // Verify we have all columns - expect(row.length).toBe(7); // timestamp + 6 data columns + expect(row.length).toBe(11); // timestamp + 10 data columns } }, 60000); - test('Should handle queries with aggregation on different data types', async () => { + test("Should handle queries with aggregation on different data types", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - // Query with COUNT - const countResult = await session.executeQueryStatement( - 'SELECT COUNT(int32_sensor), COUNT(text_sensor) FROM root.all_types_test.device1' + // Query with COUNT - IoTDB requires separate queries for different aggregations + const countResult1 = await session.executeQueryStatement( + "SELECT COUNT(int32_sensor) FROM root.test.device1", ); - - expect(countResult.rows).toBeDefined(); - console.log('COUNT result:', countResult.rows[0]); - - // Query with aggregation on numeric types - const aggResult = await session.executeQueryStatement( - 'SELECT AVG(float_sensor), MAX(int32_sensor), MIN(double_sensor) FROM root.all_types_test.device1' + expect(countResult1.rows).toBeDefined(); + expect(countResult1.rows.length).toBeGreaterThan(0); + console.log("COUNT(int32_sensor) result:", countResult1.rows[0]); + + const countResult2 = await session.executeQueryStatement( + "SELECT COUNT(text_sensor) FROM root.test.device1", ); - - expect(aggResult.rows).toBeDefined(); - console.log('Aggregation result:', aggResult.rows[0]); + expect(countResult2.rows).toBeDefined(); + expect(countResult2.rows.length).toBeGreaterThan(0); + console.log("COUNT(text_sensor) result:", countResult2.rows[0]); + + // Query with aggregation on numeric types - separate queries + const avgResult = await session.executeQueryStatement( + "SELECT AVG(float_sensor) FROM root.test.device1", + ); + expect(avgResult.rows).toBeDefined(); + expect(avgResult.rows.length).toBeGreaterThan(0); + + const maxResult = await session.executeQueryStatement( + "SELECT MAX(int32_sensor) FROM root.test.device1", + ); + expect(maxResult.rows).toBeDefined(); + expect(maxResult.rows.length).toBeGreaterThan(0); + + const minResult = await session.executeQueryStatement( + "SELECT MIN(double_sensor) FROM root.test.device1", + ); + expect(minResult.rows).toBeDefined(); + expect(minResult.rows.length).toBeGreaterThan(0); + + console.log( + "Aggregation results - AVG:", + avgResult.rows[0], + "MAX:", + maxResult.rows[0], + "MIN:", + minResult.rows[0], + ); }, 60000); });
diff --git a/tests/e2e/LargeQuery.test.ts b/tests/e2e/LargeQuery.test.ts index 98be8c9..d7ad47b 100644 --- a/tests/e2e/LargeQuery.test.ts +++ b/tests/e2e/LargeQuery.test.ts
@@ -17,13 +17,14 @@ * under the License. */ -import { Session } from '../../src/client/Session'; +import { Session } from "../../src/client/Session"; +import { TSDataType } from "../../src/utils/DataTypes"; -describe('Large Query 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'; +describe("Large Query 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; @@ -38,10 +39,12 @@ try { await session.open(); - console.log('Connected to IoTDB for large query tests'); + console.log("Connected to IoTDB for large query tests"); } 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.'); + 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.", + ); } }, 60000); @@ -49,7 +52,7 @@ if (session && session.isOpen()) { // Cleanup test data try { - await session.executeNonQueryStatement('DROP DATABASE root.large_query_test'); + await session.executeNonQueryStatement("DROP DATABASE root.test"); } catch (error) { // Ignore cleanup errors } @@ -57,16 +60,16 @@ } }, 60000); - test('Should prepare test database and timeseries', async () => { + test("Should prepare test database and timeseries", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } try { - await session.executeNonQueryStatement('CREATE DATABASE root.large_query_test'); + await session.executeNonQueryStatement("CREATE DATABASE root.test"); } catch (error: any) { - if (!error.message?.includes('already exists')) { + if (!error.message?.includes("already exists")) { throw error; } } @@ -74,38 +77,38 @@ // Create timeseries for large dataset - handle if they already exist try { await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.large_query_test.device1.sensor1 WITH DATATYPE=FLOAT, ENCODING=RLE' + "CREATE TIMESERIES root.test.device1.sensor1 WITH DATATYPE=FLOAT, ENCODING=RLE", ); } catch (error: any) { - if (!error.message?.includes('already exists')) { + if (!error.message?.includes("already exists")) { throw error; } } - + try { await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.large_query_test.device1.sensor2 WITH DATATYPE=FLOAT, ENCODING=RLE' + "CREATE TIMESERIES root.test.device1.sensor2 WITH DATATYPE=FLOAT, ENCODING=RLE", ); } catch (error: any) { - if (!error.message?.includes('already exists')) { + if (!error.message?.includes("already exists")) { throw error; } } - + try { await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.large_query_test.device1.sensor3 WITH DATATYPE=FLOAT, ENCODING=RLE' + "CREATE TIMESERIES root.test.device1.sensor3 WITH DATATYPE=FLOAT, ENCODING=RLE", ); } catch (error: any) { - if (!error.message?.includes('already exists')) { + if (!error.message?.includes("already exists")) { throw error; } } }); - test('Should insert large dataset (5,000 records)', async () => { + test("Should insert large dataset (5,000 records)", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -118,7 +121,7 @@ const timestamps: number[] = []; const values: number[][] = []; - for (let j = 0; j < batchSize && (i + j) < totalRecords; j++) { + for (let j = 0; j < batchSize && i + j < totalRecords; j++) { timestamps.push(baseTime + (i + j)); // 1ms interval values.push([ 20 + Math.random() * 10, // sensor1 @@ -128,9 +131,9 @@ } await session.insertTablet({ - deviceId: 'root.large_query_test.device1', - measurements: ['sensor1', 'sensor2', 'sensor3'], - dataTypes: [3, 3, 3], // FLOAT + deviceId: "root.test.device1", + measurements: ["sensor1", "sensor2", "sensor3"], + dataTypes: [TSDataType.FLOAT, TSDataType.FLOAT, TSDataType.FLOAT], timestamps, values, }); @@ -139,15 +142,15 @@ console.log(`Inserted ${totalRecords} records for large query test`); }); - test('Should query large dataset requiring multiple fetchResult calls', async () => { + test("Should query large dataset requiring multiple fetchResult calls", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } // Query all data - with fetchSize=100, this should require multiple fetchResult calls const result = await session.executeQueryStatement( - 'SELECT * FROM root.large_query_test.device1' + "SELECT * FROM root.test.device1", ); expect(result).toBeDefined(); @@ -157,17 +160,17 @@ expect(result.rows.length).toBeGreaterThanOrEqual(5000); console.log(`Retrieved ${result.rows.length} rows with fetchSize=100`); - console.log(`Columns: ${result.columns.join(', ')}`); + console.log(`Columns: ${result.columns.join(", ")}`); }, 30000); - test('Should query with filters on large dataset', async () => { + test("Should query with filters on large dataset", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } const result = await session.executeQueryStatement( - 'SELECT sensor1, sensor2 FROM root.large_query_test.device1 WHERE sensor1 > 25' + "SELECT sensor1, sensor2 FROM root.test.device1 WHERE sensor1 > 25", ); expect(result).toBeDefined(); @@ -178,15 +181,15 @@ console.log(`Filtered query returned ${result.rows.length} rows`); }, 30000); - test('Should query with aggregation on large dataset', async () => { + test("Should query with aggregation on large dataset", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } // IoTDB 2.x requires separate aggregation queries const countResult = await session.executeQueryStatement( - 'SELECT COUNT(sensor1) FROM root.large_query_test.device1' + "SELECT COUNT(sensor1) FROM root.test.device1", ); expect(countResult).toBeDefined(); @@ -194,27 +197,27 @@ expect(countResult.rows).toBeDefined(); expect(countResult.rows.length).toBeGreaterThan(0); - console.log('COUNT result:', countResult.rows[0]); + console.log("COUNT result:", countResult.rows[0]); const avgResult = await session.executeQueryStatement( - 'SELECT AVG(sensor1) FROM root.large_query_test.device1' + "SELECT AVG(sensor1) FROM root.test.device1", ); expect(avgResult).toBeDefined(); expect(avgResult.rows.length).toBeGreaterThan(0); - console.log('AVG result:', avgResult.rows[0]); + console.log("AVG result:", avgResult.rows[0]); }, 30000); - test('Should query with LIMIT on large dataset', async () => { + test("Should query with LIMIT on large dataset", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } // Query the first 100 records using LIMIT const result = await session.executeQueryStatement( - `SELECT * FROM root.large_query_test.device1 LIMIT 100` + `SELECT * FROM root.test.device1 LIMIT 100`, ); expect(result).toBeDefined(); @@ -224,21 +227,21 @@ console.log(`LIMIT query returned ${result.rows.length} rows`); }, 30000); - test('Should handle multiple concurrent large queries', async () => { + test("Should handle multiple concurrent large queries", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } const queries = [ - 'SELECT sensor1 FROM root.large_query_test.device1', - 'SELECT sensor2 FROM root.large_query_test.device1', - 'SELECT sensor3 FROM root.large_query_test.device1', - 'SELECT COUNT(*) FROM root.large_query_test.device1', + "SELECT sensor1 FROM root.test.device1", + "SELECT sensor2 FROM root.test.device1", + "SELECT sensor3 FROM root.test.device1", + "SELECT COUNT(*) FROM root.test.device1", ]; const results = await Promise.all( - queries.map(query => session.executeQueryStatement(query)) + queries.map((query) => session.executeQueryStatement(query)), ); expect(results).toHaveLength(4);
diff --git a/tests/e2e/MultiNode.test.ts b/tests/e2e/MultiNode.test.ts index 0077002..17e7930 100644 --- a/tests/e2e/MultiNode.test.ts +++ b/tests/e2e/MultiNode.test.ts
@@ -17,16 +17,17 @@ * under the License. */ -import { SessionPool } from '../../src/client/SessionPool'; +import { SessionPool } from "../../src/client/SessionPool"; +import { TSDataType } from "../../src/utils/DataTypes"; -describe('Multi-Node E2E Tests', () => { - const IOTDB_HOST = process.env.IOTDB_HOST || 'localhost'; - const IOTDB_PORT_1 = parseInt(process.env.IOTDB_PORT || '6667', 10); - const IOTDB_PORT_2 = parseInt(process.env.IOTDB_PORT_2 || '6668', 10); - const IOTDB_PORT_3 = parseInt(process.env.IOTDB_PORT_3 || '6669', 10); - const IOTDB_USER = process.env.IOTDB_USER || 'root'; - const IOTDB_PASSWORD = process.env.IOTDB_PASSWORD || 'root'; - const IS_MULTI_NODE = process.env.MULTI_NODE === 'true'; +describe("Multi-Node E2E Tests", () => { + const IOTDB_HOST = process.env.IOTDB_HOST || "localhost"; + const IOTDB_PORT_1 = parseInt(process.env.IOTDB_PORT || "6667", 10); + const IOTDB_PORT_2 = parseInt(process.env.IOTDB_PORT_2 || "6668", 10); + const IOTDB_PORT_3 = parseInt(process.env.IOTDB_PORT_3 || "6669", 10); + const IOTDB_USER = process.env.IOTDB_USER || "root"; + const IOTDB_PASSWORD = process.env.IOTDB_PASSWORD || "root"; + const IS_MULTI_NODE = process.env.MULTI_NODE === "true"; let pool1: SessionPool; let pool2: SessionPool; @@ -36,7 +37,9 @@ beforeAll(async () => { // Skip multi-node tests if not in multi-node environment if (!IS_MULTI_NODE) { - console.log('Skipping Multi-Node tests - not in multi-node environment (MULTI_NODE env var not set)'); + console.log( + "Skipping Multi-Node tests - not in multi-node environment (MULTI_NODE env var not set)", + ); return; } @@ -46,7 +49,7 @@ console.log(` DataNode 1: ${IOTDB_HOST}:${IOTDB_PORT_1}`); console.log(` DataNode 2: ${IOTDB_HOST}:${IOTDB_PORT_2}`); console.log(` DataNode 3: ${IOTDB_HOST}:${IOTDB_PORT_3}`); - + try { // Create three pools, each connected to a different DataNode pool1 = new SessionPool(IOTDB_HOST, IOTDB_PORT_1, { @@ -55,14 +58,14 @@ maxPoolSize: 5, minPoolSize: 2, }); - + pool2 = new SessionPool(IOTDB_HOST, IOTDB_PORT_2, { username: IOTDB_USER, password: IOTDB_PASSWORD, maxPoolSize: 5, minPoolSize: 2, }); - + pool3 = new SessionPool(IOTDB_HOST, IOTDB_PORT_3, { username: IOTDB_USER, password: IOTDB_PASSWORD, @@ -73,12 +76,16 @@ await pool1.init(); await pool2.init(); await pool3.init(); - + isConnected = true; - console.log(`Successfully connected to IoTDB cluster on ports ${IOTDB_PORT_1}, ${IOTDB_PORT_2}, ${IOTDB_PORT_3}`); + console.log( + `Successfully connected to IoTDB cluster on ports ${IOTDB_PORT_1}, ${IOTDB_PORT_2}, ${IOTDB_PORT_3}`, + ); } catch (error) { - console.warn('Could not connect to IoTDB. Multi-node E2E tests will be skipped.'); - console.warn('Error:', error); + console.warn( + "Could not connect to IoTDB. Multi-node E2E tests will be skipped.", + ); + console.warn("Error:", error); } }, 60000); @@ -88,57 +95,57 @@ } if (isConnected) { try { - await pool1.executeNonQueryStatement('DROP DATABASE root.multinode_test'); + await pool1.executeNonQueryStatement("DROP DATABASE root.test"); } catch (error) { // Ignore cleanup errors } - + // Close pools in parallel with timeout protection - await Promise.allSettled([ - pool1.close(), - pool2.close(), - pool3.close() - ]); + await Promise.allSettled([pool1.close(), pool2.close(), pool3.close()]); } }, 90000); // Increased timeout for multi-node cleanup - test('Should initialize pools with connections to all three DataNodes', async () => { + test("Should initialize pools with connections to all three DataNodes", async () => { if (!IS_MULTI_NODE || !isConnected) { - console.log('Skipping test - not in multi-node environment or no IoTDB connection'); + console.log( + "Skipping test - not in multi-node environment or no IoTDB connection", + ); return; } expect(pool1.getPoolSize()).toBeGreaterThanOrEqual(2); expect(pool2.getPoolSize()).toBeGreaterThanOrEqual(2); expect(pool3.getPoolSize()).toBeGreaterThanOrEqual(2); - console.log(`Pools initialized: DataNode1=${pool1.getPoolSize()}, DataNode2=${pool2.getPoolSize()}, DataNode3=${pool3.getPoolSize()} connections`); + console.log( + `Pools initialized: DataNode1=${pool1.getPoolSize()}, DataNode2=${pool2.getPoolSize()}, DataNode3=${pool3.getPoolSize()} connections`, + ); }); - test('Should create database and timeseries on first node', async () => { + test("Should create database and timeseries on first node", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } try { - await pool1.executeNonQueryStatement('CREATE DATABASE root.multinode_test'); + await pool1.executeNonQueryStatement("CREATE DATABASE root.test"); } catch (error: any) { - if (!error.message.includes('already exists')) { + if (!error.message.includes("already exists")) { throw error; } } await pool1.executeNonQueryStatement( - 'CREATE TIMESERIES root.multinode_test.device1.temperature WITH DATATYPE=FLOAT' + "CREATE TIMESERIES root.test.device1.temperature WITH DATATYPE=FLOAT", ); await pool1.executeNonQueryStatement( - 'CREATE TIMESERIES root.multinode_test.device1.humidity WITH DATATYPE=FLOAT' + "CREATE TIMESERIES root.test.device1.humidity WITH DATATYPE=FLOAT", ); }); - test('Should handle concurrent load distributed across all three DataNodes', async () => { + test("Should handle concurrent load distributed across all three DataNodes", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -150,71 +157,83 @@ // Insert to DataNode 1 promises.push( pool1.insertTablet({ - deviceId: 'root.multinode_test.device1', - measurements: ['temperature', 'humidity'], - dataTypes: [3, 3], + deviceId: "root.test.device1", + measurements: ["temperature", "humidity"], + dataTypes: [TSDataType.FLOAT, TSDataType.FLOAT], timestamps: [Date.now() + i * 1000], values: [[20 + i * 0.1, 60 + i * 0.2]], - }) + }), ); // Query from DataNode 2 promises.push( - pool2.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 10') + pool2.executeQueryStatement("SELECT * FROM root.test.device1 LIMIT 10"), ); // Insert to DataNode 3 promises.push( pool3.insertTablet({ - deviceId: 'root.multinode_test.device1', - measurements: ['temperature', 'humidity'], - dataTypes: [3, 3], + deviceId: "root.test.device1", + measurements: ["temperature", "humidity"], + dataTypes: [TSDataType.FLOAT, TSDataType.FLOAT], timestamps: [Date.now() + i * 1000 + 500], values: [[21 + i * 0.1, 61 + i * 0.2]], - }) + }), ); } const results = await Promise.all(promises); expect(results).toHaveLength(operationsPerNode * 3); - - console.log(`Completed ${operationsPerNode * 3} concurrent operations across 3 DataNodes`); - console.log(`Pool stats: DN1=${pool1.getPoolSize()}/${pool1.getAvailableSize()}, DN2=${pool2.getPoolSize()}/${pool2.getAvailableSize()}, DN3=${pool3.getPoolSize()}/${pool3.getAvailableSize()}`); + + console.log( + `Completed ${operationsPerNode * 3} concurrent operations across 3 DataNodes`, + ); + console.log( + `Pool stats: DN1=${pool1.getPoolSize()}/${pool1.getAvailableSize()}, DN2=${pool2.getPoolSize()}/${pool2.getAvailableSize()}, DN3=${pool3.getPoolSize()}/${pool3.getAvailableSize()}`, + ); }); - test('Should verify data replication across all DataNodes', async () => { + test("Should verify data replication across all DataNodes", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } // Insert data through DataNode 1 await pool1.insertTablet({ - deviceId: 'root.multinode_test.device1', - measurements: ['temperature', 'humidity'], - dataTypes: [3, 3], + deviceId: "root.test.device1", + measurements: ["temperature", "humidity"], + dataTypes: [TSDataType.FLOAT, TSDataType.FLOAT], timestamps: [Date.now()], values: [[99.9, 99.9]], }); // Wait a bit for replication - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, 2000)); // Query from all three DataNodes - should see the same data - const result1 = await pool1.executeQueryStatement('SELECT COUNT(*) FROM root.multinode_test.device1'); - const result2 = await pool2.executeQueryStatement('SELECT COUNT(*) FROM root.multinode_test.device1'); - const result3 = await pool3.executeQueryStatement('SELECT COUNT(*) FROM root.multinode_test.device1'); + const result1 = await pool1.executeQueryStatement( + "SELECT COUNT(*) FROM root.test.device1", + ); + const result2 = await pool2.executeQueryStatement( + "SELECT COUNT(*) FROM root.test.device1", + ); + const result3 = await pool3.executeQueryStatement( + "SELECT COUNT(*) FROM root.test.device1", + ); expect(result1.rows.length).toBeGreaterThan(0); expect(result2.rows.length).toBeGreaterThan(0); expect(result3.rows.length).toBeGreaterThan(0); - - console.log(`Data replicated across all DataNodes - verified queries from ports 6667, 6668, 6669`); + + console.log( + `Data replicated across all DataNodes - verified queries from ports 6667, 6668, 6669`, + ); }); - test('Should handle large batch inserts across multiple DataNodes', async () => { + test("Should handle large batch inserts across multiple DataNodes", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -230,25 +249,27 @@ // Insert through different DataNodes await pool1.insertTablet({ - deviceId: 'root.multinode_test.device1', - measurements: ['temperature', 'humidity'], - dataTypes: [3, 3], + deviceId: "root.test.device1", + measurements: ["temperature", "humidity"], + dataTypes: [TSDataType.FLOAT, TSDataType.FLOAT], timestamps, values, }); // Verify data from different DataNode const result = await pool2.executeQueryStatement( - 'SELECT COUNT(*) FROM root.multinode_test.device1' + "SELECT COUNT(*) FROM root.test.device1", ); expect(result.rows.length).toBeGreaterThan(0); - console.log(`Inserted ${batchSize} records via DataNode1, queried via DataNode2`); + console.log( + `Inserted ${batchSize} records via DataNode1, queried via DataNode2`, + ); }); - test('Should maintain all pool healths under stress', async () => { + test("Should maintain all pool healths under stress", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -259,9 +280,9 @@ // Execute many operations across all DataNodes const promises: Promise<any>[] = []; for (let i = 0; i < 30; i++) { - promises.push(pool1.executeQueryStatement('SHOW DATABASES')); - promises.push(pool2.executeQueryStatement('SHOW DATABASES')); - promises.push(pool3.executeQueryStatement('SHOW DATABASES')); + promises.push(pool1.executeQueryStatement("SHOW DATABASES")); + promises.push(pool2.executeQueryStatement("SHOW DATABASES")); + promises.push(pool3.executeQueryStatement("SHOW DATABASES")); } await Promise.all(promises); @@ -270,31 +291,33 @@ expect(pool1.getPoolSize()).toBeGreaterThanOrEqual(initialSize1); expect(pool2.getPoolSize()).toBeGreaterThanOrEqual(initialSize2); expect(pool3.getPoolSize()).toBeGreaterThanOrEqual(initialSize3); - console.log('All three pool healths maintained after stress test'); + console.log("All three pool healths maintained after stress test"); }); - test('Should handle queries across all DataNodes simultaneously', async () => { + test("Should handle queries across all DataNodes simultaneously", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } // Execute queries simultaneously on all three DataNodes const [result1, result2, result3] = await Promise.all([ - pool1.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 5'), - pool2.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 5'), - pool3.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 5'), + pool1.executeQueryStatement("SELECT * FROM root.test.device1 LIMIT 5"), + pool2.executeQueryStatement("SELECT * FROM root.test.device1 LIMIT 5"), + pool3.executeQueryStatement("SELECT * FROM root.test.device1 LIMIT 5"), ]); expect(result1.rows.length).toBeGreaterThan(0); expect(result2.rows.length).toBeGreaterThan(0); expect(result3.rows.length).toBeGreaterThan(0); - console.log(`Simultaneous queries across 3 DataNodes: DN1=${result1.rows.length}, DN2=${result2.rows.length}, DN3=${result3.rows.length} rows`); + console.log( + `Simultaneous queries across 3 DataNodes: DN1=${result1.rows.length}, DN2=${result2.rows.length}, DN3=${result3.rows.length} rows`, + ); }); - test('Should support nodeUrls configuration for multi-node setup', async () => { + test("Should support nodeUrls configuration for multi-node setup", async () => { if (!IS_MULTI_NODE) { - console.log('Skipping test - not in multi-node environment'); + console.log("Skipping test - not in multi-node environment"); return; } @@ -316,18 +339,18 @@ expect(nodeUrlsPool.getPoolSize()).toBeGreaterThanOrEqual(3); // Execute a query to verify it works - const result = await nodeUrlsPool.executeQueryStatement('SHOW DATABASES'); + const result = await nodeUrlsPool.executeQueryStatement("SHOW DATABASES"); expect(result.rows).toBeDefined(); - - console.log('nodeUrls string format configuration working correctly'); + + console.log("nodeUrls string format configuration working correctly"); } finally { await nodeUrlsPool.close(); } }); - test('Should support nodeUrls configuration in object format', async () => { + test("Should support nodeUrls configuration in object format", async () => { if (!IS_MULTI_NODE) { - console.log('Skipping test - not in multi-node environment'); + console.log("Skipping test - not in multi-node environment"); return; } @@ -349,10 +372,10 @@ expect(nodeUrlsPool.getPoolSize()).toBeGreaterThanOrEqual(3); // Execute a query to verify it works - const result = await nodeUrlsPool.executeQueryStatement('SHOW DATABASES'); + const result = await nodeUrlsPool.executeQueryStatement("SHOW DATABASES"); expect(result.rows).toBeDefined(); - - console.log('nodeUrls object format configuration working correctly'); + + console.log("nodeUrls object format configuration working correctly"); } finally { await nodeUrlsPool.close(); }
diff --git a/tests/e2e/Session.test.ts b/tests/e2e/Session.test.ts index 2d2a94e..c72d051 100644 --- a/tests/e2e/Session.test.ts +++ b/tests/e2e/Session.test.ts
@@ -17,13 +17,14 @@ * under the License. */ -import { Session } from '../../src/client/Session'; +import { Session } from "../../src/client/Session"; +import { TSDataType } from "../../src/utils/DataTypes"; -describe('Session 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'; +describe("Session 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; @@ -38,75 +39,86 @@ try { await session.open(); } 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.'); + 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.", + ); } }, 60000); // 30 second timeout for connection afterAll(async () => { if (session && session.isOpen()) { + // Cleanup test data + try { + await session.executeNonQueryStatement("DROP DATABASE root.test"); + } catch (e) { + // Ignore cleanup errors + } await session.close(); } }, 60000); - test('Should open and close session', async () => { + test("Should open and close session", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } expect(session.isOpen()).toBe(true); }, 60000); - test('Should create database and timeseries (tree model)', async () => { + test("Should create database and timeseries (tree model)", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - // Cleanup from previous runs + // Create database (storage group) - ignore if already exists try { - await session.executeNonQueryStatement('DELETE DATABASE root.ln'); - } catch (e) { - // Ignore if doesn't exist + await session.executeNonQueryStatement("CREATE DATABASE root.test"); + } catch (e: any) { + if (!e.message?.includes("already")) { + throw e; + } } - // Create database (storage group) - await session.executeNonQueryStatement('CREATE DATABASE root.ln'); - // Create timeseries - handle if they already exist try { await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.ln.wf01.wt01.status WITH DATATYPE=BOOLEAN, ENCODING=PLAIN' + "CREATE TIMESERIES root.test.device1.status WITH DATATYPE=BOOLEAN, ENCODING=PLAIN", ); } catch (e: any) { - if (!e.message?.includes('already exists')) { + // IoTDB returns "already exist" (without 's') + if (!e.message?.includes("already exist")) { throw e; } } - + try { await session.executeNonQueryStatement( - 'CREATE TIMESERIES root.ln.wf01.wt01.temperature WITH DATATYPE=FLOAT, ENCODING=RLE' + "CREATE TIMESERIES root.test.device1.temperature WITH DATATYPE=FLOAT, ENCODING=RLE", ); } catch (e: any) { - if (!e.message?.includes('already exists')) { + // IoTDB returns "already exist" (without 's') + if (!e.message?.includes("already exist")) { throw e; } } // Verify timeseries created - const result = await session.executeQueryStatement('SHOW TIMESERIES root.ln.**'); + const result = await session.executeQueryStatement( + "SHOW TIMESERIES root.test.**", + ); expect(result.rows.length).toBeGreaterThanOrEqual(2); }, 60000); - test('Should execute query statement (SHOW DATABASES)', async () => { + test("Should execute query statement (SHOW DATABASES)", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - const result = await session.executeQueryStatement('SHOW DATABASES'); + const result = await session.executeQueryStatement("SHOW DATABASES"); expect(result).toBeDefined(); expect(result.columns).toBeDefined(); @@ -115,19 +127,19 @@ expect(Array.isArray(result.rows)).toBe(true); }, 60000); - test('Should insert and query data (tree model)', async () => { + test("Should insert and query data (tree model)", async () => { if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } const now = Date.now(); - + // Insert tablet data const tablet = { - deviceId: 'root.ln.wf01.wt01', - measurements: ['status', 'temperature'], - dataTypes: [0, 3], // BOOLEAN, FLOAT + deviceId: "root.test.device1", + measurements: ["status", "temperature"], + dataTypes: [TSDataType.BOOLEAN, TSDataType.FLOAT], timestamps: [now, now + 1, now + 2], values: [ [true, 20.5], @@ -140,33 +152,11 @@ // Query the data const result = await session.executeQueryStatement( - 'SELECT status, temperature FROM root.ln.wf01.wt01 LIMIT 5' + "SELECT status, temperature FROM root.test.device1 LIMIT 5", ); expect(result).toBeDefined(); expect(result.columns).toBeDefined(); expect(result.rows.length).toBeGreaterThan(0); }, 60000); - - test('Should cleanup database', async () => { - if (!session.isOpen()) { - console.log('Skipping test - no IoTDB connection'); - return; - } - - try { - await session.executeNonQueryStatement('DELETE DATABASE root.ln'); - } catch (e) { - // Ignore cleanup errors - } - }, 60000); - - test('Should handle connection errors gracefully', async () => { - const badSession = new Session({ - host: 'localhost', - port: 9999, // Non-existent port - }); - - await expect(badSession.open()).rejects.toThrow(); - }, 10000); // Reduced timeout since it should fail quickly });
diff --git a/tests/e2e/SessionPool.test.ts b/tests/e2e/SessionPool.test.ts index 910b333..7a718aa 100644 --- a/tests/e2e/SessionPool.test.ts +++ b/tests/e2e/SessionPool.test.ts
@@ -17,13 +17,14 @@ * under the License. */ -import { SessionPool } from '../../src/client/SessionPool'; +import { SessionPool } from "../../src/client/SessionPool"; +import { TSDataType } from "../../src/utils/DataTypes"; -describe('SessionPool 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'; +describe("SessionPool 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: SessionPool; let isConnected = false; @@ -33,72 +34,83 @@ username: IOTDB_USER, password: IOTDB_PASSWORD, maxPoolSize: 5, - minPoolSize: 2 + minPoolSize: 2, }); try { await pool.init(); isConnected = true; } 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.'); + 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.", + ); } }, 60000); afterAll(async () => { if (pool && isConnected) { + // Cleanup test data + try { + await pool.executeNonQueryStatement("DROP DATABASE root.test"); + } catch (error) { + // Ignore cleanup errors + } await pool.close(); } }, 60000); - test('Should initialize pool with minimum connections', async () => { + test("Should initialize pool with minimum connections", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } expect(pool.getPoolSize()).toBeGreaterThanOrEqual(2); }); - test('Should execute query using pool', async () => { + test("Should execute query using pool", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - const result = await pool.executeQueryStatement('SHOW DATABASES'); + const result = await pool.executeQueryStatement("SHOW DATABASES"); expect(result).toBeDefined(); expect(result.columns).toBeDefined(); expect(Array.isArray(result.rows)).toBe(true); }); - test('Should execute non-query using pool', async () => { + test("Should execute non-query using pool", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } try { - await pool.executeNonQueryStatement('CREATE DATABASE root.test_pool'); + await pool.executeNonQueryStatement("CREATE DATABASE root.test"); // Should not throw } catch (error: any) { // Might fail if database already exists, that's ok - if (!error.message.includes('already exists')) { + if ( + !error.message.includes("already exists") && + !error.message.includes("has already been created as database") + ) { throw error; } } }); - test('Should handle multiple concurrent queries', async () => { + test("Should handle multiple concurrent queries", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } const promises = []; for (let i = 0; i < 10; i++) { - promises.push(pool.executeQueryStatement('SHOW DATABASES')); + promises.push(pool.executeQueryStatement("SHOW DATABASES")); } const results = await Promise.all(promises); @@ -110,16 +122,16 @@ }); }); - test('Should insert tablet using pool', async () => { + test("Should insert tablet using pool", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } const tablet = { - deviceId: 'root.test_pool.device1', - measurements: ['temperature'], - dataTypes: [3], // FLOAT + deviceId: "root.test.device1", + measurements: ["temperature"], + dataTypes: [TSDataType.FLOAT], timestamps: [Date.now()], values: [[25.5]], }; @@ -128,26 +140,26 @@ await pool.insertTablet(tablet); // Should not throw } catch (error: any) { - console.warn('Insert tablet via pool failed:', error.message); + console.warn("Insert tablet via pool failed:", error.message); } }); - test('Should support multi-node configuration', async () => { + test("Should support multi-node configuration", async () => { // Skip this test in 1C1D setup - only run in 3C3D if (!process.env.MULTI_NODE) { - console.log('Skipping multi-node test in 1C1D configuration'); + console.log("Skipping multi-node test in 1C1D configuration"); return; } const multiNodePool = new SessionPool( - [IOTDB_HOST, 'localhost'], + [IOTDB_HOST, "localhost"], IOTDB_PORT, { username: IOTDB_USER, password: IOTDB_PASSWORD, maxPoolSize: 3, minPoolSize: 1, - } + }, ); try { @@ -155,13 +167,15 @@ expect(multiNodePool.getPoolSize()).toBeGreaterThanOrEqual(1); await multiNodePool.close(); } catch (error) { - console.warn('Multi-node test failed, this is expected if IoTDB is not available'); + console.warn( + "Multi-node test failed, this is expected if IoTDB is not available", + ); } }); - test('Should report pool statistics', async () => { + test("Should report pool statistics", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -173,9 +187,9 @@ expect(availableSize).toBeLessThanOrEqual(poolSize); }); - test('Should support explicit session management with getSession/releaseSession', async () => { + test("Should support explicit session management with getSession/releaseSession", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -190,13 +204,12 @@ try { // Execute operations with the explicit session - const result = await session.executeQueryStatement('SHOW DATABASES'); + const result = await session.executeQueryStatement("SHOW DATABASES"); expect(result).toBeDefined(); expect(result.columns).toBeDefined(); // Session should still be open expect(session.isOpen()).toBe(true); - } finally { // Release the session back to the pool pool.releaseSession(session); @@ -205,46 +218,47 @@ // Verify pool statistics updated correctly const inUseAfter = pool.getInUseSize(); const availableAfter = pool.getAvailableSize(); - + expect(inUseAfter).toBe(inUseBefore - 1); expect(availableAfter).toBe(availableBefore + 1); }); - test('Should handle multiple explicit sessions concurrently', async () => { + test("Should handle multiple explicit sessions concurrently", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } const sessionPromises = []; - + // Get multiple sessions concurrently for (let i = 0; i < 3; i++) { sessionPromises.push( pool.getSession().then(async (session) => { try { // Execute a query with this session - const result = await session.executeQueryStatement('SHOW DATABASES'); + const result = + await session.executeQueryStatement("SHOW DATABASES"); expect(result).toBeDefined(); return session; } catch (error) { pool.releaseSession(session); throw error; } - }) + }), ); } const sessions = await Promise.all(sessionPromises); - + // All sessions should be valid expect(sessions.length).toBe(3); - sessions.forEach(session => { + sessions.forEach((session) => { expect(session.isOpen()).toBe(true); }); // Release all sessions - sessions.forEach(session => { + sessions.forEach((session) => { pool.releaseSession(session); });
diff --git a/tests/e2e/TableModelDataTypes.test.ts b/tests/e2e/TableModelDataTypes.test.ts new file mode 100644 index 0000000..f0c8dcc --- /dev/null +++ b/tests/e2e/TableModelDataTypes.test.ts
@@ -0,0 +1,397 @@ +/** + * 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 { 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.", + ); + } + }, 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 result = await pool.executeQueryStatement("SHOW TABLES"); + expect(result.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(); + + // Insert data using insertTablet - note the different structure vs tree model + // In table model: deviceId is the table name, measurements include tags/attributes/fields + const tablet = { + deviceId: "all_types_table", // Table name, not hierarchical path + measurements: [ + "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 + ], + dataTypes: [ + 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 + ], + 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 result = await pool.executeQueryStatement( + "SELECT * FROM all_types_table WHERE region = 'region1'", + ); + + expect(result).toBeDefined(); + expect(result.rows).toBeDefined(); + expect(result.rows.length).toBeGreaterThanOrEqual(3); + + console.log(`Retrieved ${result.rows.length} rows from table model`); + console.log("Sample row:", result.rows[0]); + + // Verify data types in results + const firstRow = result.rows[0]; + expect(firstRow.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 result1 = await pool.executeQueryStatement("SHOW TABLES"); + expect(result1.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 countResult = await pool.executeQueryStatement( + "SELECT COUNT(int32_field), COUNT(text_field) FROM all_types_table", + ); + + expect(countResult.rows).toBeDefined(); + expect(countResult.rows.length).toBeGreaterThan(0); + console.log("COUNT result:", countResult.rows[0]); + + // Query with aggregation on numeric types + const aggResult = await pool.executeQueryStatement( + "SELECT AVG(float_field), MAX(int32_field), MIN(double_field) FROM all_types_table", + ); + + expect(aggResult.rows).toBeDefined(); + expect(aggResult.rows.length).toBeGreaterThan(0); + console.log("Aggregation result:", aggResult.rows[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 result = await pool.executeQueryStatement( + `SELECT * FROM all_types_table WHERE time >= ${now} AND time < ${futureTime}`, + ); + + expect(result.rows).toBeDefined(); + console.log(`Time-based query returned ${result.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 tagResult = await pool.executeQueryStatement( + "SELECT device_id, int32_field FROM all_types_table WHERE region = 'region1'", + ); + + console.log(`\nTag-based query returned ${tagResult.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 = { + deviceId: "all_types_table", + measurements: [ + "region", + "device_id", + "model", + "boolean_field", + "int32_field", + "int64_field", + "float_field", + "double_field", + "text_field", + "timestamp_field", + "date_field", + "blob_field", + "string_field", + ], + dataTypes: [ + 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, + ], + timestamps, + values, + }; + + await pool.insertTablet(tablet); + console.log(`Inserted ${batchSize} rows in batch with all data types`); + + // Verify batch insert + const result = await pool.executeQueryStatement( + `SELECT COUNT(*) FROM all_types_table WHERE time >= ${baseTime}`, + ); + + expect(result.rows.length).toBeGreaterThan(0); + console.log("Batch insert verified"); + }, 60000); +});
diff --git a/tests/e2e/TableSessionPool.test.ts b/tests/e2e/TableSessionPool.test.ts index bb001e5..8367921 100644 --- a/tests/e2e/TableSessionPool.test.ts +++ b/tests/e2e/TableSessionPool.test.ts
@@ -17,13 +17,14 @@ * under the License. */ -import { TableSessionPool } from '../../src/client/TableSessionPool'; +import { TableSessionPool } from "../../src/client/TableSessionPool"; +import { TSDataType } from "../../src/utils/DataTypes"; -describe('TableSessionPool 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'; +describe("TableSessionPool 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; @@ -32,9 +33,9 @@ pool = new TableSessionPool(IOTDB_HOST, IOTDB_PORT, { username: IOTDB_USER, password: IOTDB_PASSWORD, - database: 'test1', + database: "test", maxPoolSize: 5, - minPoolSize: 2 + minPoolSize: 2, }); try { @@ -42,14 +43,15 @@ isConnected = true; // Cleanup from previous runs try { - await pool.executeNonQueryStatement('DROP DATABASE test1'); - await pool.executeNonQueryStatement('DROP DATABASE test2'); + await pool.executeNonQueryStatement("DROP DATABASE test"); } catch (e) { - // Ignore errors if databases don't exist + // Ignore errors if database 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.'); + 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.", + ); } }, 60000); @@ -57,8 +59,7 @@ if (pool && isConnected) { // Cleanup try { - await pool.executeNonQueryStatement('DROP DATABASE test1'); - await pool.executeNonQueryStatement('DROP DATABASE test2'); + await pool.executeNonQueryStatement("DROP DATABASE test"); } catch (e) { // Ignore cleanup errors } @@ -66,145 +67,147 @@ } }, 60000); - test('Should create databases and tables (based on C# example)', async () => { + test("Should create database and tables", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - // Create databases - await pool.executeNonQueryStatement('CREATE DATABASE test1'); - await pool.executeNonQueryStatement('CREATE DATABASE test2'); + // Create database + await pool.executeNonQueryStatement("CREATE DATABASE test"); - // Use test2 database - await pool.executeNonQueryStatement('USE test2'); + // Use test database + await pool.executeNonQueryStatement("USE test"); - // Create table in test1 using full qualified name + // Create table1 await pool.executeNonQueryStatement( - 'CREATE TABLE test1.table1(' + - 'region_id STRING TAG, ' + - 'plant_id STRING TAG, ' + - 'device_id STRING TAG, ' + - 'model STRING ATTRIBUTE, ' + - 'temperature FLOAT FIELD, ' + - 'humidity DOUBLE FIELD) ' + - 'WITH (TTL=3600000)' + "CREATE TABLE table1(" + + "region_id STRING TAG, " + + "plant_id STRING TAG, " + + "device_id STRING TAG, " + + "model STRING ATTRIBUTE, " + + "temperature FLOAT FIELD, " + + "humidity DOUBLE FIELD) " + + "WITH (TTL=3600000)", ); - // Create table in current database (test2) + // Create table2 await pool.executeNonQueryStatement( - 'CREATE TABLE table2(' + - 'region_id STRING TAG, ' + - 'plant_id STRING TAG, ' + - 'color STRING ATTRIBUTE, ' + - 'temperature FLOAT FIELD, ' + - 'speed DOUBLE FIELD) ' + - 'WITH (TTL=6600000)' + "CREATE TABLE table2(" + + "region_id STRING TAG, " + + "plant_id STRING TAG, " + + "color STRING ATTRIBUTE, " + + "temperature FLOAT FIELD, " + + "speed DOUBLE FIELD) " + + "WITH (TTL=6600000)", ); - // Show tables from current database (test2) - const result1 = await pool.executeQueryStatement('SHOW TABLES'); + // Show tables from current database (test) + const result1 = await pool.executeQueryStatement("SHOW TABLES"); expect(result1).toBeDefined(); - expect(result1.rows.length).toBeGreaterThan(0); - - // Show tables from test1 database - const result2 = await pool.executeQueryStatement('SHOW TABLES FROM test1'); - expect(result2).toBeDefined(); - expect(result2.rows.length).toBeGreaterThan(0); + expect(result1.rows.length).toBeGreaterThanOrEqual(2); }); - test('Should insert and query table data (based on C# example)', async () => { + test("Should insert and query table data (based on C# example)", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - const tableName = 'testTable1'; - + const tableName = "testTable1"; + // Create table await pool.executeNonQueryStatement( `CREATE TABLE ${tableName}(` + - 'region_id STRING TAG, ' + - 'plant_id STRING TAG, ' + - 'device_id STRING TAG, ' + - 'model STRING ATTRIBUTE, ' + - 'temperature FLOAT FIELD, ' + - 'humidity DOUBLE FIELD)' + "region_id STRING TAG, " + + "plant_id STRING TAG, " + + "device_id STRING TAG, " + + "model STRING ATTRIBUTE, " + + "temperature FLOAT FIELD, " + + "humidity DOUBLE FIELD)", ); // Insert data using tablet - simplified to 50 records const tablet = { deviceId: tableName, - measurements: ['region_id', 'plant_id', 'device_id', 'model', 'temperature', 'humidity'], - dataTypes: [5, 5, 5, 5, 3, 4], // STRING, STRING, STRING, STRING, FLOAT, DOUBLE + measurements: [ + "region_id", + "plant_id", + "device_id", + "model", + "temperature", + "humidity", + ], + dataTypes: [ + TSDataType.STRING, + TSDataType.STRING, + TSDataType.STRING, + TSDataType.STRING, + TSDataType.FLOAT, + TSDataType.DOUBLE, + ], timestamps: [] as number[], values: [] as any[][], }; for (let i = 0; i < 50; i++) { tablet.timestamps.push(i); - tablet.values.push(['1', '5', '3', 'A', 1.23 + i, 111.1 + i]); + tablet.values.push(["1", "5", "3", "A", 1.23 + i, 111.1 + i]); } await pool.insertTablet(tablet); // Query the data const result = await pool.executeQueryStatement( - `SELECT * FROM ${tableName} WHERE region_id = '1' AND plant_id IN ('3', '5') AND device_id = '3' LIMIT 10` + `SELECT * FROM ${tableName} WHERE region_id = '1' AND plant_id IN ('3', '5') AND device_id = '3' LIMIT 10`, ); expect(result).toBeDefined(); expect(result.rows.length).toBeGreaterThan(0); }); - test('Should use database context switching (based on C# example)', async () => { + test("Should query tables from database context", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - // Show tables from test1 - const result1 = await pool.executeQueryStatement('SHOW TABLES FROM test1'); - expect(result1).toBeDefined(); - - // Switch to test2 - await pool.executeNonQueryStatement('USE test2'); - - // Show tables from current database (test2) - const result2 = await pool.executeQueryStatement('SHOW TABLES'); - expect(result2).toBeDefined(); + // Show tables from current database (test) + const result = await pool.executeQueryStatement("SHOW TABLES"); + expect(result).toBeDefined(); + expect(result.rows.length).toBeGreaterThan(0); }); - test('Should handle insert with null values (based on C# example)', async () => { + test("Should handle insert with null values (based on C# example)", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } - const tableName = 't1'; + const tableName = "t1"; // Create table await pool.executeNonQueryStatement( - `CREATE TABLE ${tableName}(t1 STRING TAG, f1 INT32 FIELD)` + `CREATE TABLE ${tableName}(t1 STRING TAG, f1 INT32 FIELD)`, ); // Insert data with some null values const tablet = { deviceId: tableName, - measurements: ['t1', 'f1'], - dataTypes: [5, 1], // STRING, INT32 + measurements: ["t1", "f1"], + dataTypes: [TSDataType.STRING, TSDataType.INT32], timestamps: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], values: [ - ['t1', 100], - ['t1', 200], - ['t1', 300], - ['t1', 400], - ['t1', 500], - ['t1', null], - ['t1', null], - ['t1', null], - ['t1', null], - ['t1', null], + ["t1", 100], + ["t1", 200], + ["t1", 300], + ["t1", 400], + ["t1", 500], + ["t1", null], + ["t1", null], + ["t1", null], + ["t1", null], + ["t1", null], ], }; @@ -212,7 +215,7 @@ // Query null count const result = await pool.executeQueryStatement( - `SELECT COUNT(*) FROM ${tableName} WHERE f1 IS NULL` + `SELECT COUNT(*) FROM ${tableName} WHERE f1 IS NULL`, ); expect(result).toBeDefined(); @@ -222,9 +225,9 @@ expect(count).toBe(5); }); - test('Should report pool statistics', async () => { + test("Should report pool statistics", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -236,9 +239,9 @@ expect(availableSize).toBeLessThanOrEqual(poolSize); }); - test('Should support explicit session management with getSession/releaseSession', async () => { + test("Should support explicit session management with getSession/releaseSession", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -249,7 +252,7 @@ try { // Execute operations with the explicit session - const result = await session.executeQueryStatement('SHOW DATABASES'); + const result = await session.executeQueryStatement("SHOW DATABASES"); expect(result).toBeDefined(); expect(result.columns).toBeDefined(); @@ -264,9 +267,9 @@ expect(pool.getAvailableSize()).toBeGreaterThan(0); }); - test('Should support nodeUrls in string format', async () => { + test("Should support nodeUrls in string format", async () => { if (!isConnected) { - console.log('Skipping test - no IoTDB connection'); + console.log("Skipping test - no IoTDB connection"); return; } @@ -275,7 +278,7 @@ nodeUrls: [`${IOTDB_HOST}:${IOTDB_PORT}`], username: IOTDB_USER, password: IOTDB_PASSWORD, - database: 'test1', + database: "test", maxPoolSize: 3, minPoolSize: 1, }); @@ -284,10 +287,13 @@ await stringNodeUrlsPool.init(); expect(stringNodeUrlsPool.getPoolSize()).toBeGreaterThanOrEqual(1); - const result = await stringNodeUrlsPool.executeQueryStatement('SHOW DATABASES'); + const result = + await stringNodeUrlsPool.executeQueryStatement("SHOW DATABASES"); expect(result).toBeDefined(); - - console.log('TableSessionPool with string format nodeUrls working correctly'); + + console.log( + "TableSessionPool with string format nodeUrls working correctly", + ); } finally { await stringNodeUrlsPool.close(); }