Version: 1.0.0
Last Updated: 2024
- 1. Introduction
- 2. Installation
- 3. Quick Start
- 4. TableSessionPool API
- 5. Configuration Builder
- 6. Data Types
- 7. Code Examples
- 8. Best Practices
- 9. Troubleshooting
The Apache IoTDB Node.js Client provides native support for the table model (relational data model), enabling efficient management of structured data using SQL-like table operations. This guide covers the TableSessionPool API for table model operations.
The table model in IoTDB organizes data in a relational format:
- Database-based Organization: Create and manage databases containing multiple tables
- Table Schema: Define tables with tags, attributes, and fields
- SQL Operations: Use familiar SQL syntax for queries and data manipulation
- Connection Pooling: Built-in pool for high-concurrency scenarios
- Automatic Context: Database context management with
USE DATABASE
- Database: Logical grouping of related tables
- Table: Schema definition with columns and column categories
- Tags: Identifiers for time series (indexed, used in WHERE clauses)
- Attributes: Metadata for time series (not indexed)
- Fields: Actual measurement values
| Aspect | Table Model | Tree Model |
|---|---|---|
| Organization | Relational tables | Hierarchical paths |
| Schema | Explicit table schema | Timeseries definitions |
| Query Language | Standard SQL | IoTDB SQL with paths |
| Use Case | Structured, relational data | Hierarchical IoT data |
| Data Model | Tags + Attributes + Fields | Device + Measurements |
npm install @iotdb/clientRequirements:
- Node.js >= 14.0.0
- Apache IoTDB >= 1.0.0 (with table model support)
TypeScript:
import{TableSessionPool,PoolConfigBuilder,TableTablet,ColumnCategory,TSDataType}from'@iotdb/client';JavaScript:
const{ TableSessionPool, PoolConfigBuilder, TableTablet, ColumnCategory, TSDataType }=require('@iotdb/client');import{TableSessionPool,TableTablet,ColumnCategory}from'@iotdb/client';asyncfunctionquickStart(){// Create and initialize table session poolconstpool=newTableSessionPool('localhost',6667,{username: 'root',password: 'root',database: 'test_db',// Optional: set default databasemaxPoolSize: 10,minPoolSize: 2,});awaitpool.open();try{// Create databaseawaitpool.executeNonQueryStatement('CREATE DATABASE test_db');// Use databaseawaitpool.executeNonQueryStatement('USE test_db');// Create tableawaitpool.executeNonQueryStatement(` CREATE TABLE sensor_data ( region_id STRING TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity DOUBLE FIELD ) WITH (TTL=3600000) `);// Insert data using TableTablet class with addRowconsttablet=newTableTablet('sensor_data',['region_id','device_id','model','temperature','humidity'],[5,5,5,3,4],// STRING, STRING, STRING, FLOAT, DOUBLE[ColumnCategory.TAG,ColumnCategory.TAG,ColumnCategory.ATTRIBUTE,ColumnCategory.FIELD,ColumnCategory.FIELD]);tablet.addRow(Date.now(),['region1','device001','ModelA',25.5,60.0]);awaitpool.insertTablet(tablet);// Query dataconstdataSet=awaitpool.executeQueryStatement(` SELECT * FROM sensor_data WHERE region_id = 'region1' AND device_id = 'device001' `);while(awaitdataSet.hasNext()){constrow=dataSet.next();console.log(`Temperature: ${row.getFloat('temperature')}°C, Humidity: ${row.getDouble('humidity')}%`);}awaitdataSet.close();}finally{awaitpool.close();}}quickStart();asyncfunctionwithDatabaseContext(){// Create pool with database pre-configuredconstpool=newTableSessionPool('localhost',6667,{username: 'root',password: 'root',database: 'production_db',// Automatically executes USE DATABASEmaxPoolSize: 20,});awaitpool.open();try{// No need for explicit USE DATABASE// Already in 'production_db' contextconstdataSet=awaitpool.executeQueryStatement('SHOW TABLES');while(awaitdataSet.hasNext()){constrow=dataSet.next();console.log('Table:',row.getFields());}awaitdataSet.close();}finally{awaitpool.close();}}TableSessionPool is a specialized connection pool for table model operations. It extends the base SessionPool functionality with table-specific features and automatic database context management.
Key Features:
- Same connection pooling as SessionPool
- Automatic
USE DATABASEwhen configured with database - Table-specific insertTablet with column categories
- SQL-based operations
- Round-robin load balancing
constpool=newTableSessionPool('localhost',// Host6667,// Port{username: 'root',password: 'root',database: 'my_database',// OptionalmaxPoolSize: 20,minPoolSize: 5,});constpool=newTableSessionPool({nodeUrls: ['node1:6667','node2:6668','node3:6669',],username: 'root',password: 'root',database: 'my_database',maxPoolSize: 20,minPoolSize: 5,});import{PoolConfigBuilder}from'@iotdb/client';constpool=newTableSessionPool(newPoolConfigBuilder().nodeUrls(['node1:6667','node2:6667']).username('root').password('root').database('my_database').maxPoolSize(20).minPoolSize(5).maxIdleTime(60000).waitTimeout(60000).build());All SessionPool options plus:
| Option | Type | Default | Description |
|---|---|---|---|
database | string | undefined | Default database for table operations |
Opens the connection pool. Optionally enables RPC compression.
Parameters:
enableRpcCompression: Enable RPC compression (default: false)
Example:
// Open without compressionawaitpool.open();// Open with compressionawaitpool.open(true);Closes all sessions in the pool.
Example:
awaitpool.close();Executes a SQL query statement.
Parameters:
sql: SQL query statementtimeoutMs: Query timeout in milliseconds (default: 60000)
Returns: SessionDataSet for iterating results
Example:
constdataSet=awaitpool.executeQueryStatement(` SELECT temperature, humidity FROM sensor_data WHERE region_id = 'region1' LIMIT 100`);while(awaitdataSet.hasNext()){constrow=dataSet.next();console.log(row.getTimestamp(),row.getFloat('temperature'));}awaitdataSet.close();Executes DDL or DML statements.
Parameters:
sql: SQL statement
Example:
// Create databaseawaitpool.executeNonQueryStatement('CREATE DATABASE my_db');// Use databaseawaitpool.executeNonQueryStatement('USE my_db');// Create tableawaitpool.executeNonQueryStatement(` CREATE TABLE devices ( device_id STRING TAG, location STRING ATTRIBUTE, value FLOAT FIELD )`);// Drop tableawaitpool.executeNonQueryStatement('DROP TABLE devices');// Drop databaseawaitpool.executeNonQueryStatement('DROP DATABASE my_db');Inserts data into a table using tablet format.
Parameters:
tablet: TableTablet object or plain object containing table data
TableTablet Interface (for plain objects):
interfaceITableTablet{tableName: string;// Table namecolumnNames: string[];// Column namescolumnTypes: number[];// Data type codes (TSDataType)columnCategories: ColumnCategory[];// Column categoriestimestamps: number[];// Timestamps in millisecondsvalues: any[][];// 2D array: [rows][columns]}ColumnCategory Enum:
enumColumnCategory{TAG=0,// Tag column - indexed for WHERE clause filtering (e.g., device_id, region_id)FIELD=2,// Field column - measurement values (e.g., temperature, humidity)ATTRIBUTE=1,// Attribute column - metadata not indexed (e.g., model, firmware_version)TIME=3,// Time column (reserved for internal use only)}Column Categories Explained:
TAG(0) - Indexed columns used for filtering in WHERE clauses (e.g., device_id, region_id)FIELD(2) - Measurement values (e.g., temperature, humidity)ATTRIBUTE(1) - Metadata not used for filtering (e.g., device_model, firmware_version)TIME(3) - Reserved for internal use. Do not use in columnCategories array - timestamps are handled separately via the timestamps array
TableTablet Class (with helper methods - recommended):
import{TableTablet,ColumnCategory,TSDataType}from'@iotdb/client';// Create a tabletconsttablet=newTableTablet('sensor_data',['region_id','device_id','model','temperature','humidity'],[TSDataType.TEXT,TSDataType.TEXT,TSDataType.TEXT,TSDataType.FLOAT,TSDataType.DOUBLE],[ColumnCategory.TAG,ColumnCategory.TAG,ColumnCategory.ATTRIBUTE,ColumnCategory.FIELD,ColumnCategory.FIELD]);// Add rows one at a time using addRow methodtablet.addRow(Date.now(),['region1','device001','ModelA',25.5,60.0]);tablet.addRow(Date.now()+1000,['region1','device001','ModelA',26.0,61.5]);tablet.addRow(Date.now()+2000,['region1','device002','ModelB',24.8,58.5]);// Insert the tabletawaitpool.insertTablet(tablet);Alternative: Plain object approach (still supported):
import{ColumnCategory,TSDataType}from'@iotdb/client';awaitpool.insertTablet({tableName: 'sensor_data',columnNames: ['region_id','device_id','model','temperature','humidity'],columnTypes: [TSDataType.TEXT,TSDataType.TEXT,TSDataType.TEXT,TSDataType.FLOAT,TSDataType.DOUBLE],columnCategories: [ColumnCategory.TAG,// region_id - indexed tagColumnCategory.TAG,// device_id - indexed tagColumnCategory.ATTRIBUTE,// model - metadataColumnCategory.FIELD,// temperature - measurementColumnCategory.FIELD,// humidity - measurement],timestamps: [Date.now(),Date.now()+1000,Date.now()+2000,],values: [['region1','device001','ModelA',25.5,60.0],['region1','device001','ModelA',26.0,61.5],['region1','device002','ModelB',24.8,58.5],],});Example with numeric values (also supported):
awaitpool.insertTablet({tableName: 'sensor_data',columnNames: ['region_id','device_id','model','temperature','humidity'],columnTypes: [5,5,5,3,4],// TEXT, TEXT, TEXT, FLOAT, DOUBLEcolumnCategories: [0,0,1,2,2],// TAG, TAG, ATTRIBUTE, FIELD, FIELDtimestamps: [Date.now()],values: [['region1','device001','ModelA',25.5,60.0]],});Benefits of TableTablet class:
- ✅ Convenient:
addRow()method simplifies adding data row-by-row - ✅ Type-safe: Constructor validates parameter lengths
- ✅ Validated: Automatic checking that values match columns count
- ✅ Streaming-friendly: Easy to add rows as data arrives
The PoolConfigBuilder is used to create TableSessionPool configurations.
Available Methods:
host(host: string): thisport(port: number): thisnodeUrls(urls: string[]): thisusername(username: string): thispassword(password: string): thisdatabase(database: string): this- Important for table modeltimezone(timezone: string): thisfetchSize(size: number): thismaxPoolSize(size: number): thisminPoolSize(size: number): thismaxIdleTime(time: number): thiswaitTimeout(timeout: number): thisenableSSL(enable: boolean): thissslOptions(options: SSLOptions): thisbuild(): PoolConfig
Example:
constconfig=newPoolConfigBuilder().nodeUrls(['iotdb1:6667','iotdb2:6667','iotdb3:6667']).username('root').password('root').database('production_db').fetchSize(2048).maxPoolSize(30).minPoolSize(10).maxIdleTime(60000).waitTimeout(60000).build();constpool=newTableSessionPool(config);awaitpool.open();The table model supports all IoTDB data types:
| Code | Type | JavaScript Type | Usage in Table Model |
|---|---|---|---|
| 0 | BOOLEAN | boolean | Tags, Attributes, Fields |
| 1 | INT32 | number | Tags, Attributes, Fields |
| 2 | INT64 | number/string | Tags, Attributes, Fields |
| 3 | FLOAT | number | Attributes, Fields |
| 4 | DOUBLE | number | Attributes, Fields |
| 5 | TEXT | string | Tags, Attributes, Fields |
| 8 | TIMESTAMP | number/Date | Fields |
| 9 | DATE | number/Date | Fields |
| 10 | BLOB | Buffer | Fields |
| 11 | STRING | string | Tags, Attributes, Fields |
| Code | Category | Purpose | Indexed | Usage |
|---|---|---|---|---|
| 0 | TAG | Identifiers | Yes | Use in WHERE clauses for filtering |
| 1 | ATTRIBUTE | Metadata | No | Descriptive information |
| 2 | FIELD | Measurements | No | Actual sensor/measurement values |
Example with Mixed Types:
awaitpool.insertTablet({tableName: 'equipment_metrics',columnNames: ['factory_id',// TAG'equipment_id',// TAG'manufacturer',// ATTRIBUTE'model',// ATTRIBUTE'temperature',// FIELD'pressure',// FIELD'is_active',// FIELD'last_check',// FIELD],columnTypes: [5,5,5,5,3,4,0,8],// STRING, STRING, STRING, STRING, FLOAT, DOUBLE, BOOLEAN, TIMESTAMPcolumnCategories: [0,0,1,1,2,2,2,2],// TAG, TAG, ATTR, ATTR, FIELD, FIELD, FIELD, FIELDtimestamps: [Date.now()],values: [['factory01','equip123','ManufacturerA','ModelX',75.5,101.325,true,Date.now(),]],});import{TableSessionPool,PoolConfigBuilder}from'@iotdb/client';asyncfunctionsetupDatabase(){constpool=newTableSessionPool(newPoolConfigBuilder().host('localhost').port(6667).username('root').password('root').maxPoolSize(10).build());awaitpool.open();try{// Create databaseawaitpool.executeNonQueryStatement('CREATE DATABASE iot_platform');// Use the databaseawaitpool.executeNonQueryStatement('USE iot_platform');// Create table with TTLawaitpool.executeNonQueryStatement(` CREATE TABLE sensor_readings ( region_id STRING TAG, building_id STRING TAG, floor INT32 TAG, device_id STRING TAG, device_type STRING ATTRIBUTE, location STRING ATTRIBUTE, temperature FLOAT FIELD, humidity FLOAT FIELD, co2_level INT32 FIELD, timestamp TIMESTAMP FIELD ) WITH (TTL=7776000000) `);console.log('Database and table created successfully');// Show tablesconstdataSet=awaitpool.executeQueryStatement('SHOW TABLES');console.log('Tables in database:');while(awaitdataSet.hasNext()){console.log(dataSet.next().getFields());}awaitdataSet.close();}finally{awaitpool.close();}}setupDatabase();asyncfunctionbatchInsert(pool: TableSessionPool){constregionIds=['north','south','east','west'];constdeviceIds=['dev001','dev002','dev003'];consttimestamps=[];constvalues=[];constnow=Date.now();// Generate 100 recordsfor(leti=0;i<100;i++){timestamps.push(now+i*1000);constregion=regionIds[i%regionIds.length];constdevice=deviceIds[i%deviceIds.length];values.push([region,// region_id (TAG)device,// device_id (TAG)'SensorModelA',// model (ATTRIBUTE)20+Math.random()*10,// temperature (FIELD)50+Math.random()*30,// humidity (FIELD)]);}awaitpool.insertTablet({tableName: 'sensor_readings',columnNames: ['region_id','device_id','model','temperature','humidity'],columnTypes: [5,5,5,3,3],columnCategories: [0,0,1,2,2],
timestamps,
values,});console.log(`Inserted ${timestamps.length} records`);}asyncfunctionqueryWithFilters(pool: TableSessionPool){// Query by TAG (indexed, efficient)constdataSet=awaitpool.executeQueryStatement(` SELECT device_id, temperature, humidity, timestamp FROM sensor_readings WHERE region_id = 'north' AND device_id IN ('dev001', 'dev002') AND temperature > 25.0 ORDER BY timestamp DESC LIMIT 100 `);constresults=[];while(awaitdataSet.hasNext()){constrow=dataSet.next();results.push({deviceId: row.getString('device_id'),temperature: row.getFloat('temperature'),humidity: row.getFloat('humidity'),timestamp: newDate(row.getTimestamp()),});}awaitdataSet.close();console.log(`Found ${results.length} matching records`);returnresults;}asyncfunctionaggregationQuery(pool: TableSessionPool){constdataSet=awaitpool.executeQueryStatement(` SELECT region_id, device_id, AVG(temperature) as avg_temp, MAX(temperature) as max_temp, MIN(temperature) as min_temp, COUNT(*) as record_count FROM sensor_readings WHERE timestamp >= ${Date.now()-3600000} GROUP BY region_id, device_id `);console.log('Aggregation Results:');while(awaitdataSet.hasNext()){constrow=dataSet.next();console.log(`Region: ${row.getString('region_id')}, Device: ${row.getString('device_id')}`);console.log(` Avg Temp: ${row.getFloat('avg_temp').toFixed(2)}°C`);console.log(` Max Temp: ${row.getFloat('max_temp').toFixed(2)}°C`);console.log(` Min Temp: ${row.getFloat('min_temp').toFixed(2)}°C`);console.log(` Records: ${row.getInt('record_count')}`);}awaitdataSet.close();}asyncfunctionmultiDatabaseOps(pool: TableSessionPool){awaitpool.open();try{// Create multiple databasesawaitpool.executeNonQueryStatement('CREATE DATABASE production');awaitpool.executeNonQueryStatement('CREATE DATABASE staging');// Work with production databaseawaitpool.executeNonQueryStatement('USE production');awaitpool.executeNonQueryStatement(` CREATE TABLE metrics ( device_id STRING TAG, value DOUBLE FIELD ) `);// Switch to staging databaseawaitpool.executeNonQueryStatement('USE staging');awaitpool.executeNonQueryStatement(` CREATE TABLE test_metrics ( device_id STRING TAG, value DOUBLE FIELD ) `);// Query across databases using fully qualified namesconstprodData=awaitpool.executeQueryStatement('SELECT * FROM production.metrics LIMIT 10');conststagingData=awaitpool.executeQueryStatement('SELECT * FROM staging.test_metrics LIMIT 10');awaitprodData.close();awaitstagingData.close();}finally{awaitpool.close();}}Use TAGs effectively:
- Use TAGs for columns frequently used in WHERE clauses
- TAGs are indexed, enabling fast queries
- Keep TAG cardinality reasonable (avoid millions of unique values)
Use ATTRIBUTEs for metadata:
- Descriptive information that doesn't need indexing
- Device model, manufacturer, location, etc.
- Not used in WHERE clauses
Use FIELDs for measurements:
- Actual sensor readings and metrics
- Time-series data values
Example:
// Good table designCREATETABLEsensor_data(region_idSTRINGTAG,// Indexed, used in WHEREdevice_idSTRINGTAG,// Indexed, used in WHEREmanufacturerSTRINGATTRIBUTE,// Metadata, not indexedmodelSTRINGATTRIBUTE,// Metadata, not indexedtemperatureFLOATFIELD,// Measurement valuehumidityFLOATFIELD// Measurement value)// Poor design - using FIELD for identifiersCREATETABLEsensor_data(temperatureFLOATFIELD,humidityFLOATFIELD,device_idSTRINGFIELD// Should be TAG!)Filter by TAGs in WHERE clause:
// Good: Uses indexed TAGsSELECT*FROMsensorsWHEREregion_id='north'ANDdevice_id='dev001'// Poor: Filter by non-indexed FIELDSELECT*FROMsensorsWHEREtemperature>25.0// No TAG filteringUse appropriate LIMIT:
// Prevent loading too much dataSELECT*FROMsensorsWHEREregion_id='north'LIMIT1000Use time range filters:
SELECT*FROMsensorsWHEREregion_id='north'ANDtimestamp>=${Date.now()-3600000}ANDtimestamp<=${Date.now()}Size the pool appropriately:
constpool=newTableSessionPool({nodeUrls: ['localhost:6667'],maxPoolSize: 50,// Peak concurrent queriesminPoolSize: 10,// Keep warm connectionsmaxIdleTime: 60000,// Clean up after 1 minute idlewaitTimeout: 30000,// Wait max 30s for connection});Monitor pool health:
setInterval(()=>{console.log('Pool Stats:');console.log(` Total: ${pool.getPoolSize()}`);console.log(` Available: ${pool.getAvailableSize()}`);console.log(` In Use: ${pool.getInUseSize()}`);},60000);// Every minuteasyncfunctionrobustInsert(pool: TableSessionPool,data: any){try{awaitpool.insertTablet(data);console.log('Insert successful');}catch(error){if(error.message.includes('Table does not exist')){console.log('Creating table...');awaitcreateTable(pool);awaitpool.insertTablet(data);}elseif(error.message.includes('Database does not exist')){console.log('Creating database...');awaitcreateDatabase(pool);awaitcreateTable(pool);awaitpool.insertTablet(data);}else{console.error('Insert failed:',error);throwerror;}}}asyncfunctionproperCleanup(){constpool=newTableSessionPool('localhost',6667,{username: 'root',password: 'root',});awaitpool.open();try{constdataSet=awaitpool.executeQueryStatement('SELECT * FROM table1');try{while(awaitdataSet.hasNext()){// Process results}}finally{awaitdataSet.close();// Always close DataSet}}finally{awaitpool.close();// Always close pool}}Symptoms:
Error: Database 'my_db' does not exist
Solutions:
// Create database firstawaitpool.executeNonQueryStatement('CREATE DATABASE my_db');awaitpool.executeNonQueryStatement('USE my_db');// Or configure pool with existing databaseconstpool=newTableSessionPool('localhost',6667,{database: 'my_db',// Must exist});Symptoms:
Error: Table 'my_table' does not exist
Solutions:
// Check if table existsconstdataSet=awaitpool.executeQueryStatement('SHOW TABLES');// ... verify table exists// Create table if neededawaitpool.executeNonQueryStatement(` CREATE TABLE my_table (...)`);Symptoms:
Error: Column count mismatch
Solutions:
- Ensure
columnNames,columnTypes, andcolumnCategorieshave same length - Verify
valuesarray matches column count - Check table schema matches your data
// Verify schemaconstdataSet=awaitpool.executeQueryStatement('DESCRIBE my_table');while(awaitdataSet.hasNext()){console.log(dataSet.next().getFields());}Symptoms: Data automatically deleted after some time
Solutions:
// Check TTL settingconstdataSet=awaitpool.executeQueryStatement('SHOW TABLES');// Look for TTL in table properties// Modify TTLawaitpool.executeNonQueryStatement(` ALTER TABLE my_table SET PROPERTIES TTL=31536000000`);// 1 year in millisecondsSlow Queries:
- Add indexes by using TAGs appropriately
- Use time range filters
- Add LIMIT clauses
- Consider table partitioning
Slow Inserts:
- Increase batch size (100-1000 rows)
- Use connection pooling
- Consider multiple concurrent writers
- Monitor server resources
Enable debug logging:
process.env.LOG_LEVEL='debug';Check SQL syntax:
try{awaitpool.executeQueryStatement('EXPLAIN SELECT * FROM my_table');}catch(error){console.error('Invalid SQL:',error.message);}Monitor query execution:
conststart=Date.now();constdataSet=awaitpool.executeQueryStatement('SELECT ...');console.log(`Query took ${Date.now()-start}ms`);letrowCount=0;while(awaitdataSet.hasNext()){dataSet.next();rowCount++;}console.log(`Returned ${rowCount} rows`);- Documentation: IoTDB Table Model Docs
- GitHub Issues: Report bugs
- Community: dev@iotdb.apache.org
open(enableRpcCompression?)- Open connection poolclose()- Close all sessionsexecuteQueryStatement(sql, timeout?)- Execute SQL queryexecuteNonQueryStatement(sql)- Execute DDL/DMLinsertTablet(tablet)- Batch insert into tablegetPoolSize()- Total sessionsgetAvailableSize()- Available sessionsgetInUseSize()- Active sessions
CREATE DATABASE database_nameDROP DATABASE database_nameUSE database_nameSHOW DATABASESSHOW TABLESCREATE TABLE table_name (...)DROP TABLE table_nameALTER TABLE table_name SET PROPERTIES TTL=<ms>SELECT ... FROM table_name WHERE ... LIMIT ...DESCRIBE table_name
See data-types.md for comprehensive data type documentation.
Version: 1.0.0
Last Updated: January 2024
License: Apache License 2.0