| /** |
| * 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. |
| */ |
| |
| /** |
| * Schema Manager Module |
| * |
| * Handles metadata registration before performance testing to avoid |
| * metadata creation overhead during benchmarks. Supports both tree |
| * and table models. |
| */ |
| |
| const { TSDataType } = require('./config'); |
| |
| /** |
| * Map TSDataType code to IoTDB type string |
| * @param {number} dataType - TSDataType code |
| * @returns {string} IoTDB data type string |
| */ |
| function getDataTypeString(dataType) { |
| const typeMap = { |
| [TSDataType.BOOLEAN]: 'BOOLEAN', |
| [TSDataType.INT32]: 'INT32', |
| [TSDataType.INT64]: 'INT64', |
| [TSDataType.FLOAT]: 'FLOAT', |
| [TSDataType.DOUBLE]: 'DOUBLE', |
| [TSDataType.TEXT]: 'TEXT', |
| [TSDataType.TIMESTAMP]: 'TIMESTAMP', |
| [TSDataType.DATE]: 'DATE', |
| [TSDataType.BLOB]: 'BLOB', |
| [TSDataType.STRING]: 'STRING', |
| }; |
| return typeMap[dataType] || 'FLOAT'; |
| } |
| |
| /** |
| * Create tree model schema |
| * @param {Object} session - IoTDB session or pool |
| * @param {Object} testData - Test data structure |
| * @param {Object} config - Configuration object |
| */ |
| async function createTreeModelSchema(session, testData, config) { |
| console.log('\n=== Creating Tree Model Schema (Using Device Template) ==='); |
| const startTime = Date.now(); |
| |
| try { |
| // Delete existing storage group if exists |
| console.log(`Deleting existing storage group: ${config.STORAGE_GROUP_PREFIX}...`); |
| try { |
| await session.executeNonQueryStatement( |
| `DROP DATABASE ${config.STORAGE_GROUP_PREFIX}` |
| ); |
| console.log(' ✓ Existing storage group deleted'); |
| } catch (error) { |
| if (error.message && (error.message.includes('does not exist') || error.message.includes('not exist'))) { |
| console.log(' ℹ Storage group does not exist, will create new one'); |
| } else { |
| console.log(` ℹ Delete storage group: ${error.message}`); |
| } |
| } |
| |
| // Create storage group |
| console.log(`Creating storage group: ${config.STORAGE_GROUP_PREFIX}...`); |
| await session.executeNonQueryStatement( |
| `CREATE DATABASE ${config.STORAGE_GROUP_PREFIX}` |
| ); |
| console.log(' ✓ Storage group created'); |
| |
| // Use device template for fast schema creation |
| const templateName = 'benchmark_template'; |
| |
| // Step 1: Drop existing template if exists |
| console.log(`Dropping existing device template: ${templateName}...`); |
| try { |
| await session.executeNonQueryStatement( |
| `DROP DEVICE TEMPLATE ${templateName}` |
| ); |
| console.log(' ✓ Existing template dropped'); |
| } catch (error) { |
| console.log(' ℹ Template does not exist, will create new one'); |
| } |
| |
| // Step 2: Create device template with measurements from first device |
| // All devices have same structure in benchmark |
| if (testData.devices && testData.devices.length > 0) { |
| const device = testData.devices[0]; |
| |
| // Build template creation SQL |
| const measurements = []; |
| for (let i = 0; i < device.measurements.length; i++) { |
| const measurement = device.measurements[i]; |
| const dataType = device.dataTypes[i]; |
| const typeString = getDataTypeString(dataType); |
| measurements.push(`${measurement} ${typeString}`); |
| } |
| |
| const createTemplateSQL = `CREATE DEVICE TEMPLATE ${templateName} (${measurements.join(', ')})`; |
| console.log(`Creating device template with ${device.measurements.length} measurements...`); |
| console.log(`SQL: ${createTemplateSQL}`); |
| |
| await session.executeNonQueryStatement(createTemplateSQL); |
| console.log(' ✓ Device template created'); |
| |
| // Step 3: Set template to storage group |
| console.log(`Setting template to storage group: ${config.STORAGE_GROUP_PREFIX}...`); |
| await session.executeNonQueryStatement( |
| `SET DEVICE TEMPLATE ${templateName} TO ${config.STORAGE_GROUP_PREFIX}` |
| ); |
| console.log(' ✓ Template set to storage group'); |
| |
| // Step 4: Template will be auto-activated when data is inserted |
| // No need to manually activate for each device |
| console.log(`Template will be auto-activated for ${testData.devices.length} devices on first insert`); |
| |
| const duration = Date.now() - startTime; |
| console.log(`\n✓ Schema creation completed in ${(duration / 1000).toFixed(2)}s`); |
| console.log(` Using device template for ${testData.devices.length} devices`); |
| console.log(` Total measurements per device: ${device.measurements.length}`); |
| } else { |
| throw new Error('No devices in test data'); |
| } |
| |
| } catch (error) { |
| console.error('✗ Schema creation failed:', error); |
| throw error; |
| } |
| } |
| |
| /** |
| * Create table model schema |
| * @param {Object} session - IoTDB session or pool |
| * @param {Object} testData - Test data structure |
| * @param {Object} config - Configuration object |
| */ |
| async function createTableModelSchema(session, testData, config) { |
| console.log('\n=== Creating Table Model Schema ==='); |
| const startTime = Date.now(); |
| |
| try { |
| // Delete existing database if exists |
| console.log(`Deleting existing database: ${config.DATABASE_NAME}...`); |
| try { |
| await session.executeNonQueryStatement( |
| `DROP DATABASE ${config.DATABASE_NAME}` |
| ); |
| console.log(' ✓ Existing database deleted'); |
| } catch (error) { |
| if (error.message && (error.message.includes('does not exist') || error.message.includes('not exist'))) { |
| console.log(' ℹ Database does not exist, will create new one'); |
| } else { |
| console.log(` ℹ Delete database: ${error.message}`); |
| } |
| } |
| |
| // Create database |
| console.log(`Creating database: ${config.DATABASE_NAME}...`); |
| await session.executeNonQueryStatement( |
| `CREATE DATABASE ${config.DATABASE_NAME}` |
| ); |
| console.log(' ✓ Database created'); |
| |
| // Use database |
| await session.executeNonQueryStatement(`USE ${config.DATABASE_NAME}`); |
| console.log(' ✓ Using database'); |
| |
| // Drop existing table if exists |
| try { |
| await session.executeNonQueryStatement(`DROP TABLE ${config.TABLE_NAME}`); |
| console.log(' ✓ Dropped existing table'); |
| } catch (error) { |
| // Ignore if table doesn't exist |
| } |
| |
| // Build CREATE TABLE statement from device structure |
| const columns = ['device_id STRING TAG']; // device_id is TAG for identification |
| |
| // Add sensor columns from first device (all devices have same structure) |
| if (testData.devices && testData.devices.length > 0) { |
| const device = testData.devices[0]; |
| for (let i = 0; i < device.measurements.length; i++) { |
| const measurementName = device.measurements[i]; |
| const dataType = device.dataTypes[i]; |
| const typeString = getDataTypeString(dataType); |
| // All sensors are FIELD columns (measurement values) |
| columns.push(`${measurementName} ${typeString} FIELD`); |
| } |
| } |
| |
| const createTableSQL = `CREATE TABLE ${config.TABLE_NAME} (${columns.join(', ')})`; |
| console.log(`Creating table with ${columns.length} columns...`); |
| console.log(`SQL: ${createTableSQL}`); |
| |
| await session.executeNonQueryStatement(createTableSQL); |
| console.log(' ✓ Table created'); |
| |
| const duration = Date.now() - startTime; |
| console.log(`\n✓ Schema creation completed in ${(duration / 1000).toFixed(2)}s`); |
| |
| } catch (error) { |
| console.error('✗ Schema creation failed:', error); |
| throw error; |
| } |
| } |
| |
| /** |
| * Clean up test schema |
| * @param {Object} session - IoTDB session or pool |
| * @param {string} model - 'tree' or 'table' |
| * @param {Object} config - Configuration object |
| */ |
| async function cleanupSchema(session, model, config) { |
| console.log('\n=== Cleaning Up Schema ==='); |
| |
| try { |
| if (model === 'tree') { |
| console.log(`Dropping storage group: ${config.STORAGE_GROUP_PREFIX}...`); |
| try { |
| await session.executeNonQueryStatement( |
| `DELETE DATABASE ${config.STORAGE_GROUP_PREFIX}.*` |
| ); |
| console.log(' ✓ Storage group deleted'); |
| } catch (error) { |
| console.log(' ℹ Storage group does not exist or already deleted'); |
| } |
| } else if (model === 'table') { |
| console.log(`Dropping database: ${config.DATABASE_NAME}...`); |
| try { |
| await session.executeNonQueryStatement( |
| `DROP DATABASE ${config.DATABASE_NAME}` |
| ); |
| console.log(' ✓ Database dropped'); |
| } catch (error) { |
| console.log(' ℹ Database does not exist or already deleted'); |
| } |
| } |
| } catch (error) { |
| console.error('✗ Cleanup failed:', error.message); |
| // Don't throw - cleanup is best effort |
| } |
| } |
| |
| /** |
| * Verify schema creation |
| * @param {Object} session - IoTDB session or pool |
| * @param {string} model - 'tree' or 'table' |
| * @param {Object} config - Configuration object |
| * @returns {boolean} True if schema exists |
| */ |
| async function verifySchema(session, model, config) { |
| console.log('\n=== Verifying Schema ==='); |
| |
| try { |
| if (model === 'tree') { |
| // Check if storage group exists |
| const result = await session.executeQueryStatement('SHOW DATABASES'); |
| const databases = []; |
| |
| while (await result.hasNext()) { |
| const row = result.next(); |
| databases.push(row.getFields()[0]); |
| } |
| await result.close(); |
| |
| const exists = databases.some(db => |
| db === config.STORAGE_GROUP_PREFIX || db.startsWith(config.STORAGE_GROUP_PREFIX + '.') |
| ); |
| |
| if (exists) { |
| console.log(` ✓ Storage group ${config.STORAGE_GROUP_PREFIX} exists`); |
| return true; |
| } else { |
| console.log(` ✗ Storage group ${config.STORAGE_GROUP_PREFIX} not found`); |
| return false; |
| } |
| } else if (model === 'table') { |
| // Check if database exists |
| const result = await session.executeQueryStatement('SHOW DATABASES'); |
| const databases = []; |
| |
| while (await result.hasNext()) { |
| const row = result.next(); |
| databases.push(row.getFields()[0]); |
| } |
| await result.close(); |
| |
| const exists = databases.includes(config.DATABASE_NAME); |
| |
| if (exists) { |
| console.log(` ✓ Database ${config.DATABASE_NAME} exists`); |
| return true; |
| } else { |
| console.log(` ✗ Database ${config.DATABASE_NAME} not found`); |
| return false; |
| } |
| } |
| } catch (error) { |
| console.error('✗ Verification failed:', error.message); |
| return false; |
| } |
| |
| return false; |
| } |
| |
| module.exports = { |
| createTreeModelSchema, |
| createTableModelSchema, |
| cleanupSchema, |
| verifySchema, |
| getDataTypeString, |
| }; |