From 608ddcc0850c2504ef5c9a56a79cfaa7734ab5a2 Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Wed, 5 Nov 2025 11:45:42 +0200 Subject: [PATCH 1/5] feat: added dedicated abort controller for all spawned processes --- .../helpers/connectionHelper.js | 114 +++++++++++++++--- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/reverse_engineering/helpers/connectionHelper.js b/reverse_engineering/helpers/connectionHelper.js index 9f91aad..afe575e 100644 --- a/reverse_engineering/helpers/connectionHelper.js +++ b/reverse_engineering/helpers/connectionHelper.js @@ -63,6 +63,8 @@ const MISSING_JAVA_PATH_MESSAGE = let connection; let useSshTunnel; +let abortController; +let activeQueries = new Set(); const isWindows = () => os.platform() === 'win32'; @@ -109,9 +111,36 @@ const buildCommand = (teradataClientPath, connectionInfo) => { return commandArgs; }; -const getDefaultJavaPath = () => { - const javaHome = isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME'; - return javaHome + '/bin/java'; +const getDefaultJavaPath = async () => { + if (isWindows()) { + const winJavaHome = '%JAVA_HOME%'; + const { stdout } = await exec(`echo ${winJavaHome}`, { shell: 'cmd.exe' }); + const expandedPath = stdout.trim(); + + if (!expandedPath || expandedPath === winJavaHome) { + return winJavaHome; + } + + if (expandedPath.endsWith('java.exe')) { + return expandedPath; + } + + return expandedPath.replaceAll('/', '\\'); + } + + const unixJavaHome = '$JAVA_HOME'; + const { stdout } = await exec(`echo ${unixJavaHome}`); + const expandedPath = stdout.trim(); + + if (!expandedPath) { + return unixJavaHome; + } + + if (expandedPath.endsWith('java')) { + return expandedPath; + } + + return expandedPath + '/bin/java'; }; const checkJavaPath = async (javaPath, logger) => { @@ -164,7 +193,7 @@ const formatError = errorLike => { const createConnection = async (connectionInfo, sshService, logger) => { const connectionSettings = await getConnectionSettings(connectionInfo, sshService); - const javaPath = connectionSettings.javaHomePath ? connectionSettings.javaHomePath : getDefaultJavaPath(); + const javaPath = connectionSettings.javaHomePath ? connectionSettings.javaHomePath : await getDefaultJavaPath(); await checkJavaPath(javaPath, logger); @@ -172,8 +201,12 @@ const createConnection = async (connectionInfo, sshService, logger) => { const teradataClientCommandArguments = buildCommand(teradataClientPath, connectionSettings); return { - execute: query => { + execute: (query, signal) => { return new Promise((resolve, reject) => { + if (signal?.aborted) { + return reject(new Error('Query execution was aborted')); + } + const queryArgument = createArgument('query', query); const javaArgs = [...teradataClientCommandArguments, queryArgument]; @@ -181,7 +214,29 @@ const createConnection = async (connectionInfo, sshService, logger) => { shell: true, }); + activeQueries.add(queryResult); + + const abortHandler = () => { + if (queryResult && !queryResult.killed) { + queryResult.kill('SIGTERM'); + activeQueries.delete(queryResult); + } + reject(new Error('Query execution was aborted')); + }; + + if (signal) { + signal.addEventListener('abort', abortHandler); + } + + const cleanup = () => { + activeQueries.delete(queryResult); + if (signal) { + signal.removeEventListener('abort', abortHandler); + } + }; + queryResult.on('error', error => { + cleanup(); reject(new Error(error)); }); @@ -196,6 +251,12 @@ const createConnection = async (connectionInfo, sshService, logger) => { }); queryResult.on('close', code => { + cleanup(); + + if (signal?.aborted) { + return; + } + if (code !== 0) { reject(new Error(Buffer.concat(errorData).toString())); return; @@ -225,6 +286,7 @@ const createConnection = async (connectionInfo, sshService, logger) => { }); }); }, + getAbortController: () => abortController, }; }; @@ -233,6 +295,9 @@ const connect = async (connectionInfo, sshService, logger) => { return connection; } + abortController = new AbortController(); + activeQueries.clear(); + useSshTunnel = connectionInfo.useSshTunnel; connection = await createConnection(connectionInfo, sshService, logger); @@ -246,12 +311,14 @@ const getConcatenatedQueryResult = (queryResult = []) => { }; const createInstance = (connection, _) => { + const signal = connection.getAbortController()?.signal; + const getDatabasesWithTableNames = async tableType => { const query = buildQuery(queryType.GET_DATABASE_AND_TABLE_NAMES, { tableType, systemDatabases: SYSTEM_DATABASES, }); - const queryResult = await connection.execute(query); + const queryResult = await connection.execute(query, signal); return groupBy({ items: queryResult, @@ -261,24 +328,27 @@ const createInstance = (connection, _) => { }; const getCount = async (dbName, tableName) => { - const count = await connection.execute(buildQuery(queryType.COUNT_COLUMNS, { dbName, tableName })); + const count = await connection.execute(buildQuery(queryType.COUNT_COLUMNS, { dbName, tableName }), signal); return Number(count[0]?.Quantity || 0); }; const getRecords = async (dbName, tableName, limit) => { - return connection.execute(buildQuery(queryType.GET_RECORDS, { dbName, tableName, limit })); + return connection.execute(buildQuery(queryType.GET_RECORDS, { dbName, tableName, limit }), signal); }; const getVersion = async () => { - const result = await connection.execute('SELECT * FROM dbc.dbcinfo'); + const result = await connection.execute('SELECT * FROM dbc.dbcinfo', signal); const versionInfo = result.find(info => info.InfoKey === 'VERSION'); return versionInfo?.InfoData; }; const describeDatabase = async dbName => { - const databaseInfoResult = await connection.execute(buildQuery(queryType.DESCRIBE_DATABASE, { dbName })); + const databaseInfoResult = await connection.execute( + buildQuery(queryType.DESCRIBE_DATABASE, { dbName }), + signal, + ); if (!databaseInfoResult.length) { return {}; @@ -303,6 +373,7 @@ const createInstance = (connection, _) => { const showCreateEntity = async (dbName, tableName, entityType) => { const result = await connection.execute( buildQuery(queryType.SHOW_CREATE_ENTITY_STATEMENT, { dbName, tableName, entityType }), + signal, ); return getConcatenatedQueryResult(result); @@ -310,7 +381,7 @@ const createInstance = (connection, _) => { const getColumns = async (dbName, tableName) => { const query = buildQuery(queryType.GET_COLUMNS, { dbName, tableName }); - const result = await connection.execute(query); + const result = await connection.execute(query, signal); return result.map(raw => ({ dbName: raw.DatabaseName, @@ -322,7 +393,7 @@ const createInstance = (connection, _) => { const getCreateIndexStatement = async index => { const query = `SHOW ${index.indexType} INDEX "${index.dbName}"."${index.indxName}";`; - const createStatement = await connection.execute(query); + const createStatement = await connection.execute(query, signal); return { ...index, @@ -332,7 +403,7 @@ const createInstance = (connection, _) => { const getIndexes = async dbName => { const query = buildQuery(queryType.GET_INDEXES, { dbName }); - const queryResult = await connection.execute(query); + const queryResult = await connection.execute(query, signal); const indexes = _.uniqBy(queryResult, 'IndexName').map(index => ({ dbName: index.DatabaseName, @@ -353,7 +424,7 @@ const createInstance = (connection, _) => { const getCreateUdtStatement = async type => { const name = type['Table/View/Macro Dictionary Name']; const query = `SHOW TYPE "${name}";`; - const createStatement = await connection.execute(query); + const createStatement = await connection.execute(query, signal); return { name, @@ -363,7 +434,7 @@ const createInstance = (connection, _) => { const getUserDefinedTypes = async () => { const query = 'HELP DATABASE SYSUDTLIB;'; - const queryResult = await connection.execute(query); + const queryResult = await connection.execute(query, signal); return queryResult .filter(filterUdt) @@ -390,6 +461,19 @@ const createInstance = (connection, _) => { }; const close = async sshService => { + if (abortController) { + abortController.abort(); + abortController = null; + } + + for (const activeQuery of activeQueries) { + if (activeQuery && !activeQuery.killed) { + activeQuery.kill('SIGTERM'); + } + } + + activeQueries.clear(); + if (connection) { connection = null; } From 627a86cf2ad5ca520e492cee3e7769e9f520904d Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Wed, 5 Nov 2025 12:07:02 +0200 Subject: [PATCH 2/5] chore: improved check on static values --- reverse_engineering/helpers/connectionHelper.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/reverse_engineering/helpers/connectionHelper.js b/reverse_engineering/helpers/connectionHelper.js index afe575e..d971692 100644 --- a/reverse_engineering/helpers/connectionHelper.js +++ b/reverse_engineering/helpers/connectionHelper.js @@ -39,7 +39,8 @@ const SYSTEM_DATABASES = [ 'TD_SYSXML', 'TDPUSER', ]; -const SYSTEM_UDT = [ + +const SYSTEM_UDT = new Set([ 'ArrayVec', 'InternalPeriodDateType', 'InternalPeriodTimeStampType', @@ -57,7 +58,8 @@ const SYSTEM_UDT = [ 'TD_JSON_BSON', 'TD_JSON_UBJSON', 'XML', -]; +]); + const MISSING_JAVA_PATH_MESSAGE = 'Path to JAVA binary file is incorrect. Please specify JAVA_HOME variable in your system or put specific path to JAVA binary file in connection settings.'; @@ -528,7 +530,7 @@ const getIndexType = index => { const filterUdt = object => object.Kind === 'U'; -const excludeSystemUdt = type => !SYSTEM_UDT.includes(type['Table/View/Macro Dictionary Name']); +const excludeSystemUdt = type => !SYSTEM_UDT.has(type['Table/View/Macro Dictionary Name']); module.exports = { connect, From a8ed2e9e0bab72356e0f010588b511a2b3d7378a Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Wed, 5 Nov 2025 13:56:11 +0200 Subject: [PATCH 3/5] feat: made the Host field required --- .../connectionSettingsModalConfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json b/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json index d4303c7..62706df 100644 --- a/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json +++ b/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json @@ -15,7 +15,9 @@ "inputType": "text", "inputPlaceholder": "host", "inputTooltip": "Specify host IP address or DNS name of Teradata server", - "regex": "([^\\s])" + "validation": { + "regex": "([^\\s])" + } }, { "inputLabel": "Port", From 1f0b4db36db4e934a54247bf2b21db6daf3ed84c Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Wed, 5 Nov 2025 14:48:56 +0200 Subject: [PATCH 4/5] fix: reverted previous simple implementation --- .../helpers/connectionHelper.js | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/reverse_engineering/helpers/connectionHelper.js b/reverse_engineering/helpers/connectionHelper.js index d971692..78cbe94 100644 --- a/reverse_engineering/helpers/connectionHelper.js +++ b/reverse_engineering/helpers/connectionHelper.js @@ -114,35 +114,8 @@ const buildCommand = (teradataClientPath, connectionInfo) => { }; const getDefaultJavaPath = async () => { - if (isWindows()) { - const winJavaHome = '%JAVA_HOME%'; - const { stdout } = await exec(`echo ${winJavaHome}`, { shell: 'cmd.exe' }); - const expandedPath = stdout.trim(); - - if (!expandedPath || expandedPath === winJavaHome) { - return winJavaHome; - } - - if (expandedPath.endsWith('java.exe')) { - return expandedPath; - } - - return expandedPath.replaceAll('/', '\\'); - } - - const unixJavaHome = '$JAVA_HOME'; - const { stdout } = await exec(`echo ${unixJavaHome}`); - const expandedPath = stdout.trim(); - - if (!expandedPath) { - return unixJavaHome; - } - - if (expandedPath.endsWith('java')) { - return expandedPath; - } - - return expandedPath + '/bin/java'; + const javaHome = isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME'; + return javaHome + '/bin/java'; }; const checkJavaPath = async (javaPath, logger) => { From 121dadf6ad6155713daaf5eec5e2fdcd4c5869c5 Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Wed, 5 Nov 2025 15:04:04 +0200 Subject: [PATCH 5/5] chore: remarks --- reverse_engineering/helpers/connectionHelper.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reverse_engineering/helpers/connectionHelper.js b/reverse_engineering/helpers/connectionHelper.js index 78cbe94..49c63ed 100644 --- a/reverse_engineering/helpers/connectionHelper.js +++ b/reverse_engineering/helpers/connectionHelper.js @@ -113,7 +113,7 @@ const buildCommand = (teradataClientPath, connectionInfo) => { return commandArgs; }; -const getDefaultJavaPath = async () => { +const getDefaultJavaPath = () => { const javaHome = isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME'; return javaHome + '/bin/java'; }; @@ -168,7 +168,7 @@ const formatError = errorLike => { const createConnection = async (connectionInfo, sshService, logger) => { const connectionSettings = await getConnectionSettings(connectionInfo, sshService); - const javaPath = connectionSettings.javaHomePath ? connectionSettings.javaHomePath : await getDefaultJavaPath(); + const javaPath = connectionSettings.javaHomePath ? connectionSettings.javaHomePath : getDefaultJavaPath(); await checkJavaPath(javaPath, logger); @@ -192,7 +192,7 @@ const createConnection = async (connectionInfo, sshService, logger) => { activeQueries.add(queryResult); const abortHandler = () => { - if (queryResult && !queryResult.killed) { + if (!queryResult?.killed) { queryResult.kill('SIGTERM'); activeQueries.delete(queryResult); }