Merge branch 'master' into feature/mongo-server-summary

This commit is contained in:
Pavel
2025-08-21 14:27:27 +02:00
19 changed files with 365 additions and 46 deletions

View File

@@ -1,4 +1,5 @@
const _ = require('lodash');
const crypto = require('crypto');
const sql = require('./sql');
const { DatabaseAnalyser, isTypeString, isTypeNumeric } = global.DBGATE_PACKAGES['dbgate-tools'];
@@ -54,6 +55,8 @@ function getColumnInfo({
defaultValue,
defaultConstraint,
computedExpression,
columnComment,
objectId,
}) {
const fullDataType = getFullDataTypeName({
dataType,
@@ -71,6 +74,7 @@ function getColumnInfo({
}
return {
objectId,
columnName,
dataType: fullDataType,
notNull: !isNullable,
@@ -79,9 +83,36 @@ function getColumnInfo({
defaultConstraint,
computedExpression: simplifyComutedExpression(computedExpression),
hasAutoValue: !!(dataType == 'timestamp' || dataType == 'rowversion' || computedExpression),
columnComment,
};
}
/**
* @param {ReturnType<objectTypeToField>} fieldType
* @param {any} item
* @param {Array<{ objectId: string; columnId: number, columnComment: string }>} columns
* @returns {string|null}
*/
function createObjectContentHash(fieldType, item, columns) {
if (!fieldType) return null;
const { modifyDate } = item;
if ((columns?.length && fieldType === 'tables') || fieldType === 'views') {
const modifyDateStr = modifyDate ? modifyDate.toISOString() : '';
const objectColumns = columns.filter(col => col.objectId == item.objectId);
const colsComments = objectColumns
.filter(i => i.columnComment)
.map(i => `${i.columnId}/${i.columnComment}`)
.join('||');
const objectComment = item.objectComment || '';
return crypto.createHash('sha256').update(`${modifyDateStr}:${colsComments}:${objectComment}`).digest('hex');
}
if (!modifyDate) return null;
return modifyDate.toISOString();
}
class MsSqlAnalyser extends DatabaseAnalyser {
constructor(dbhan, driver, version) {
super(dbhan, driver, version);
@@ -104,6 +135,9 @@ class MsSqlAnalyser extends DatabaseAnalyser {
const tablesRows = await this.analyserQuery('tables', ['tables']);
this.feedback({ analysingMessage: 'DBGM-00206 Loading columns' });
const columnsRows = await this.analyserQuery('columns', ['tables']);
const columns = columnsRows.rows.map(getColumnInfo);
const baseColumnsRows = await this.analyserQuery('baseColumns', ['tables']);
const baseColumns = baseColumnsRows.rows.map(getColumnInfo);
this.feedback({ analysingMessage: 'DBGM-00207 Loading primary keys' });
const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']);
this.feedback({ analysingMessage: 'DBGM-00208 Loading foreign keys' });
@@ -142,8 +176,8 @@ class MsSqlAnalyser extends DatabaseAnalyser {
this.feedback({ analysingMessage: 'DBGM-00217 Finalizing DB structure' });
const tables = tablesRows.rows.map(row => ({
...row,
contentHash: row.modifyDate && row.modifyDate.toISOString(),
columns: columnsRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
contentHash: createObjectContentHash('tables', row, baseColumns),
columns: columns.filter(col => col.objectId == row.objectId),
primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows),
foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows),
indexes: indexesRows.rows
@@ -171,7 +205,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
const views = viewsRows.rows.map(row => ({
...row,
contentHash: row.modifyDate && row.modifyDate.toISOString(),
contentHash: createObjectContentHash('views', row, baseColumns),
createSql: getCreateSql(row),
columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
}));
@@ -192,7 +226,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
.filter(x => x.sqlObjectType.trim() == 'P')
.map(row => ({
...row,
contentHash: row.modifyDate && row.modifyDate.toISOString(),
contentHash: createObjectContentHash('procedures', row),
createSql: getCreateSql(row),
parameters: prodceureToParameters[row.objectId],
}));
@@ -213,14 +247,14 @@ class MsSqlAnalyser extends DatabaseAnalyser {
.filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
.map(row => ({
...row,
contentHash: row.modifyDate && row.modifyDate.toISOString(),
contentHash: createObjectContentHash('functions', row),
createSql: getCreateSql(row),
parameters: functionToParameters[row.objectId],
}));
const triggers = triggerRows.rows.map(row => ({
objectId: `triggers:${row.objectId}`,
contentHash: row.modifyDate && row.modifyDate.toISOString(),
contentHash: createObjectContentHash('triggers', row),
createSql: row.definition,
triggerTiming: row.triggerTiming,
eventType: row.eventType,
@@ -241,17 +275,19 @@ class MsSqlAnalyser extends DatabaseAnalyser {
async _getFastSnapshot() {
const modificationsQueryData = await this.analyserQuery('modifications');
const baseColumnsRows = await this.analyserQuery('baseColumns', ['tables']);
const baseColumns = baseColumnsRows.rows;
const tableSizes = await this.analyserQuery('tableSizes');
const res = DatabaseAnalyser.createEmptyStructure();
for (const item of modificationsQueryData.rows) {
const { type, objectId, modifyDate, schemaName, pureName } = item;
const { type, objectId, schemaName, pureName } = item;
const field = objectTypeToField(type);
if (!field || !res[field]) continue;
res[field].push({
objectId,
contentHash: modifyDate && modifyDate.toISOString(),
contentHash: createObjectContentHash(field, item, baseColumns),
schemaName,
pureName,
});

View File

@@ -0,0 +1,11 @@
module.exports = `
select c.object_id as objectId,
ep.value as columnComment,
c.column_id as columnId
from sys.columns c
inner join sys.objects o on c.object_id = o.object_id
INNER JOIN sys.schemas u ON u.schema_id=o.schema_id
INNER JOIN sys.extended_properties ep on ep.major_id = c.object_id and ep.minor_id = c.column_id and ep.name = 'MS_Description'
where o.type IN ('U', 'V') and o.object_id =OBJECT_ID_CONDITION and u.name =SCHEMA_NAME_CONDITION
order by c.column_id
`;

View File

@@ -7,7 +7,8 @@ select c.name as columnName, t.name as dataType, c.object_id as objectId, c.is_i
col.NUMERIC_PRECISION as numericPrecision,
col.NUMERIC_SCALE as numericScale,
-- TODO only if version >= 2008
c.is_sparse as isSparse
c.is_sparse as isSparse,
ep.value as columnComment
from sys.columns c
inner join sys.types t on c.system_type_id = t.system_type_id and c.user_type_id = t.user_type_id
inner join sys.objects o on c.object_id = o.object_id
@@ -15,6 +16,7 @@ INNER JOIN sys.schemas u ON u.schema_id=o.schema_id
INNER JOIN INFORMATION_SCHEMA.COLUMNS col ON col.TABLE_NAME = o.name AND col.TABLE_SCHEMA = u.name and col.COLUMN_NAME = c.name
left join sys.default_constraints d on c.default_object_id = d.object_id
left join sys.computed_columns m on m.object_id = c.object_id and m.column_id = c.column_id
left join sys.extended_properties ep on ep.major_id = c.object_id and ep.minor_id = c.column_id and ep.name = 'MS_Description'
where o.type = 'U' and o.object_id =OBJECT_ID_CONDITION and u.name =SCHEMA_NAME_CONDITION
order by c.column_id
`;

View File

@@ -16,6 +16,7 @@ const triggers = require('./triggers');
const listVariables = require('./listVariables');
const listDatabases = require('./listDatabases');
const listProcesses = require('./listProcesses');
const baseColumns = require('./baseColumns');
module.exports = {
columns,
@@ -36,4 +37,5 @@ module.exports = {
listVariables,
listDatabases,
listProcesses,
baseColumns,
};

View File

@@ -1,8 +1,14 @@
module.exports = `
select
o.name as pureName, s.name as schemaName, o.object_id as objectId,
o.create_date as createDate, o.modify_date as modifyDate
o.name as pureName,
s.name as schemaName,
o.object_id as objectId,
o.create_date as createDate,
o.modify_date as modifyDate,
ep.value as objectComment
from sys.tables o
inner join sys.schemas s on o.schema_id = s.schema_id
where o.object_id =OBJECT_ID_CONDITION and s.name =SCHEMA_NAME_CONDITION
`;
left join sys.extended_properties ep on ep.major_id = o.object_id
and ep.minor_id = 0
and ep.name = 'MS_Description'
where o.object_id =OBJECT_ID_CONDITION and s.name =SCHEMA_NAME_CONDITION`;

View File

@@ -66,14 +66,16 @@ async function tediousConnect(storedConnection) {
const authentication = await getAuthentication(storedConnection);
return new Promise((resolve, reject) => {
const [host, instance] = (server || '').split('\\');
const connectionOptions = {
instanceName: instance,
encrypt: !!ssl || authType == 'msentra' || authType == 'azureManagedIdentity',
cryptoCredentialsDetails: ssl ? _.pick(ssl, ['ca', 'cert', 'key']) : undefined,
trustServerCertificate: ssl ? (!ssl.ca && !ssl.cert && !ssl.key ? true : ssl.rejectUnauthorized) : undefined,
enableArithAbort: true,
validateBulkLoadParameters: false,
requestTimeout: 1000 * 3600,
port: port ? parseInt(port) : undefined,
port: port && !instance ? parseInt(port) : undefined,
trustServerCertificate: !!trustServerCertificate,
appName: 'DbGate',
};
@@ -83,7 +85,7 @@ async function tediousConnect(storedConnection) {
}
const connection = new tedious.Connection({
server,
server: host,
authentication,
options: connectionOptions,
});