Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HCK-8816: queries optimization #120

Merged
merged 16 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
28 changes: 16 additions & 12 deletions reverse_engineering/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const crypto = require('crypto');
const randomstring = require('randomstring');
const base64url = require('base64url');
const { getClient, setClient, clearClient } = require('./connectionState');
const { clientManager } = require('./clientManager');
const { getObjectsFromDatabase, getDatabaseCollationOption } = require('./databaseService/databaseService');
const {
reverseCollectionsToJSON,
Expand All @@ -21,19 +21,22 @@ const { prepareError } = require('./databaseService/helpers/errorService');

module.exports = {
async connect(connectionInfo, logger, callback, app) {
const client = getClient();
const sshService = app.require('@hackolade/ssh-service');
const client = clientManager.getClient();

if (!client) {
await setClient(connectionInfo, sshService, 0, logger);
return getClient();
await clientManager.setClient({
chulanovskyi-bs marked this conversation as resolved.
Show resolved Hide resolved
connectionInfo,
logger,
sshService: app.require('@hackolade/ssh-service'),
chulanovskyi-bs marked this conversation as resolved.
Show resolved Hide resolved
});
return clientManager.getClient();
}

return client;
},

disconnect(connectionInfo, logger, callback, app) {
const sshService = app.require('@hackolade/ssh-service');
clearClient(sshService);
clientManager.clearClient({ sshService: app.require('@hackolade/ssh-service') });
chulanovskyi-bs marked this conversation as resolved.
Show resolved Hide resolved
callback();
},

Expand All @@ -46,7 +49,7 @@ module.exports = {
const client = await this.connect(connectionInfo, logger, () => {}, app);
await logDatabaseVersion(client, logger);
}
callback(null);
callback();
} catch (error) {
const errorWithUpdatedInfo = prepareError({ error });
logger.log(
Expand Down Expand Up @@ -88,8 +91,9 @@ module.exports = {
async getDbCollectionsNames(connectionInfo, logger, callback, app) {
try {
logInfo('Retrieving databases and tables information', connectionInfo, logger);

const client = await this.connect(connectionInfo, logger, () => {}, app);
if (!client.config.database) {
if (!client?.config.database) {
chulanovskyi-bs marked this conversation as resolved.
Show resolved Hide resolved
throw new Error('No database specified');
}

Expand Down Expand Up @@ -120,9 +124,9 @@ module.exports = {
logger.log('info', collectionsInfo, 'Retrieving schema', collectionsInfo.hiddenKeys);
logger.progress({ message: 'Start reverse-engineering process', containerName: '', entityName: '' });
const { collections } = collectionsInfo.collectionData;
const client = getClient();
const dbName = client.config.database;
if (!dbName) {
const client = clientManager.getClient();
const dbName = client?.config.database;
if (!client || !dbName) {
throw new Error('No database specified');
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
const { getConnectionClient } = require('./databaseService/databaseService');

const stateInstance = {
_client: null,
_isSshTunnel: false,
getClient: () => this._client,
setClient: async (connectionInfo, sshService, attempts = 0, logger) => {
if (connectionInfo.ssh && !this._isSshTunnel) {
class ClientManager {
#client = null;
#isSshTunnel = false;

getClient() {
return this.#client;
}

async setClient({ connectionInfo, sshService, attempts = 0, logger }) {
let connectionParams = { ...connectionInfo };

if (connectionInfo.ssh && !this.#isSshTunnel) {
const { options } = await sshService.openTunnel({
sshAuthMethod: connectionInfo.ssh_method === 'privateKey' ? 'IDENTITY_FILE' : 'USER_PASSWORD',
sshTunnelHostname: connectionInfo.ssh_host,
Expand All @@ -18,44 +24,49 @@ const stateInstance = {
port: connectionInfo.port,
});

this._isSshTunnel = true;
connectionInfo = {
this.#isSshTunnel = true;

connectionParams = {
...connectionInfo,
...options,
};
}

try {
this._client = await getConnectionClient(connectionInfo, logger);
this.#client = await getConnectionClient(connectionParams, logger);
} catch (error) {
const encryptConnection =
connectionInfo.encryptConnection === undefined || Boolean(connectionInfo.encryptConnection);
connectionParams.encryptConnection === undefined || Boolean(connectionParams.encryptConnection);

const isEncryptedConnectionToLocalInstance =
error.message.includes('self signed certificate') && encryptConnection;

if (isEncryptedConnectionToLocalInstance && attempts <= 0) {
return stateInstance.setClient(
{
...connectionInfo,
return this.setClient({
connectionInfo: {
...connectionParams,
encryptConnection: false,
},
sshService,
attempts + 1,
attempts: attempts + 1,
logger,
);
});
}

throw error;
}
},
clearClient: async sshService => {
this._client = null;
}

clearClient({ sshService }) {
this.#client = null;

if (this._isSshTunnel) {
await sshService.closeConsumer();
this._isSshTunnel = false;
if (this.#isSshTunnel) {
sshService.closeConsumer();
this.#isSshTunnel = false;
}
},
};
}
}

module.exports = stateInstance;
module.exports = {
clientManager: new ClientManager(),
};
54 changes: 24 additions & 30 deletions reverse_engineering/databaseService/databaseService.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const getSampleDocSize = require('../helpers/getSampleDocSize');
const QUERY_REQUEST_TIMEOUT = 60000;

const getSslConfig = connectionInfo => {
const encrypt = connectionInfo.encryptConnection === undefined ? true : Boolean(connectionInfo.encryptConnection);

if (connectionInfo.sslType === 'SYSTEMCA') {
return {};
}
Expand Down Expand Up @@ -233,21 +231,21 @@ const getTableInfo = async (connectionClient, dbName, tableName, tableSchema, lo
logger,
);
const objectId = `${tableSchema}.${tableName}`;
return mapResponse(
await currentDbConnectionClient.query`
SELECT c.*,
ic.SEED_VALUE,
ic.INCREMENT_VALUE,
COLUMNPROPERTY(OBJECT_ID(${objectId}), c.column_name, 'IsSparse') AS IS_SPARSE,
COLUMNPROPERTY(OBJECT_ID(${objectId}), c.column_name, 'IsIdentity') AS IS_IDENTITY,
o.type AS TABLE_TYPE
FROM INFORMATION_SCHEMA.COLUMNS AS c
LEFT JOIN sys.identity_columns ic ON ic.object_id=OBJECT_ID(${objectId})
LEFT JOIN sys.objects o ON o.object_id=OBJECT_ID(${objectId})
WHERE c.table_name = ${tableName}
AND c.table_schema = ${tableSchema}
;`,
);

const result = await currentDbConnectionClient.query`
SELECT c.*,
ic.SEED_VALUE,
ic.INCREMENT_VALUE,
COLUMNPROPERTY(OBJECT_ID(${objectId}), c.column_name, 'IsSparse') AS IS_SPARSE,
COLUMNPROPERTY(OBJECT_ID(${objectId}), c.column_name, 'IsIdentity') AS IS_IDENTITY,
o.type AS TABLE_TYPE
FROM INFORMATION_SCHEMA.COLUMNS AS c
LEFT JOIN sys.identity_columns ic ON ic.object_id=OBJECT_ID(${objectId})
LEFT JOIN sys.objects o ON o.object_id=OBJECT_ID(${objectId})
WHERE c.table_name = ${tableName}
AND c.table_schema = ${tableSchema};`;

return mapResponse(result);
};

const getTableSystemTime = async (connectionClient, dbName, tableName, tableSchema, logger) => {
Expand Down Expand Up @@ -275,6 +273,12 @@ const getTableSystemTime = async (connectionClient, dbName, tableName, tableSche
);
};

const getTableRowCount = async (tableSchema, tableName, currentDbConnectionClient) => {
const rowCountQuery = `SELECT COUNT(*) as rowsCount FROM [${tableSchema}].[${tableName}]`;
const rowCountResponse = await currentDbConnectionClient.query(rowCountQuery);
return rowCountResponse?.recordset[0]?.rowsCount;
};

const getTableRow = async (connectionClient, dbName, tableName, tableSchema, recordSamplingSettings, logger) => {
const currentDbConnectionClient = await getClient(
connectionClient,
Expand Down Expand Up @@ -870,18 +874,18 @@ const getTableDefaultConstraintNames = async (connectionClient, dbName, tableNam
SELECT
ac.name AS columnName,
dc.name
FROM
FROM
sys.all_columns AS ac
INNER JOIN
sys.tables
ON ac.object_id = tables.object_id
INNER JOIN
INNER JOIN
sys.schemas
ON tables.schema_id = schemas.schema_id
INNER JOIN
sys.default_constraints AS dc
ON ac.default_object_id = dc.object_id
WHERE
WHERE
schemas.name = ${schemaName}
AND tables.name = ${tableName}
`,
Expand Down Expand Up @@ -1004,8 +1008,6 @@ const getToken = async ({ connectionInfo, tenantId, clientId, redirectUri, logge
if (axiosToken) {
return axiosToken;
}

return;
};

const getAuthConfig = (clientId, tenantId, logger) => ({
Expand Down Expand Up @@ -1084,11 +1086,3 @@ module.exports = {
getVersionInfo,
getDescriptionComments,
};

async function getTableRowCount(tableSchema, tableName, currentDbConnectionClient) {
const rowCountQuery = `SELECT COUNT(*) as rowsCount FROM [${tableSchema}].[${tableName}]`;
const rowCountResponse = await currentDbConnectionClient.query(rowCountQuery);
const rowCount = rowCountResponse?.recordset[0]?.rowsCount;

return rowCount;
}
Loading