From 5e2776f264b307df4e882abd6bb1a7c547a32df7 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 24 Jul 2025 21:08:05 +0200 Subject: [PATCH 01/26] feat: add MS_Description to mssql analyzer columns --- plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js | 3 +++ plugins/dbgate-plugin-mssql/src/backend/sql/columns.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index 020b0d315..94b5c6382 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -54,6 +54,7 @@ function getColumnInfo({ defaultValue, defaultConstraint, computedExpression, + columnComment, }) { const fullDataType = getFullDataTypeName({ dataType, @@ -79,6 +80,7 @@ function getColumnInfo({ defaultConstraint, computedExpression: simplifyComutedExpression(computedExpression), hasAutoValue: !!(dataType == 'timestamp' || dataType == 'rowversion' || computedExpression), + columnComment, }; } @@ -104,6 +106,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { const tablesRows = await this.analyserQuery('tables', ['tables']); this.feedback({ analysingMessage: 'Loading columns' }); const columnsRows = await this.analyserQuery('columns', ['tables']); + this.feedback({ analysingMessage: 'Loading primary keys' }); const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']); this.feedback({ analysingMessage: 'Loading foreign keys' }); diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/columns.js b/plugins/dbgate-plugin-mssql/src/backend/sql/columns.js index 7a6ee8cf2..ad4b67781 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/sql/columns.js +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/columns.js @@ -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 `; From 252db191a60e21d0d230c1df79210ce6545246a5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 25 Jul 2025 05:56:42 +0200 Subject: [PATCH 02/26] feat: add ms_description to mssqldumper table options --- .../src/frontend/MsSqlDumper.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index 2cb9ee526..9e0265a6f 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -163,6 +163,25 @@ class MsSqlDumper extends SqlDumper { this.put('^select ^scope_identity()'); } + /** + * @param {import('dbgate-types').TableInfo} table + */ + tableOptions(table) { + super.tableOptions(table); + + for (const col of table.columns) { + if (col.columnComment) { + this.put('^exec sp_addextendedproperty&n'); + this.put("@name = N'MS_Description',"); + this.put("@value = N'Identifier for the user who created this notification record'&n"); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", table.schemaName); + this.put("@level1type = N'TABLE', @level1name = '%s',&n", table.pureName); + this.put("@level2type = N'COLUMN', @level2name = '%s'", col.pureName); + this.endCommand(); + } + } + } + callableTemplate(func) { const putParameters = (parameters, delimiter) => { this.putCollection(delimiter, parameters || [], param => { @@ -207,8 +226,8 @@ MsSqlDumper.prototype.changeProcedureSchema = MsSqlDumper.prototype.changeObject MsSqlDumper.prototype.renameFunction = MsSqlDumper.prototype.renameObject; MsSqlDumper.prototype.changeFunctionSchema = MsSqlDumper.prototype.changeObjectSchema; - MsSqlDumper.prototype.renameTrigger = MsSqlDumper.prototype.renameObject; + MsSqlDumper.prototype.changeTriggerSchema = MsSqlDumper.prototype.changeObjectSchema; MsSqlDumper.prototype.renameTable = MsSqlDumper.prototype.renameObject; From 6af56a61b8efc3b69ddc372159f7fe03d978c5da Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 29 Jul 2025 19:40:43 +0200 Subject: [PATCH 03/26] feat: add ignoreComments options testEqualColumns --- packages/tools/src/diffTools.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/tools/src/diffTools.ts b/packages/tools/src/diffTools.ts index 1bb7ee7d5..6093ad666 100644 --- a/packages/tools/src/diffTools.ts +++ b/packages/tools/src/diffTools.ts @@ -59,6 +59,7 @@ export interface DbDiffOptions { ignoreForeignKeyActions?: boolean; ignoreDataTypes?: boolean; + ignoreComments?: boolean; } export function generateTablePairingId(table: TableInfo): TableInfo { @@ -322,11 +323,14 @@ export function testEqualColumns( ); return false; } - if ((a.columnComment || '') != (b.columnComment || '')) { - console.debug( - `Column ${a.pureName}.${a.columnName}, ${b.pureName}.${b.columnName}: different comment: ${a.columnComment}, ${b.columnComment}` - ); - return false; + + if (!opts.ignoreComments) { + if ((a.columnComment || '') != (b.columnComment || '')) { + console.debug( + `Column ${a.pureName}.${a.columnName}, ${b.pureName}.${b.columnName}: different comment: ${a.columnComment}, ${b.columnComment}` + ); + return false; + } } if (!testEqualTypes(a, b, opts)) { From a9cff0157988c56a0021a269527480b12ec14c94 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 29 Jul 2025 19:41:01 +0200 Subject: [PATCH 04/26] fix: show columnComment in structure ui --- plugins/dbgate-plugin-mssql/src/frontend/driver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/dbgate-plugin-mssql/src/frontend/driver.js b/plugins/dbgate-plugin-mssql/src/frontend/driver.js index 56c315e14..309cfba19 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/driver.js @@ -45,6 +45,7 @@ const dialect = { namedDefaultConstraint: true, columnProperties: { + columnComment: true, isSparse: true, isPersisted: true, }, From c20aec23a23e074cb9ed07c83877f565947658e5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 29 Jul 2025 19:41:36 +0200 Subject: [PATCH 05/26] feat: change ms_description when columnComment changes --- .../src/frontend/MsSqlDumper.js | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index 9e0265a6f..00f052544 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -124,8 +124,51 @@ class MsSqlDumper extends SqlDumper { this.putCmd("^execute sp_rename '%f.%i', '%s', 'COLUMN'", column, column.columnName, newcol); } + /** + * @param {import('dbgate-types').ColumnInfo} oldcol + * @param {import('dbgate-types').ColumnInfo} newcol + */ + changeColumnDescription(oldcol, newcol) { + if (oldcol.columnComment == newcol.columnComment) return; + if (oldcol.columnComment && !newcol.columnComment) { + this.dropColumnDescription(newcol); + } else { + this.dropColumnDescription(newcol); + this.createColumnDescription(newcol); + } + } + + /** + * @param {import('dbgate-types').ColumnInfo} column + */ + dropColumnDescription(column) { + const { schemaName, columnName, pureName } = column; + + this.put('^exec sp_dropextendedproperty&n'); + this.put("@name = N'MS_Description',"); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); + this.put("@level1type = N'TABLE', @level1name = '%s',&n", pureName); + this.put("@level2type = N'COLUMN', @level2name = '%s'", columnName); + this.endCommand(); + } + + /** + * @param {import('dbgate-types').ColumnInfo} column + */ + createColumnDescription(column) { + const { schemaName, columnName, pureName, columnComment } = column; + + this.put('^exec sp_addextendedproperty&n'); + this.put("@name = N'MS_Description',"); + this.put(`@value = N'%s',&n`, columnComment); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); + this.put("@level1type = N'TABLE', @level1name = '%s',&n", pureName); + this.put("@level2type = N'COLUMN', @level2name = '%s'", columnName); + this.endCommand(); + } + changeColumn(oldcol, newcol, constraints) { - if (testEqualColumns(oldcol, newcol, false, false)) { + if (testEqualColumns(oldcol, newcol, false, false, { ignoreComments: true })) { this.dropDefault(oldcol); if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName); this.createDefault(newcol); @@ -140,6 +183,8 @@ class MsSqlDumper extends SqlDumper { this.endCommand(); this.createDefault(newcol); } + + this.changeColumnDescription(oldcol, newcol); } specialColumnOptions(column) { @@ -163,25 +208,6 @@ class MsSqlDumper extends SqlDumper { this.put('^select ^scope_identity()'); } - /** - * @param {import('dbgate-types').TableInfo} table - */ - tableOptions(table) { - super.tableOptions(table); - - for (const col of table.columns) { - if (col.columnComment) { - this.put('^exec sp_addextendedproperty&n'); - this.put("@name = N'MS_Description',"); - this.put("@value = N'Identifier for the user who created this notification record'&n"); - this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", table.schemaName); - this.put("@level1type = N'TABLE', @level1name = '%s',&n", table.pureName); - this.put("@level2type = N'COLUMN', @level2name = '%s'", col.pureName); - this.endCommand(); - } - } - } - callableTemplate(func) { const putParameters = (parameters, delimiter) => { this.putCollection(delimiter, parameters || [], param => { From 4f6a3c23ad4e60bd028216daf1226d525e045755 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 30 Jul 2025 03:35:01 +0200 Subject: [PATCH 06/26] feat: ms_description for tables, upd columns --- packages/types/dialect.d.ts | 6 ++ .../src/backend/sql/tables.js | 14 ++- .../src/frontend/MsSqlDumper.js | 89 ++++++++++++++++--- .../src/frontend/driver.js | 12 +++ 4 files changed, 106 insertions(+), 15 deletions(-) diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index 017b6f98b..da85ac967 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -74,6 +74,12 @@ export interface SqlDialect { predefinedDataTypes: string[]; + columnProperties?: { + columnName?: boolean; + isSparse?: true; + isPersisted?: true; + }; + // create sql-tree expression createColumnViewExpression( columnName: string, diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/tables.js b/plugins/dbgate-plugin-mssql/src/backend/sql/tables.js index 32dace94d..17887761a 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/sql/tables.js +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/tables.js @@ -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`; diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index 00f052544..e15a705f5 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -124,46 +124,98 @@ class MsSqlDumper extends SqlDumper { this.putCmd("^execute sp_rename '%f.%i', '%s', 'COLUMN'", column, column.columnName, newcol); } + /** + * @param {import('dbgate-types').TableInfo} table + */ + dropTableCommentIfExists(table) { + const { schemaName, pureName } = table; + + const fullName = `${schemaName && schemaName + '.'}${pureName}`; + + this.put('&>^if ^exists (&n'); + this.put('&>^select 1 ^from sys.extended_properties&n'); + this.put("^where major_id = OBJECT_ID('%s')&n", fullName); + this.put('^and minor_id = 0&n'); + this.put("^and name = N'MS_Description'&<&<&n"); + this.put(')&n'); + this.put('&>^begin&n'); + this.put('&>^exec sp_dropextendedproperty&n'); + this.put("@name = N'MS_Description',&n"); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); + this.put("@level1type = N'TABLE', @level1name = '%s'&<&n", pureName); + this.put('^end'); + this.endCommand(); + } + + /** + * @param {import('dbgate-types').TableInfo} table + */ + createTableComment(table) { + const { schemaName, pureName, objectComment } = table; + if (!objectComment) return; + + this.put('&>^exec sp_addextendedproperty&n'); + this.put("@name = N'MS_Description', @value = N'%s',&n", objectComment); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); + this.put("@level1type = N'TABLE', @level1name = '%s&<'", pureName); + this.endCommand(); + } + + /** + * @param {import('dbgate-types').TableInfo} table + */ + createColumnComment(table) { + const { schemaName, pureName, objectComment } = table; + if (!objectComment) return; + + this.put('&>^exec sp_addextendedproperty&n'); + this.put(`@value = N'%s',&n`, objectComment); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); + this.put("@level1type = N'TABLE', @level1name = '%s'&n&<", pureName); + this.endCommand(); + } + /** * @param {import('dbgate-types').ColumnInfo} oldcol * @param {import('dbgate-types').ColumnInfo} newcol */ - changeColumnDescription(oldcol, newcol) { + changeColumnComment(oldcol, newcol) { if (oldcol.columnComment == newcol.columnComment) return; if (oldcol.columnComment && !newcol.columnComment) { - this.dropColumnDescription(newcol); + this.dropColumnComment(newcol); } else { - this.dropColumnDescription(newcol); - this.createColumnDescription(newcol); + this.dropColumnComment(newcol); + this.createColumnComment(newcol); } } /** * @param {import('dbgate-types').ColumnInfo} column */ - dropColumnDescription(column) { + dropColumnComment(column) { const { schemaName, columnName, pureName } = column; - this.put('^exec sp_dropextendedproperty&n'); + this.put('&>^exec sp_dropextendedproperty&n'); this.put("@name = N'MS_Description',"); this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); this.put("@level1type = N'TABLE', @level1name = '%s',&n", pureName); - this.put("@level2type = N'COLUMN', @level2name = '%s'", columnName); + this.put("@level2type = N'COLUMN', @level2name = '%s'&<", columnName); this.endCommand(); } /** * @param {import('dbgate-types').ColumnInfo} column */ - createColumnDescription(column) { + createColumnComment(column) { const { schemaName, columnName, pureName, columnComment } = column; + if (!columnComment) return; - this.put('^exec sp_addextendedproperty&n'); + this.put('&>^exec sp_addextendedproperty&n'); this.put("@name = N'MS_Description',"); this.put(`@value = N'%s',&n`, columnComment); this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); this.put("@level1type = N'TABLE', @level1name = '%s',&n", pureName); - this.put("@level2type = N'COLUMN', @level2name = '%s'", columnName); + this.put("@level2type = N'COLUMN', @level2name = '%s&<'", columnName); this.endCommand(); } @@ -184,7 +236,7 @@ class MsSqlDumper extends SqlDumper { this.createDefault(newcol); } - this.changeColumnDescription(oldcol, newcol); + this.changeColumnComment(oldcol, newcol); } specialColumnOptions(column) { @@ -208,6 +260,21 @@ class MsSqlDumper extends SqlDumper { this.put('^select ^scope_identity()'); } + /** + * @param {import('dbgate-types').TableInfo} table + * @param {string} optionName + * @param {string} optionValue + */ + setTableOption(table, optionName, optionValue) { + if (optionName == 'objectComment') { + this.dropTableCommentIfExists(table); + if (optionValue) this.createTableComment(table); + return; + } + + super.setTableOption(table, optionName, optionValue); + } + callableTemplate(func) { const putParameters = (parameters, delimiter) => { this.putCollection(delimiter, parameters || [], param => { diff --git a/plugins/dbgate-plugin-mssql/src/frontend/driver.js b/plugins/dbgate-plugin-mssql/src/frontend/driver.js index 309cfba19..91d36b12d 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/driver.js @@ -112,6 +112,18 @@ const dialect = { }; } }, + + getTableFormOptions(intent) { + return [ + { + type: 'text', + label: 'Comment', + name: 'objectComment', + sqlFormatString: '^comment = %v', + allowEmptyValue: true, + }, + ]; + }, }; /** @type {import('dbgate-types').EngineDriver} */ From d151114f08383934b03801631f52775d3e84c270 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 31 Jul 2025 16:32:21 +0200 Subject: [PATCH 07/26] fix: correctly parse table comment when creating a table --- .../src/frontend/MsSqlDumper.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index e15a705f5..41bb495c0 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -156,7 +156,7 @@ class MsSqlDumper extends SqlDumper { this.put('&>^exec sp_addextendedproperty&n'); this.put("@name = N'MS_Description', @value = N'%s',&n", objectComment); - this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); + this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName || 'dbo'); this.put("@level1type = N'TABLE', @level1name = '%s&<'", pureName); this.endCommand(); } @@ -260,6 +260,29 @@ class MsSqlDumper extends SqlDumper { this.put('^select ^scope_identity()'); } + /** + * @param {import('dbgate-types').TableInfo} table + */ + tableOptions(table) { + this.endCommand(); + + const options = this.driver?.dialect?.getTableFormOptions?.('sqlCreateTable') || []; + for (const option of options) { + const { name, sqlFormatString } = option; + const value = table[name]; + + if (name == 'objectComment') { + this.createTableComment(table); + return; + } + + if (value) { + this.put('&n'); + this.put(sqlFormatString, value); + } + } + } + /** * @param {import('dbgate-types').TableInfo} table * @param {string} optionName From 888e284f84c57266b2e073898b9f7abbeb494192 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 31 Jul 2025 17:46:51 +0200 Subject: [PATCH 08/26] fix:fix: removeu duplicate method, simplify changeColumnComment --- .../src/frontend/MsSqlDumper.js | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index 41bb495c0..66ddde21d 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -161,32 +161,15 @@ class MsSqlDumper extends SqlDumper { this.endCommand(); } - /** - * @param {import('dbgate-types').TableInfo} table - */ - createColumnComment(table) { - const { schemaName, pureName, objectComment } = table; - if (!objectComment) return; - - this.put('&>^exec sp_addextendedproperty&n'); - this.put(`@value = N'%s',&n`, objectComment); - this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); - this.put("@level1type = N'TABLE', @level1name = '%s'&n&<", pureName); - this.endCommand(); - } - /** * @param {import('dbgate-types').ColumnInfo} oldcol * @param {import('dbgate-types').ColumnInfo} newcol */ changeColumnComment(oldcol, newcol) { - if (oldcol.columnComment == newcol.columnComment) return; - if (oldcol.columnComment && !newcol.columnComment) { - this.dropColumnComment(newcol); - } else { - this.dropColumnComment(newcol); - this.createColumnComment(newcol); - } + if (oldcol.columnComment === newcol.columnComment) return; + + if (oldcol.columnComment) this.dropColumnComment(newcol); + if (newcol.columnComment) this.createColumnComment(newcol); } /** @@ -211,7 +194,7 @@ class MsSqlDumper extends SqlDumper { if (!columnComment) return; this.put('&>^exec sp_addextendedproperty&n'); - this.put("@name = N'MS_Description',"); + this.put("@name = N'MS_Description', "); this.put(`@value = N'%s',&n`, columnComment); this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); this.put("@level1type = N'TABLE', @level1name = '%s',&n", pureName); @@ -219,6 +202,17 @@ class MsSqlDumper extends SqlDumper { this.endCommand(); } + /** + * @param {import('dbgate-types').TableInfo} table + */ + createTable(table) { + super.createTable(table); + + for (const column of table.columns || []) { + this.createColumnComment(column); + } + } + changeColumn(oldcol, newcol, constraints) { if (testEqualColumns(oldcol, newcol, false, false, { ignoreComments: true })) { this.dropDefault(oldcol); From 489f3aa19df41c041f9c812dbaf5d801b53c23ec Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 31 Jul 2025 17:47:12 +0200 Subject: [PATCH 09/26] feat: add test for table creation with comments --- .../__tests__/table-create.spec.js | 34 +++++++++++++++++++ integration-tests/engines.js | 2 ++ packages/types/test-engines.d.ts | 3 ++ 3 files changed, 39 insertions(+) diff --git a/integration-tests/__tests__/table-create.spec.js b/integration-tests/__tests__/table-create.spec.js index fd5ffe0df..7e0357d68 100644 --- a/integration-tests/__tests__/table-create.spec.js +++ b/integration-tests/__tests__/table-create.spec.js @@ -64,6 +64,40 @@ describe('Table create', () => { }) ); + test.each( + engines.filter(i => i.supportTableComments || i.supportColumnComments).map(engine => [engine.label, engine]) + )( + 'Simple table with comment - %s', + testWrapper(async (conn, driver, engine) => { + await testTableCreate(engine, conn, driver, { + ...(engine.supportTableComments && { + schemaName: 'dbo', + objectComment: 'table comment', + }), + ...(engine.defaultSchemaName && { + schemaName: engine.defaultSchemaName, + }), + columns: [ + { + columnName: 'col1', + dataType: 'int', + pureName: 'tested', + ...(engine.skipNullability ? {} : { notNull: true }), + ...(engine.supportColumnComments && { + columnComment: 'column comment', + }), + ...(engine.defaultSchemaName && { + schemaName: engine.defaultSchemaName, + }), + }, + ], + primaryKey: { + columns: [{ columnName: 'col1' }], + }, + }); + }) + ); + test.each(engines.filter(x => !x.skipIndexes).map(engine => [engine.label, engine]))( 'Table with index - %s', testWrapper(async (conn, driver, engine) => { diff --git a/integration-tests/engines.js b/integration-tests/engines.js index 1fb4f28dd..6c16040a9 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -443,6 +443,8 @@ const sqlServerEngine = { supportSchemas: true, supportRenameSqlObject: true, defaultSchemaName: 'dbo', + supportTableComments: true, + supportColumnComments: true, // skipSeparateSchemas: true, triggers: [ { diff --git a/packages/types/test-engines.d.ts b/packages/types/test-engines.d.ts index 4388e5c85..9f4e2e3e9 100644 --- a/packages/types/test-engines.d.ts +++ b/packages/types/test-engines.d.ts @@ -56,6 +56,9 @@ export type TestEngineInfo = { useTextTypeForStrings?: boolean; + supportTableComments?: boolean; + supportColumnComments?: boolean; + supportRenameSqlObject?: boolean; supportSchemas?: boolean; From 339eab33c89512e6c8c63319ebf4504a38720699 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 31 Jul 2025 18:30:20 +0200 Subject: [PATCH 10/26] feat: add add comment to column test --- integration-tests/__tests__/alter-table.spec.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index c6883543b..d0cfbfd61 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -118,6 +118,22 @@ describe('Alter table', () => { }) ); + test.each(engines.filter(i => i.supportColumnComments).map(engine => [engine.label, engine]))( + 'Add comment to column - %s', + testWrapper(async (conn, driver, engine) => { + await testTableDiff(engine, conn, driver, tbl => { + tbl.columns.push({ + columnName: 'added', + columnComment: 'Added column comment', + dataType: 'int', + pairingId: crypto.randomUUID(), + notNull: false, + autoIncrement: false, + }); + }); + }) + ); + test.each( createEnginesColumnsSource(engines.filter(x => !x.skipDropColumn)).filter( ([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk') From 795992fb4270e06a640bf118024f7dd96661c622 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 31 Jul 2025 18:31:49 +0200 Subject: [PATCH 11/26] feat: add add comment to table teest --- integration-tests/__tests__/alter-table.spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index d0cfbfd61..65e1b2d3f 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -118,6 +118,15 @@ describe('Alter table', () => { }) ); + test.each(engines.filter(i => i.supportTableComments).map(engine => [engine.label, engine]))( + 'Add comment to table - %s', + testWrapper(async (conn, driver, engine) => { + await testTableDiff(engine, conn, driver, tbl => { + tbl.objectComment = 'Added table comment'; + }); + }) + ); + test.each(engines.filter(i => i.supportColumnComments).map(engine => [engine.label, engine]))( 'Add comment to column - %s', testWrapper(async (conn, driver, engine) => { From 32b1a5b22d8d9759314a5ba266eba54f646f2a82 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 1 Aug 2025 05:44:41 +0200 Subject: [PATCH 12/26] feat: include comments in contentHash for mssql --- .../src/backend/MsSqlAnalyser.js | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index 94b5c6382..682cdeb48 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -1,4 +1,5 @@ const _ = require('lodash'); +const crypto = require('crypto'); const sql = require('./sql'); const { DatabaseAnalyser, isTypeString, isTypeNumeric } = global.DBGATE_PACKAGES['dbgate-tools']; @@ -55,6 +56,7 @@ function getColumnInfo({ defaultConstraint, computedExpression, columnComment, + objectId, }) { const fullDataType = getFullDataTypeName({ dataType, @@ -72,6 +74,7 @@ function getColumnInfo({ } return { + objectId, columnName, dataType: fullDataType, notNull: !isNullable, @@ -84,6 +87,31 @@ function getColumnInfo({ }; } +/** + * @param {ReturnType} fieldType + * @param {any} item + * @param {Array>} 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.map(i => i.columnComment).join(','); + + return crypto + .createHash('sha256') + .update(modifyDateStr + colsComments) + .digest('hex'); + } + + if (!modifyDate) return null; + return modifyDate.toISOString(); +} + class MsSqlAnalyser extends DatabaseAnalyser { constructor(dbhan, driver, version) { super(dbhan, driver, version); @@ -106,6 +134,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { const tablesRows = await this.analyserQuery('tables', ['tables']); this.feedback({ analysingMessage: 'Loading columns' }); const columnsRows = await this.analyserQuery('columns', ['tables']); + const columns = columnsRows.rows.map(getColumnInfo); this.feedback({ analysingMessage: 'Loading primary keys' }); const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']); @@ -145,8 +174,8 @@ class MsSqlAnalyser extends DatabaseAnalyser { this.feedback({ analysingMessage: '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, columns), + columns: columns.filter(col => col.objectId == row.objectId), primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows), foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows), indexes: indexesRows.rows @@ -174,7 +203,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { const views = viewsRows.rows.map(row => ({ ...row, - contentHash: row.modifyDate && row.modifyDate.toISOString(), + contentHash: createObjectContentHash('views', row, columns), createSql: getCreateSql(row), columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo), })); @@ -195,7 +224,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], })); @@ -216,14 +245,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, @@ -244,17 +273,19 @@ class MsSqlAnalyser extends DatabaseAnalyser { async _getFastSnapshot() { const modificationsQueryData = await this.analyserQuery('modifications'); + const columnsRows = await this.analyserQuery('columns', ['tables']); + const columns = columnsRows.rows.map(getColumnInfo); 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, columns), schemaName, pureName, }); From de43880a1c6a55ec266968a660b4187274f839e9 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 5 Aug 2025 17:39:53 +0200 Subject: [PATCH 13/26] feat: fetch only base column info for modifications, drop columnComment only if exists --- .../src/backend/MsSqlAnalyser.js | 8 +++---- .../src/backend/sql/baseColumns.js | 10 +++++++++ .../src/backend/sql/index.js | 2 ++ .../src/frontend/MsSqlDumper.js | 21 +++++++++++++++---- 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index 682cdeb48..3bdf57629 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -90,7 +90,7 @@ function getColumnInfo({ /** * @param {ReturnType} fieldType * @param {any} item - * @param {Array>} columns + * @param {Array<{ objectId: string; columnComment: string }>} columns * @returns {string|null} */ function createObjectContentHash(fieldType, item, columns) { @@ -273,8 +273,8 @@ class MsSqlAnalyser extends DatabaseAnalyser { async _getFastSnapshot() { const modificationsQueryData = await this.analyserQuery('modifications'); - const columnsRows = await this.analyserQuery('columns', ['tables']); - const columns = columnsRows.rows.map(getColumnInfo); + const baseColumnsRows = await this.analyserQuery('columns', ['tables']); + const baseColumns = baseColumnsRows.rows.map(getColumnInfo); const tableSizes = await this.analyserQuery('tableSizes'); const res = DatabaseAnalyser.createEmptyStructure(); @@ -285,7 +285,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { res[field].push({ objectId, - contentHash: createObjectContentHash(field, item, columns), + contentHash: createObjectContentHash(field, item, baseColumns), schemaName, pureName, }); diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js b/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js new file mode 100644 index 000000000..497d48b77 --- /dev/null +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js @@ -0,0 +1,10 @@ +module.exports = ` +select c.object_id as objectId, + ep.value as columnComment +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 +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 IN ('U', 'V') and o.object_id =OBJECT_ID_CONDITION and u.name =SCHEMA_NAME_CONDITION +order by c.column_id +`; diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/index.js b/plugins/dbgate-plugin-mssql/src/backend/sql/index.js index 4125b1ebd..1676b4758 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/index.js @@ -13,6 +13,7 @@ const viewColumns = require('./viewColumns'); const indexes = require('./indexes'); const indexcols = require('./indexcols'); const triggers = require('./triggers'); +const baseColumns = require('./baseColumns'); module.exports = { columns, @@ -30,4 +31,5 @@ module.exports = { indexcols, tableSizes, triggers, + baseColumns, }; diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index 66ddde21d..fd731ab64 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -168,21 +168,34 @@ class MsSqlDumper extends SqlDumper { changeColumnComment(oldcol, newcol) { if (oldcol.columnComment === newcol.columnComment) return; - if (oldcol.columnComment) this.dropColumnComment(newcol); + if (oldcol.columnComment) this.dropColumnCommentIfExists(newcol); if (newcol.columnComment) this.createColumnComment(newcol); } /** * @param {import('dbgate-types').ColumnInfo} column */ - dropColumnComment(column) { + dropColumnCommentIfExists(column) { const { schemaName, columnName, pureName } = column; + const fullName = `${schemaName && schemaName + '.'}${pureName}`; + this.put('&>^if ^exists (&n'); + this.put('&>^select 1 ^from sys.extended_properties&n'); + this.put("^where major_id = OBJECT_ID('%s')&n", fullName); + this.put( + "^and minor_id = (^select column_id ^from sys.columns ^where object_id = OBJECT_ID('%s') ^and name = '%s')&n", + fullName, + columnName + ); + this.put("^and name = N'MS_Description'&<&<&n"); + this.put(')&n'); + this.put('&>^begin&n'); this.put('&>^exec sp_dropextendedproperty&n'); - this.put("@name = N'MS_Description',"); + this.put("@name = N'MS_Description',&n"); this.put("@level0type = N'SCHEMA', @level0name = '%s',&n", schemaName); this.put("@level1type = N'TABLE', @level1name = '%s',&n", pureName); - this.put("@level2type = N'COLUMN', @level2name = '%s'&<", columnName); + this.put("@level2type = N'COLUMN', @level2name = '%s'&<&n", columnName); + this.put('^end'); this.endCommand(); } From bfafcb76ba5ea5e82b2bae26fcc7f35c89b8325a Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 7 Aug 2025 14:17:28 +0200 Subject: [PATCH 14/26] fix: use only cols with comments for obj hash --- .../src/backend/MsSqlAnalyser.js | 13 +++++++------ .../src/backend/sql/baseColumns.js | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index c818061a2..5d394d90e 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -90,7 +90,7 @@ function getColumnInfo({ /** * @param {ReturnType} fieldType * @param {any} item - * @param {Array<{ objectId: string; columnComment: string }>} columns + * @param {Array<{ objectId: string; columnId: number, columnComment: string }>} columns * @returns {string|null} */ function createObjectContentHash(fieldType, item, columns) { @@ -100,12 +100,13 @@ function createObjectContentHash(fieldType, item, columns) { if ((columns?.length && fieldType === 'tables') || fieldType === 'views') { const modifyDateStr = modifyDate ? modifyDate.toISOString() : ''; const objectColumns = columns.filter(col => col.objectId == item.objectId); - const colsComments = objectColumns.map(i => i.columnComment).join(','); + 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) - .digest('hex'); + return crypto.createHash('sha256').update(`${modifyDateStr}:${colsComments}:${objectComment}`).digest('hex'); } if (!modifyDate) return null; diff --git a/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js b/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js index 497d48b77..389f2f747 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js +++ b/plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js @@ -1,10 +1,11 @@ module.exports = ` select c.object_id as objectId, - ep.value as columnComment + 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 -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' +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 `; From 21641da0bf19e51d20af352e77fadabc6de8c2ae Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 7 Aug 2025 15:32:24 +0200 Subject: [PATCH 15/26] feat: safeCommentChanges flag to dialect --- packages/tools/src/alterPlan.ts | 59 +++++++++++++------ packages/types/dialect.d.ts | 2 + .../src/frontend/driver.js | 2 + 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index d049fa464..5462efab8 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -292,6 +292,16 @@ export class AlterPlan { } } + _hasOnlyCommentChange(op: AlterOperation): boolean { + if (op.operationType === 'changeColumn') { + return _.isEqual( + _.omit(op.oldObject, ['columnComment', 'ordinal']), + _.omit(op.newObject, ['columnComment', 'ordinal']) + ); + } + return false; + } + _getDependendColumnConstraints(column: ColumnInfo, dependencyDefinition) { const table = this.wholeOldDb.tables.find(x => x.pureName == column.pureName && x.schemaName == column.schemaName); if (!table) return []; @@ -337,31 +347,42 @@ export class AlterPlan { ]) { if (op.operationType == testedOperationType) { const constraints = this._getDependendColumnConstraints(testedObject as ColumnInfo, testedDependencies); + const ignoreContraints = this.dialect.safeCommentChanges && this._hasOnlyCommentChange(op); // if (constraints.length > 0 && this.opts.noDropConstraint) { // return []; // } - const res: AlterOperation[] = [ - ...constraints.map(oldObject => { - const opRes: AlterOperation = { - operationType: 'dropConstraint', - oldObject, - isRecreate: true, - }; - return opRes; - }), - op, - ..._.reverse([...constraints]).map(newObject => { - const opRes: AlterOperation = { - operationType: 'createConstraint', - newObject, - }; - return opRes; - }), - ]; + const res: AlterOperation[] = []; - if (constraints.length > 0) { + if (!ignoreContraints) { + res.push( + ...constraints.map(oldObject => { + const opRes: AlterOperation = { + operationType: 'dropConstraint', + oldObject, + isRecreate: true, + }; + return opRes; + }) + ); + } + + res.push(op); + + if (!ignoreContraints) { + res.push( + ..._.reverse([...constraints]).map(newObject => { + const opRes: AlterOperation = { + operationType: 'createConstraint', + newObject, + }; + return opRes; + }) + ); + } + + if (!ignoreContraints && constraints.length > 0) { this.recreates.constraints += 1; } return res; diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index da85ac967..b06a30071 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -80,6 +80,8 @@ export interface SqlDialect { isPersisted?: true; }; + safeCommentChanges?: boolean; + // create sql-tree expression createColumnViewExpression( columnName: string, diff --git a/plugins/dbgate-plugin-mssql/src/frontend/driver.js b/plugins/dbgate-plugin-mssql/src/frontend/driver.js index 91d36b12d..137a63fbc 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/driver.js @@ -50,6 +50,8 @@ const dialect = { isPersisted: true, }, + safeCommentChanges: true, + predefinedDataTypes: [ 'bigint', 'bit', From 971af1df5f91713bf702993259b2b07005207fa3 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 12 Aug 2025 15:44:22 +0200 Subject: [PATCH 16/26] fix: use baseColumns for tables hashes in _runAnalysis --- plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js index 5d394d90e..9025808a5 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js +++ b/plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js @@ -136,6 +136,8 @@ class MsSqlAnalyser extends DatabaseAnalyser { 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' }); @@ -174,7 +176,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { this.feedback({ analysingMessage: 'DBGM-00217 Finalizing DB structure' }); const tables = tablesRows.rows.map(row => ({ ...row, - contentHash: createObjectContentHash('tables', row, columns), + 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), @@ -203,7 +205,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { const views = viewsRows.rows.map(row => ({ ...row, - contentHash: createObjectContentHash('views', row, columns), + contentHash: createObjectContentHash('views', row, baseColumns), createSql: getCreateSql(row), columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo), })); From 1dbfa71bdeb3bca673f2e13e0f01d0112913b02c Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Wed, 13 Aug 2025 07:21:34 +0200 Subject: [PATCH 17/26] v6.6.1-beta.16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48fcdb715..84bd66e2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "6.6.1-premium-beta.15", + "version": "6.6.1-beta.16", "name": "dbgate-all", "workspaces": [ "packages/*", From d49345de9c29c3f3e009b3df011c90b5254eb854 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Wed, 13 Aug 2025 07:22:06 +0200 Subject: [PATCH 18/26] v6.6.1-beta.18 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84bd66e2d..f239cc78d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "6.6.1-beta.16", + "version": "6.6.1-beta.18", "name": "dbgate-all", "workspaces": [ "packages/*", From 7913c4135f7de85914c8b78cef3c854bafc37ff4 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Wed, 13 Aug 2025 07:22:17 +0200 Subject: [PATCH 19/26] v6.6.1-premium-beta.18 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f239cc78d..e28bec70a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "6.6.1-beta.18", + "version": "6.6.1-premium-beta.18", "name": "dbgate-all", "workspaces": [ "packages/*", From de5f3a31edd8e39d660712d13190116ba150aea6 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 13 Aug 2025 14:47:59 +0200 Subject: [PATCH 20/26] fix: fetch oracle blobs as buffer --- plugins/dbgate-plugin-oracle/src/backend/driver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/dbgate-plugin-oracle/src/backend/driver.js b/plugins/dbgate-plugin-oracle/src/backend/driver.js index fda8e074a..0fb226a0d 100644 --- a/plugins/dbgate-plugin-oracle/src/backend/driver.js +++ b/plugins/dbgate-plugin-oracle/src/backend/driver.js @@ -13,6 +13,7 @@ function getOracledb() { if (!oracledbValue) { oracledbValue = require('oracledb'); oracledbValue.fetchAsString = [oracledbValue.CLOB, oracledbValue.NCLOB]; + oracledbValue.fetchAsBuffer = [oracledbValue.BLOB]; } return oracledbValue; } From c5d7e30bed80c5d9cc5d383833e0e6c4246734f9 Mon Sep 17 00:00:00 2001 From: daujyungx Date: Thu, 14 Aug 2025 15:50:46 +0800 Subject: [PATCH 21/26] fix: #340, SQL Server tedious driver connect to named instance --- plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js b/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js index 5f531c7ab..e5ec77db8 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js +++ b/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js @@ -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, }); From 30dbb233300bbc2ab6cc364cf6f0f73a7a8205ed Mon Sep 17 00:00:00 2001 From: "SPRINX0\\prochazka" Date: Thu, 14 Aug 2025 17:05:56 +0200 Subject: [PATCH 22/26] fixed potential error --- plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js b/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js index e5ec77db8..f4a8a4cfa 100644 --- a/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js +++ b/plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js @@ -66,7 +66,7 @@ async function tediousConnect(storedConnection) { const authentication = await getAuthentication(storedConnection); return new Promise((resolve, reject) => { - const [host, instance] = server.split('\\'); + const [host, instance] = (server || '').split('\\'); const connectionOptions = { instanceName: instance, encrypt: !!ssl || authType == 'msentra' || authType == 'azureManagedIdentity', From 9b7021b1cd168d2bab94c1e4dbe602507837ec5b Mon Sep 17 00:00:00 2001 From: "SPRINX0\\prochazka" Date: Thu, 14 Aug 2025 17:06:20 +0200 Subject: [PATCH 23/26] v6.6.1-beta.19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e28bec70a..7720a691f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "6.6.1-premium-beta.18", + "version": "6.6.1-beta.19", "name": "dbgate-all", "workspaces": [ "packages/*", From 88c74f020c25a116209d5e95e6d50de0fb46e940 Mon Sep 17 00:00:00 2001 From: "SPRINX0\\prochazka" Date: Thu, 14 Aug 2025 17:29:59 +0200 Subject: [PATCH 24/26] changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c2f15d7..915b1cb47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ Builds: - linux - application for linux - win - application for Windows +## 6.6.1 +- ADDED: Support for Mongo shell (Premium) - #1114 +- FIXED: Support for BLOB in Oracle #1181 +- ADDED: Connect to named SQL Server instance #340 +- ADDED: Support for SQL Server descriptions #1137 +- ADDED: Application log viewer +- FIXED: Selecting default database in connection dialog +- CHANGED: Improved logging system, added related database and connection to logs metadata + ## 6.6.0 - ADDED: Database chat - AI powered chatbot, which knows your database (Premium) - ADDED: Firestore support (Premium) From 354d925f94a4a1349b35e810e0c12c3367a621a4 Mon Sep 17 00:00:00 2001 From: "SPRINX0\\prochazka" Date: Thu, 14 Aug 2025 17:30:52 +0200 Subject: [PATCH 25/26] v6.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7720a691f..240a4d409 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "6.6.1-beta.19", + "version": "6.6.1", "name": "dbgate-all", "workspaces": [ "packages/*", From be4fe6ab77bbbc79f7d51323a4cfcce7d2844877 Mon Sep 17 00:00:00 2001 From: "SPRINX0\\prochazka" Date: Tue, 19 Aug 2025 12:26:31 +0200 Subject: [PATCH 26/26] removed obsolete method --- packages/api/src/controllers/storage.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/api/src/controllers/storage.js b/packages/api/src/controllers/storage.js index f839bd01b..d69323eb9 100644 --- a/packages/api/src/controllers/storage.js +++ b/packages/api/src/controllers/storage.js @@ -13,10 +13,6 @@ module.exports = { return null; }, - async loadSuperadminPermissions() { - return []; - }, - getConnectionsForLoginPage_meta: true, async getConnectionsForLoginPage() { return null;