Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions forward_engineering/api.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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,
};
23 changes: 23 additions & 0 deletions forward_engineering/api/applyToInstance.js
Original file line number Diff line number Diff line change
@@ -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, ddl: true });

callback();
} catch (err) {
applyToInstanceLogger.error(err);
callback(err);
}
}

module.exports = { applyToInstance };
2 changes: 1 addition & 1 deletion forward_engineering/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
],
"hasUpdateScript": false,
"applyScriptToInstance": false,
"applyScriptToInstance": true,
"combinedContainers": true,
"feLevelSelector": {
"container": true,
Expand Down
29 changes: 1 addition & 28 deletions reverse_engineering/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
65 changes: 33 additions & 32 deletions shared/Db2Client/src/main/java/org/db2/App.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,63 @@
package org.db2;

import org.json.JSONArray;
import org.json.JSONObject;

import java.sql.SQLException;
import java.util.Arrays;
import java.io.*;
import java.util.stream.Collectors;

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 {
db2Service.openConnection();
String jsonInput = readStdin();
JSONObject input = new JSONObject(jsonInput);

boolean isCallableQuery = Boolean.parseBoolean(callable);
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);

if (isCallableQuery) {
db2Service = new Db2Service(host, port, database, user, password, new ResponseMapper());
db2Service.openConnection();

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 {
JSONArray queryResult = db2Service.executeQuery(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();
if (db2Service != null) {
db2Service.closeConnection();
}
print(result.toString());
}
}

private static String cleanStringValue(String value) {
return value.replace("__PERCENT__", "%");
}

private static String findArgument(String[] args, Argument argument) {
return Arrays.stream(args)
.filter(arg -> arg.startsWith(argument.getPrefix()))
.map(arg -> arg.substring(argument.getStartValueIndex()))
.findFirst()
.orElse("");
}
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("<hackolade>%s</hackolade>", value));
Expand Down
28 changes: 0 additions & 28 deletions shared/Db2Client/src/main/java/org/db2/Argument.java

This file was deleted.

121 changes: 105 additions & 16 deletions shared/Db2Client/src/main/java/org/db2/Db2Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.json.JSONArray;

import java.sql.*;
import java.util.regex.*;

public class Db2Service {
final String DB_URL;
Expand Down Expand Up @@ -30,6 +31,90 @@ public JSONArray executeQuery(String query) throws SQLException {
return mapper.convertToJson(response);
}

public int applyScript(String script) throws SQLException {
String[] statements = splitStatements(script);
int totalUpdateCount = 0;

for (String statement : statements) {
statement = statement.trim();

if (statement.isEmpty()) {
continue;
}

Statement statementInstance = connection.createStatement();

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 totalUpdateCount;
}

private String[] splitStatements(String query) {
String[] parts = query.trim().split(";\\s+", -1);
java.util.ArrayList<String> statements = new java.util.ArrayList<>();
for (String part : parts) {
part = part.trim();
if (!part.isEmpty()) {
statements.add(part);
}
}
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
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
// 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);

Expand All @@ -53,29 +138,33 @@ public void openConnection() throws SQLException {
}

public void closeConnection() {
if (response != null) {
if (this.response != null) {
try {
response.close();
} catch (SQLException e) {
/* Ignored */}
this.response.close();
} catch (SQLException _) {
/* Ignored */
}
}
if (statement != null) {
if (this.statement != null) {
try {
statement.close();
} catch (SQLException e) {
/* Ignored */}
this.statement.close();
} catch (SQLException _) {
/* Ignored */
}
}
if (callableStatement != null) {
if (this.callableStatement != null) {
try {
callableStatement.close();
} catch (SQLException e) {
/* Ignored */}
this.callableStatement.close();
} catch (SQLException _) {
/* Ignored */
}
}
if (connection != null) {
if (this.connection != null) {
try {
connection.close();
} catch (SQLException e) {
/* Ignored */}
this.connection.close();
} catch (SQLException _) {
/* Ignored */
}
}
}

Expand Down
Binary file modified shared/addons/Db2Client.jar
Binary file not shown.
Loading