From 769f782d9fe6858b7d5405e94d7fbfe25f33131b Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Fri, 9 Jan 2026 19:19:57 +0200 Subject: [PATCH 1/6] feat: added "Apply Script" feature --- forward_engineering/api.js | 10 +-- forward_engineering/api/applyToInstance.js | 23 ++++++ forward_engineering/config.json | 2 +- reverse_engineering/api.js | 29 +------- .../Db2Client/src/main/java/org/db2/App.java | 27 ++++++- .../src/main/java/org/db2/Db2Service.java | 69 +++++++++++++++++- shared/addons/Db2Client.jar | Bin 4300990 -> 4302622 bytes shared/api/testConnection.js | 31 ++++++++ shared/helpers/connectionHelper.js | 10 ++- shared/helpers/instanceHelper.js | 7 ++ 10 files changed, 165 insertions(+), 43 deletions(-) create mode 100644 forward_engineering/api/applyToInstance.js create mode 100644 shared/api/testConnection.js diff --git a/forward_engineering/api.js b/forward_engineering/api.js index 178d5d7..20f1620 100644 --- a/forward_engineering/api.js +++ b/forward_engineering/api.js @@ -1,5 +1,7 @@ const { generateContainerScript } = require('./api/generateContainerScript'); const { isDropInStatements } = require('./api/isDropInStatements'); +const { testConnection } = require('../shared/api/testConnection'); +const { applyToInstance } = require('./api/applyToInstance'); module.exports = { generateScript(data, logger, callback, app) { @@ -16,13 +18,9 @@ module.exports = { throw new Error('Not implemented'); }, - applyToInstance(connectionInfo, logger, callback, app) { - throw new Error('Not implemented'); - }, + applyToInstance, - testConnection(connectionInfo, logger, callback, app) { - throw new Error('Not implemented'); - }, + testConnection, isDropInStatements, }; diff --git a/forward_engineering/api/applyToInstance.js b/forward_engineering/api/applyToInstance.js new file mode 100644 index 0000000..a6af64b --- /dev/null +++ b/forward_engineering/api/applyToInstance.js @@ -0,0 +1,23 @@ +const { logHelper } = require('../../shared/helpers/logHelper'); +const { connectionHelper } = require('../../shared/helpers/connectionHelper'); +const { instanceHelper } = require('../../shared/helpers/instanceHelper'); + +async function applyToInstance(connectionInfo, logger, callback, app) { + const applyToInstanceLogger = logHelper.createLogger({ + title: 'Apply to instance', + hiddenKeys: connectionInfo.hiddenKeys, + logger, + }); + + try { + const connection = await connectionHelper.connect({ connectionInfo, logger: applyToInstanceLogger }); + await instanceHelper.executeQuery({ connection, query: connectionInfo.script }); + + callback(); + } catch (err) { + applyToInstanceLogger.error(err); + callback(err); + } +} + +module.exports = { applyToInstance }; diff --git a/forward_engineering/config.json b/forward_engineering/config.json index e213887..aabbeec 100644 --- a/forward_engineering/config.json +++ b/forward_engineering/config.json @@ -9,7 +9,7 @@ } ], "hasUpdateScript": false, - "applyScriptToInstance": false, + "applyScriptToInstance": true, "combinedContainers": true, "feLevelSelector": { "container": true, diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index cfa704e..003f62d 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -13,6 +13,7 @@ const { instanceHelper } = require('../shared/helpers/instanceHelper'); const { logHelper } = require('../shared/helpers/logHelper'); const { TABLE_TYPE } = require('../constants/constants'); const { nameHelper } = require('../shared/helpers/nameHelper'); +const { testConnection } = require('../shared/api/testConnection'); /** * @param {ConnectionInfo} connectionInfo @@ -35,34 +36,6 @@ const disconnect = async (connectionInfo, appLogger, callback) => { } }; -/** - * @param {ConnectionInfo} connectionInfo - * @param {AppLogger} appLogger - * @param {Callback} callback - * @param {App} app - */ -const testConnection = async (connectionInfo, appLogger, callback, app) => { - const logger = logHelper.createLogger({ - title: 'Test database connection', - hiddenKeys: connectionInfo.hiddenKeys, - logger: appLogger, - }); - - try { - logger.info(connectionInfo); - - const connection = await connectionHelper.connect({ connectionInfo, logger }); - const version = await instanceHelper.getDbVersion({ connection }); - await connectionHelper.disconnect(); - - logger.info('Db version: ' + version); - callback(); - } catch (error) { - logger.error(error); - callback(error); - } -}; - /** * @param {ConnectionInfo} connectionInfo * @param {AppLogger} appLogger diff --git a/shared/Db2Client/src/main/java/org/db2/App.java b/shared/Db2Client/src/main/java/org/db2/App.java index b08abfc..764e5f8 100644 --- a/shared/Db2Client/src/main/java/org/db2/App.java +++ b/shared/Db2Client/src/main/java/org/db2/App.java @@ -30,7 +30,7 @@ public static void main(String[] args) { int queryResult = db2Service.executeCallableQuery(query, inParam); result.put("data", queryResult); } else { - JSONArray queryResult = db2Service.executeQuery(query); + Object queryResult = db2Service.execute(query); result.put("data", queryResult); } } catch (SQLException e) { @@ -47,15 +47,36 @@ public static void main(String[] args) { } private static String cleanStringValue(String value) { - return value.replace("__PERCENT__", "%"); + value = value.replace("__PERCENT__", "%"); + + // Check if the value is base64 encoded (query arguments are base64 encoded to preserve quotes) + if (value.length() > 20 && value.matches("^[A-Za-z0-9+/=]+$")) { + try { + // preserve quotes and special characters + byte[] decodedBytes = java.util.Base64.getDecoder().decode(value); + value = new String(decodedBytes, java.nio.charset.StandardCharsets.UTF_8); + } catch (Exception _) { + // use the original value for backward compatibility, + // handles cases where the value isn't actually base64 encoded + } + } + + value = value.replace("\\\"", "\""); + return value; } private static String findArgument(String[] args, Argument argument) { - return Arrays.stream(args) + String value = Arrays.stream(args) .filter(arg -> arg.startsWith(argument.getPrefix())) .map(arg -> arg.substring(argument.getStartValueIndex())) .findFirst() .orElse(""); + + if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1, value.length() - 1); + } + + return value; } private static void print(String value) { diff --git a/shared/Db2Client/src/main/java/org/db2/Db2Service.java b/shared/Db2Client/src/main/java/org/db2/Db2Service.java index 94ab1cd..52b3789 100644 --- a/shared/Db2Client/src/main/java/org/db2/Db2Service.java +++ b/shared/Db2Client/src/main/java/org/db2/Db2Service.java @@ -30,6 +30,67 @@ public JSONArray executeQuery(String query) throws SQLException { return mapper.convertToJson(response); } + public Object execute(String query) throws SQLException { + this.statement = connection.createStatement(); + String[] statements = splitStatements(query); + + java.util.ArrayList selectStatements = new java.util.ArrayList<>(); + java.util.ArrayList ddlDmlStatements = new java.util.ArrayList<>(); + + for (String sqlStatement : statements) { + sqlStatement = sqlStatement.trim(); + if (sqlStatement.isEmpty()) { + continue; + } + + String upperStatement = sqlStatement.toUpperCase().trim(); + if (upperStatement.startsWith("SELECT") || upperStatement.startsWith("WITH")) { + selectStatements.add(sqlStatement); + } else { + ddlDmlStatements.add(sqlStatement); + } + } + + Object lastResult = null; + + for (String sqlStatement : ddlDmlStatements) { + lastResult = executeStatement(sqlStatement); + } + + for (String sqlStatement : selectStatements) { + lastResult = executeStatement(sqlStatement); + } + + return lastResult != null ? lastResult : 0; + } + + private Object executeStatement(String sqlStatement) throws SQLException { + boolean hasResultSet = statement.execute(sqlStatement); + if (hasResultSet) { + this.response = statement.getResultSet(); + Object result = mapper.convertToJson(response); + if (this.response != null) { + this.response.close(); + this.response = null; + } + return result; + } else { + return statement.getUpdateCount(); + } + } + + private String[] splitStatements(String query) { + String[] parts = query.trim().split(";\\s+", -1); + java.util.ArrayList statements = new java.util.ArrayList<>(); + for (String part : parts) { + part = part.trim(); + if (!part.isEmpty()) { + statements.add(part); + } + } + return statements.toArray(new String[0]); + } + public int executeCallableQuery(String query, String inParam) throws SQLException { this.callableStatement = connection.prepareCall(query); @@ -56,25 +117,25 @@ public void closeConnection() { if (response != null) { try { response.close(); - } catch (SQLException e) { + } catch (SQLException _) { /* Ignored */} } if (statement != null) { try { statement.close(); - } catch (SQLException e) { + } catch (SQLException _) { /* Ignored */} } if (callableStatement != null) { try { callableStatement.close(); - } catch (SQLException e) { + } catch (SQLException _) { /* Ignored */} } if (connection != null) { try { connection.close(); - } catch (SQLException e) { + } catch (SQLException _) { /* Ignored */} } } diff --git a/shared/addons/Db2Client.jar b/shared/addons/Db2Client.jar index 55670cb5be0343208af38172eae11fa14d859fd2..87fe0591f2ed050c24c87d6cfec9450b259643dd 100644 GIT binary patch delta 8321 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 F{{#Qqlx6?` delta 6688 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 iL|WvZzW#4y&=I`S0W9<>X?M0Z~hMz19!Rr diff --git a/shared/api/testConnection.js b/shared/api/testConnection.js new file mode 100644 index 0000000..b8c4d64 --- /dev/null +++ b/shared/api/testConnection.js @@ -0,0 +1,31 @@ +const { logHelper } = require('../helpers/logHelper'); +const { connectionHelper } = require('../helpers/connectionHelper'); +const { instanceHelper } = require('../helpers/instanceHelper'); + +/** + * @param {ConnectionInfo} connectionInfo + * @param {AppLogger} appLogger + * @param {Callback} callback + * @param {App} app + */ +const testConnection = async (connectionInfo, appLogger, callback, app) => { + const logger = logHelper.createLogger({ + title: 'Test database connection', + hiddenKeys: connectionInfo.hiddenKeys, + logger: appLogger, + }); + + try { + const connection = await connectionHelper.connect({ connectionInfo, logger }); + const version = await instanceHelper.getDbVersion({ connection }); + await connectionHelper.disconnect(); + + logger.info('Db version: ' + version); + callback(); + } catch (error) { + logger.error(error); + callback(error); + } +}; + +module.exports = { testConnection }; diff --git a/shared/helpers/connectionHelper.js b/shared/helpers/connectionHelper.js index e6f6d3b..811391f 100644 --- a/shared/helpers/connectionHelper.js +++ b/shared/helpers/connectionHelper.js @@ -26,7 +26,15 @@ const isWindows = () => os.platform() === 'win32'; * @param {string | number} argValue * @returns {string} */ -const createArgument = (argKey, argValue) => ` --${argKey}="${argValue}"`; +const createArgument = (argKey, argValue) => { + // base64 encode to preserve quotes and special characters + // to avoid shell interpretation issues in command line args + if (argKey === 'query') { + const encoded = Buffer.from(String(argValue), 'utf8').toString('base64'); + return ` --${argKey}="${encoded}"`; + } + return ` --${argKey}="${argValue}"`; +}; /** * @param {{ [argKey: string]: string }} queryData diff --git a/shared/helpers/instanceHelper.js b/shared/helpers/instanceHelper.js index 0b89a27..1e50df0 100644 --- a/shared/helpers/instanceHelper.js +++ b/shared/helpers/instanceHelper.js @@ -91,12 +91,19 @@ const getTableDdl = async ({ connection, schemaName, tableName, tableType, logge } }; +/** + * @param {{ connection: Connection }} + * @returns {Promise} + */ +const executeQuery = async ({ connection, query }) => await connection.execute({ query }); + const instanceHelper = { getDbVersion, getSchemaNames, getSchemaProperties, getDatabasesWithTableNames, getTableDdl, + executeQuery, }; module.exports = { From 53063578049ce9f174f70365d6ae9b64b3250f95 Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Tue, 13 Jan 2026 12:30:41 +0200 Subject: [PATCH 2/6] feat: refactored the approach of passing the connection info and query to java driver --- forward_engineering/api/applyToInstance.js | 2 +- .../Db2Client/src/main/java/org/db2/App.java | 81 ++++------ .../src/main/java/org/db2/Argument.java | 28 ---- .../src/main/java/org/db2/Db2Service.java | 129 +++++++++------- shared/addons/Db2Client.jar | Bin 4302622 -> 4301113 bytes shared/helpers/connectionHelper.js | 63 +++----- shared/helpers/instanceHelper.js | 4 +- shared/types.d.ts | 144 ++++++++++-------- 8 files changed, 211 insertions(+), 240 deletions(-) delete mode 100644 shared/Db2Client/src/main/java/org/db2/Argument.java diff --git a/forward_engineering/api/applyToInstance.js b/forward_engineering/api/applyToInstance.js index a6af64b..1df0c53 100644 --- a/forward_engineering/api/applyToInstance.js +++ b/forward_engineering/api/applyToInstance.js @@ -11,7 +11,7 @@ async function applyToInstance(connectionInfo, logger, callback, app) { try { const connection = await connectionHelper.connect({ connectionInfo, logger: applyToInstanceLogger }); - await instanceHelper.executeQuery({ connection, query: connectionInfo.script }); + await instanceHelper.executeQuery({ connection, query: connectionInfo.script, ddl: true }); callback(); } catch (err) { diff --git a/shared/Db2Client/src/main/java/org/db2/App.java b/shared/Db2Client/src/main/java/org/db2/App.java index 764e5f8..74390cc 100644 --- a/shared/Db2Client/src/main/java/org/db2/App.java +++ b/shared/Db2Client/src/main/java/org/db2/App.java @@ -1,82 +1,59 @@ package org.db2; -import org.json.JSONArray; import org.json.JSONObject; -import java.sql.SQLException; -import java.util.Arrays; +import java.util.Scanner; public class App { public static void main(String[] args) { - String host = findArgument(args, Argument.HOST); - String port = findArgument(args, Argument.PORT); - String database = findArgument(args, Argument.DATABASE); - String user = findArgument(args, Argument.USER); - String password = findArgument(args, Argument.PASSWORD); - String query = cleanStringValue(findArgument(args, Argument.QUERY)); - String callable = findArgument(args, Argument.CALLABLE); - String inParam = findArgument(args, Argument.IN_PARAM); - - Db2Service db2Service = new Db2Service(host, port, database, user, password, new ResponseMapper()); - JSONObject result = new JSONObject(); + String query = ""; + Db2Service db2Service = null; try { + String jsonInput = readStdin(); + JSONObject input = new JSONObject(jsonInput); + + String host = input.optString("host", ""); + String port = input.optString("port", ""); + String database = input.optString("database", ""); + String user = input.optString("user", ""); + String password = input.optString("password", ""); + query = input.optString("query", ""); + boolean callable = input.optBoolean("callable", false); + String inParam = input.optString("inParam", ""); + boolean ddl = input.optBoolean("ddl", false); + + db2Service = new Db2Service(host, port, database, user, password, new ResponseMapper()); db2Service.openConnection(); - boolean isCallableQuery = Boolean.parseBoolean(callable); - - if (isCallableQuery) { + if (callable) { int queryResult = db2Service.executeCallableQuery(query, inParam); result.put("data", queryResult); + } else if (ddl) { + int queryResult = db2Service.applyScript(query); + result.put("data", queryResult); } else { - Object queryResult = db2Service.execute(query); + org.json.JSONArray queryResult = db2Service.executeQuery(query); result.put("data", queryResult); } - } catch (SQLException e) { + } catch (Exception e) { JSONObject errorObj = new JSONObject(); errorObj.put("message", e.getMessage()); errorObj.put("stack", e.getStackTrace()); errorObj.put("query", query); - result.put("error", errorObj); } finally { - db2Service.closeConnection(); - print(result.toString()); - } - } - - private static String cleanStringValue(String value) { - value = value.replace("__PERCENT__", "%"); - - // Check if the value is base64 encoded (query arguments are base64 encoded to preserve quotes) - if (value.length() > 20 && value.matches("^[A-Za-z0-9+/=]+$")) { - try { - // preserve quotes and special characters - byte[] decodedBytes = java.util.Base64.getDecoder().decode(value); - value = new String(decodedBytes, java.nio.charset.StandardCharsets.UTF_8); - } catch (Exception _) { - // use the original value for backward compatibility, - // handles cases where the value isn't actually base64 encoded + if (db2Service != null) { + db2Service.closeConnection(); } + print(result.toString()); } - - value = value.replace("\\\"", "\""); - return value; } - private static String findArgument(String[] args, Argument argument) { - String value = Arrays.stream(args) - .filter(arg -> arg.startsWith(argument.getPrefix())) - .map(arg -> arg.substring(argument.getStartValueIndex())) - .findFirst() - .orElse(""); - - if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) { - value = value.substring(1, value.length() - 1); - } - - return value; + private static String readStdin() { + Scanner scanner = new Scanner(System.in).useDelimiter("\\A"); + return scanner.hasNext() ? scanner.next() : "{}"; } private static void print(String value) { diff --git a/shared/Db2Client/src/main/java/org/db2/Argument.java b/shared/Db2Client/src/main/java/org/db2/Argument.java deleted file mode 100644 index 1e72dd7..0000000 --- a/shared/Db2Client/src/main/java/org/db2/Argument.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.db2; - -public enum Argument { - HOST("--host", 7), - USER("--user", 7), - PASSWORD("--pass", 7), - PORT("--port", 7), - QUERY("--query", 8), - DATABASE("--database", 11), - CALLABLE("--callable", 11), - IN_PARAM("--inparam", 10); - - private final String argPrefix; - private final int startValueIndex; - - Argument(String argPrefix, int startValueIndex) { - this.argPrefix = argPrefix; - this.startValueIndex = startValueIndex; - } - - public String getPrefix() { - return this.argPrefix; - } - - public int getStartValueIndex() { - return this.startValueIndex; - } -} diff --git a/shared/Db2Client/src/main/java/org/db2/Db2Service.java b/shared/Db2Client/src/main/java/org/db2/Db2Service.java index 52b3789..a8ed215 100644 --- a/shared/Db2Client/src/main/java/org/db2/Db2Service.java +++ b/shared/Db2Client/src/main/java/org/db2/Db2Service.java @@ -30,53 +30,45 @@ public JSONArray executeQuery(String query) throws SQLException { return mapper.convertToJson(response); } - public Object execute(String query) throws SQLException { - this.statement = connection.createStatement(); - String[] statements = splitStatements(query); + public int applyScript(String script) throws SQLException { + String[] statements = splitStatements(script); + int totalUpdateCount = 0; - java.util.ArrayList selectStatements = new java.util.ArrayList<>(); - java.util.ArrayList ddlDmlStatements = new java.util.ArrayList<>(); + for (String statement : statements) { + statement = statement.trim(); - for (String sqlStatement : statements) { - sqlStatement = sqlStatement.trim(); - if (sqlStatement.isEmpty()) { + if (statement.isEmpty()) { continue; } - String upperStatement = sqlStatement.toUpperCase().trim(); - if (upperStatement.startsWith("SELECT") || upperStatement.startsWith("WITH")) { - selectStatements.add(sqlStatement); - } else { - ddlDmlStatements.add(sqlStatement); - } - } - - Object lastResult = null; + Statement statementInstance = connection.createStatement(); - for (String sqlStatement : ddlDmlStatements) { - lastResult = executeStatement(sqlStatement); - } - - for (String sqlStatement : selectStatements) { - lastResult = executeStatement(sqlStatement); - } - - return lastResult != null ? lastResult : 0; - } - - private Object executeStatement(String sqlStatement) throws SQLException { - boolean hasResultSet = statement.execute(sqlStatement); - if (hasResultSet) { - this.response = statement.getResultSet(); - Object result = mapper.convertToJson(response); - if (this.response != null) { - this.response.close(); - this.response = null; + try { + statementInstance.execute(statement); + totalUpdateCount += statementInstance.getUpdateCount(); + } catch (SQLException e) { + int reorgPendingErrorCode = -668; + + if (e.getErrorCode() == reorgPendingErrorCode) { + String tableName = extractTableNameFromError(e.getMessage()); + if (tableName != null) { + reorganizeTable(tableName, statementInstance); + + // retry + statementInstance.execute(statement); + totalUpdateCount += statementInstance.getUpdateCount(); + } else { + throw e; + } + } else { + throw e; + } + } finally { + statementInstance.close(); } - return result; - } else { - return statement.getUpdateCount(); } + + return totalUpdateCount; } private String[] splitStatements(String query) { @@ -91,6 +83,37 @@ private String[] splitStatements(String query) { return statements.toArray(new String[0]); } + private void reorganizeTable(String tableName, Statement stmt) throws SQLException { + // Use ADMIN_CMD to execute REORG TABLE command + // Escape single quotes in table name for the command string + String escapedTableName = tableName.replace("'", "''"); + String reorgSql = "CALL SYSPROC.ADMIN_CMD('REORG TABLE " + escapedTableName + "')"; + stmt.execute(reorgSql); + if (!connection.getAutoCommit()) { + connection.commit(); + } + } + + + private String extractTableNameFromError(String errorMessage) { + // Extract table name from error message like: SQLERRMC=7;db1.table2 + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("SQLERRMC=\\d+;([^,;\\s]+)"); + java.util.regex.Matcher matcher = pattern.matcher(errorMessage); + if (matcher.find()) { + String tableName = matcher.group(1).trim(); + // Quote the table name properly for REORG statement + // If it contains a dot, split into schema.table and quote both parts + if (tableName.contains(".")) { + String[] parts = tableName.split("\\.", 2); + if (parts.length == 2) { + return "\"" + parts[0] + "\".\"" + parts[1] + "\""; + } + } + return "\"" + tableName + "\""; + } + return null; + } + public int executeCallableQuery(String query, String inParam) throws SQLException { this.callableStatement = connection.prepareCall(query); @@ -114,29 +137,33 @@ public void openConnection() throws SQLException { } public void closeConnection() { - if (response != null) { + if (this.response != null) { try { - response.close(); + this.response.close(); } catch (SQLException _) { - /* Ignored */} + /* Ignored */ + } } - if (statement != null) { + if (this.statement != null) { try { - statement.close(); + this.statement.close(); } catch (SQLException _) { - /* Ignored */} + /* Ignored */ + } } - if (callableStatement != null) { + if (this.callableStatement != null) { try { - callableStatement.close(); + this.callableStatement.close(); } catch (SQLException _) { - /* Ignored */} + /* Ignored */ + } } - if (connection != null) { + if (this.connection != null) { try { - connection.close(); + this.connection.close(); } catch (SQLException _) { - /* Ignored */} + /* Ignored */ + } } } diff --git a/shared/addons/Db2Client.jar b/shared/addons/Db2Client.jar index 87fe0591f2ed050c24c87d6cfec9450b259643dd..01d4a12232d0e19476cebba50ad94e7190745e63 100644 GIT binary patch delta 5622 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 delta 7195 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 os.platform() === 'win32'; /** - * @param {string} argKey - * @param {string | number} argValue - * @returns {string} - */ -const createArgument = (argKey, argValue) => { - // base64 encode to preserve quotes and special characters - // to avoid shell interpretation issues in command line args - if (argKey === 'query') { - const encoded = Buffer.from(String(argValue), 'utf8').toString('base64'); - return ` --${argKey}="${encoded}"`; - } - return ` --${argKey}="${argValue}"`; -}; - -/** - * @param {{ [argKey: string]: string }} queryData + * @param {{ clientPath: string }} * @returns {string[]} */ -const getQueryArguments = queryData => { - return Object.entries(queryData).reduce((result, [argKey, argValue]) => { - return [...result, createArgument(argKey, argValue)]; - }, []); -}; - -/** - * @param {{ clientPath: string, connectionInfo: ConnectionInfo }} - * @returns {string[]} - */ -const buildCommand = ({ clientPath, connectionInfo }) => { - let commandArgs = ['-jar', clientPath]; - - connectionInfo.host && commandArgs.push(createArgument('host', connectionInfo.host)); - connectionInfo.port && commandArgs.push(createArgument('port', connectionInfo.port)); - connectionInfo.database && commandArgs.push(createArgument('database', connectionInfo.database)); - connectionInfo.userName && commandArgs.push(createArgument('user', connectionInfo.userName)); - connectionInfo.userPassword && commandArgs.push(createArgument('pass', connectionInfo.userPassword)); - - return commandArgs; +const buildCommand = ({ clientPath }) => { + return ['-jar', clientPath]; }; /** @@ -97,13 +64,12 @@ const createConnection = async ({ connectionInfo, logger }) => { // If you need to change this clientPath, please ensure that your changes work in the packaged plugin const clientPath = path.resolve(__dirname, '..', 'addons', 'Db2Client.jar'); - const clientCommandArguments = buildCommand({ clientPath, connectionInfo }); + const clientCommandArguments = buildCommand({ clientPath }); return { execute: queryData => { return new Promise((resolve, reject) => { - const queryArguments = getQueryArguments(queryData); - const queryResult = spawn(`"${javaPath}"`, [...clientCommandArguments, ...queryArguments], { + const queryResult = spawn(`"${javaPath}"`, clientCommandArguments, { shell: true, }); @@ -121,6 +87,25 @@ const createConnection = async ({ connectionInfo, logger }) => { resultData.push(data); }); + const inputJson = JSON.stringify({ + host: connectionInfo.host || '', + port: connectionInfo.port || '', + database: connectionInfo.database || '', + user: connectionInfo.userName || '', + password: connectionInfo.userPassword || '', + query: queryData.query || '', + callable: queryData.callable || false, + inParam: queryData.inparam ? String(queryData.inparam) : '', + ddl: queryData.ddl || false, + }); + + queryResult.stdin.on('error', error => { + reject(error); + }); + + queryResult.stdin.write(inputJson, 'utf8'); + queryResult.stdin.end(); + queryResult.on('close', code => { if (code !== 0) { reject(new Error(Buffer.concat(errorData).toString())); diff --git a/shared/helpers/instanceHelper.js b/shared/helpers/instanceHelper.js index 1e50df0..995b8db 100644 --- a/shared/helpers/instanceHelper.js +++ b/shared/helpers/instanceHelper.js @@ -92,10 +92,10 @@ const getTableDdl = async ({ connection, schemaName, tableName, tableType, logge }; /** - * @param {{ connection: Connection }} + * @param {{ connection: Connection, query: string, ddl?: boolean }} * @returns {Promise} */ -const executeQuery = async ({ connection, query }) => await connection.execute({ query }); +const executeQuery = async ({ connection, query, ddl = false }) => await connection.execute({ query, ddl }); const instanceHelper = { getDbVersion, diff --git a/shared/types.d.ts b/shared/types.d.ts index c593458..cdf87c1 100644 --- a/shared/types.d.ts +++ b/shared/types.d.ts @@ -5,113 +5,123 @@ type FilePath = string; type AppTarget = 'Db2'; type App = { - require: (packageName: string) => any; + require: (packageName: string) => any; }; type AppLogger = { - log: (logType: string, logData: { message: string }, title: string, hiddenKeys: string[]) => void; + log: (logType: string, logData: { message: string }, title: string, hiddenKeys: string[]) => void; }; type Pagination = { - enabled: boolean; - value: number; + enabled: boolean; + value: number; }; type RecordSamplingType = 'relative' | 'absolute'; type RecordSamplingSettings = { - [key: RecordSamplingType]: { - value: number; - }; - active: RecordSamplingType; - maxValue: number; + [key: RecordSamplingType]: { + value: number; + }; + active: RecordSamplingType; + maxValue: number; }; enum AuthTypeEnum { - usernamePassword = 'username_password', + usernamePassword = 'username_password', } type AuthType = `${AuthTypeEnum}`; type ConnectionInfo = { - name: string; - host: string; - authType: AuthType; - port: number; - userName: string; - userPassword: string; - database: string; - target: AppTarget; - id: UUID; - appVersion: string; - tempFolder: FilePath; - pluginVersion?: string; - includeSystemCollection: boolean; - includeEmptyCollection: boolean; - pagination: Pagination; - recordSamplingSettings: RecordSamplingSettings; - queryRequestTimeout: number; - applyToInstanceQueryRequestTimeout: number; - activeProxyPool: string[]; - hiddenKeys: string[]; - options: any; + name: string; + host: string; + authType: AuthType; + port: number; + userName: string; + userPassword: string; + database: string; + target: AppTarget; + id: UUID; + appVersion: string; + tempFolder: FilePath; + pluginVersion?: string; + includeSystemCollection: boolean; + includeEmptyCollection: boolean; + pagination: Pagination; + recordSamplingSettings: RecordSamplingSettings; + queryRequestTimeout: number; + applyToInstanceQueryRequestTimeout: number; + activeProxyPool: string[]; + hiddenKeys: string[]; + options: any; }; type Logger = { - error: (error: Error) => void; - info: (message: string) => void; - progress: (message: string, containerName: string, entityName: string) => void; + error: (error: Error) => void; + info: (message: string) => void; + progress: (message: string, containerName: string, entityName: string) => void; }; type Callback = (error: Error, result: any[], info?: { version?: string }, relationships?: any[]) => void; type NameMap = { - [key: string]: NameMap | string[]; + [key: string]: NameMap | string[]; }; type BucketCollectionNamesData = { - dbName: string; - scopeName?: string; - dbCollections?: string[]; - status?: string; - disabledTooltip?: string; + dbName: string; + scopeName?: string; + dbCollections?: string[]; + status?: string; + disabledTooltip?: string; }; type Document = { - [key: string]: any; + [key: string]: any; }; type DbCollectionData = { - dbName: string; - collectionName: string; - documentKind: string; - standardDoc: object; - collectionDocs: object; - bucketInfo: object; - emptyBucket: boolean; - indexes: object[]; - documents: Document[]; - entityLevel: object; + dbName: string; + collectionName: string; + documentKind: string; + standardDoc: object; + collectionDocs: object; + bucketInfo: object; + emptyBucket: boolean; + indexes: object[]; + documents: Document[]; + entityLevel: object; }; type Connection = { - execute: ({ query, callable, inparam }: { query: string, callable?: boolean, inparam?: number }) => Promise; + execute: ({ + query, + callable, + inparam, + ddl, + }: { + query: string; + callable?: boolean; + inparam?: number; + ddl?: boolean; + }) => Promise; }; export { - App, - AppLogger, - AppTarget, - BucketCollectionNamesData, - Callback, - Connection, - ConnectionInfo, - DbCollectionData, - Document, - FilePath, - NameMap, - Logger, - Pagination, - RecordSamplingSettings, - UUID, + App, + AppLogger, + AppTarget, + BucketCollectionNamesData, + Callback, + Connection, + ConnectionInfo, + DbCollectionData, + Document, + FilePath, + NameMap, + Logger, + Pagination, + RecordSamplingSettings, + UUID, }; From e75f26179603a06a9142bb6dc069972dc34b5a43 Mon Sep 17 00:00:00 2001 From: chulanovskyi-bs <56116665+chulanovskyi-bs@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:45:09 +0000 Subject: [PATCH 3/6] Update shared/helpers/connectionHelper.js Co-authored-by: Vitalii Bedletskyi <70570504+VitaliiBedletskyi@users.noreply.github.com> --- shared/helpers/connectionHelper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/helpers/connectionHelper.js b/shared/helpers/connectionHelper.js index c9fcbbd..8d8e013 100644 --- a/shared/helpers/connectionHelper.js +++ b/shared/helpers/connectionHelper.js @@ -71,6 +71,7 @@ const createConnection = async ({ connectionInfo, logger }) => { return new Promise((resolve, reject) => { const queryResult = spawn(`"${javaPath}"`, clientCommandArguments, { shell: true, + stdio: 'pipe', }); queryResult.on('error', error => { From b3ddd13a4bb3ef3179a99ae8449187f95a065920 Mon Sep 17 00:00:00 2001 From: chulanovskyi-bs <56116665+chulanovskyi-bs@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:48:51 +0000 Subject: [PATCH 4/6] Update shared/Db2Client/src/main/java/org/db2/App.java Co-authored-by: Vitalii Bedletskyi <70570504+VitaliiBedletskyi@users.noreply.github.com> --- shared/Db2Client/src/main/java/org/db2/App.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shared/Db2Client/src/main/java/org/db2/App.java b/shared/Db2Client/src/main/java/org/db2/App.java index 74390cc..664a135 100644 --- a/shared/Db2Client/src/main/java/org/db2/App.java +++ b/shared/Db2Client/src/main/java/org/db2/App.java @@ -51,10 +51,12 @@ public static void main(String[] args) { } } - private static String readStdin() { - Scanner scanner = new Scanner(System.in).useDelimiter("\\A"); - return scanner.hasNext() ? scanner.next() : "{}"; - } + private static String readStdin() throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + String result = reader.lines().collect(Collectors.joining("\n")); + return result.isEmpty() ? "{}" : result; + } + } private static void print(String value) { System.out.println(String.format("%s", value)); From 62fd19058556a6fe672ce0a9ae34d2d7feb62cd4 Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Tue, 13 Jan 2026 14:53:10 +0200 Subject: [PATCH 5/6] fix: imports --- shared/Db2Client/src/main/java/org/db2/App.java | 3 ++- shared/Db2Client/src/main/java/org/db2/Db2Service.java | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/shared/Db2Client/src/main/java/org/db2/App.java b/shared/Db2Client/src/main/java/org/db2/App.java index 664a135..c230f97 100644 --- a/shared/Db2Client/src/main/java/org/db2/App.java +++ b/shared/Db2Client/src/main/java/org/db2/App.java @@ -2,7 +2,8 @@ import org.json.JSONObject; -import java.util.Scanner; +import java.io.*; +import java.util.stream.Collectors; public class App { public static void main(String[] args) { diff --git a/shared/Db2Client/src/main/java/org/db2/Db2Service.java b/shared/Db2Client/src/main/java/org/db2/Db2Service.java index a8ed215..21778aa 100644 --- a/shared/Db2Client/src/main/java/org/db2/Db2Service.java +++ b/shared/Db2Client/src/main/java/org/db2/Db2Service.java @@ -3,6 +3,7 @@ import org.json.JSONArray; import java.sql.*; +import java.util.regex.*; public class Db2Service { final String DB_URL; @@ -97,8 +98,8 @@ private void reorganizeTable(String tableName, Statement stmt) throws SQLExcepti private String extractTableNameFromError(String errorMessage) { // Extract table name from error message like: SQLERRMC=7;db1.table2 - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("SQLERRMC=\\d+;([^,;\\s]+)"); - java.util.regex.Matcher matcher = pattern.matcher(errorMessage); + Pattern pattern = Pattern.compile("SQLERRMC=\\d+;([^,;\\s]+)"); + Matcher matcher = pattern.matcher(errorMessage); if (matcher.find()) { String tableName = matcher.group(1).trim(); // Quote the table name properly for REORG statement From 5f8a918dbc096423d088fa1fb14f74dc1f904535 Mon Sep 17 00:00:00 2001 From: chulanovskyi Date: Tue, 13 Jan 2026 14:56:31 +0200 Subject: [PATCH 6/6] fix: updated jar --- shared/addons/Db2Client.jar | Bin 4301113 -> 4301356 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/shared/addons/Db2Client.jar b/shared/addons/Db2Client.jar index 01d4a12232d0e19476cebba50ad94e7190745e63..364efc06f6adc8b73b3fcf264467b4478465e635 100644 GIT binary patch delta 5912 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