mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 18:03:58 +00:00
Merge pull request #1171 from dbgate/feature/1137-mssql-column-desc
feat: add MS_Description to mssql analyzer columns
This commit is contained in:
@@ -118,6 +118,31 @@ 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) => {
|
||||||
|
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(
|
test.each(
|
||||||
createEnginesColumnsSource(engines.filter(x => !x.skipDropColumn)).filter(
|
createEnginesColumnsSource(engines.filter(x => !x.skipDropColumn)).filter(
|
||||||
([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk')
|
([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk')
|
||||||
|
|||||||
@@ -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]))(
|
test.each(engines.filter(x => !x.skipIndexes).map(engine => [engine.label, engine]))(
|
||||||
'Table with index - %s',
|
'Table with index - %s',
|
||||||
testWrapper(async (conn, driver, engine) => {
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
|||||||
@@ -443,6 +443,8 @@ const sqlServerEngine = {
|
|||||||
supportSchemas: true,
|
supportSchemas: true,
|
||||||
supportRenameSqlObject: true,
|
supportRenameSqlObject: true,
|
||||||
defaultSchemaName: 'dbo',
|
defaultSchemaName: 'dbo',
|
||||||
|
supportTableComments: true,
|
||||||
|
supportColumnComments: true,
|
||||||
// skipSeparateSchemas: true,
|
// skipSeparateSchemas: true,
|
||||||
triggers: [
|
triggers: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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) {
|
_getDependendColumnConstraints(column: ColumnInfo, dependencyDefinition) {
|
||||||
const table = this.wholeOldDb.tables.find(x => x.pureName == column.pureName && x.schemaName == column.schemaName);
|
const table = this.wholeOldDb.tables.find(x => x.pureName == column.pureName && x.schemaName == column.schemaName);
|
||||||
if (!table) return [];
|
if (!table) return [];
|
||||||
@@ -337,31 +347,42 @@ export class AlterPlan {
|
|||||||
]) {
|
]) {
|
||||||
if (op.operationType == testedOperationType) {
|
if (op.operationType == testedOperationType) {
|
||||||
const constraints = this._getDependendColumnConstraints(testedObject as ColumnInfo, testedDependencies);
|
const constraints = this._getDependendColumnConstraints(testedObject as ColumnInfo, testedDependencies);
|
||||||
|
const ignoreContraints = this.dialect.safeCommentChanges && this._hasOnlyCommentChange(op);
|
||||||
|
|
||||||
// if (constraints.length > 0 && this.opts.noDropConstraint) {
|
// if (constraints.length > 0 && this.opts.noDropConstraint) {
|
||||||
// return [];
|
// return [];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const res: AlterOperation[] = [
|
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;
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
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;
|
this.recreates.constraints += 1;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export interface DbDiffOptions {
|
|||||||
|
|
||||||
ignoreForeignKeyActions?: boolean;
|
ignoreForeignKeyActions?: boolean;
|
||||||
ignoreDataTypes?: boolean;
|
ignoreDataTypes?: boolean;
|
||||||
|
ignoreComments?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateTablePairingId(table: TableInfo): TableInfo {
|
export function generateTablePairingId(table: TableInfo): TableInfo {
|
||||||
@@ -322,11 +323,14 @@ export function testEqualColumns(
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((a.columnComment || '') != (b.columnComment || '')) {
|
|
||||||
console.debug(
|
if (!opts.ignoreComments) {
|
||||||
`Column ${a.pureName}.${a.columnName}, ${b.pureName}.${b.columnName}: different comment: ${a.columnComment}, ${b.columnComment}`
|
if ((a.columnComment || '') != (b.columnComment || '')) {
|
||||||
);
|
console.debug(
|
||||||
return false;
|
`Column ${a.pureName}.${a.columnName}, ${b.pureName}.${b.columnName}: different comment: ${a.columnComment}, ${b.columnComment}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!testEqualTypes(a, b, opts)) {
|
if (!testEqualTypes(a, b, opts)) {
|
||||||
|
|||||||
8
packages/types/dialect.d.ts
vendored
8
packages/types/dialect.d.ts
vendored
@@ -74,6 +74,14 @@ export interface SqlDialect {
|
|||||||
|
|
||||||
predefinedDataTypes: string[];
|
predefinedDataTypes: string[];
|
||||||
|
|
||||||
|
columnProperties?: {
|
||||||
|
columnName?: boolean;
|
||||||
|
isSparse?: true;
|
||||||
|
isPersisted?: true;
|
||||||
|
};
|
||||||
|
|
||||||
|
safeCommentChanges?: boolean;
|
||||||
|
|
||||||
// create sql-tree expression
|
// create sql-tree expression
|
||||||
createColumnViewExpression(
|
createColumnViewExpression(
|
||||||
columnName: string,
|
columnName: string,
|
||||||
|
|||||||
3
packages/types/test-engines.d.ts
vendored
3
packages/types/test-engines.d.ts
vendored
@@ -56,6 +56,9 @@ export type TestEngineInfo = {
|
|||||||
|
|
||||||
useTextTypeForStrings?: boolean;
|
useTextTypeForStrings?: boolean;
|
||||||
|
|
||||||
|
supportTableComments?: boolean;
|
||||||
|
supportColumnComments?: boolean;
|
||||||
|
|
||||||
supportRenameSqlObject?: boolean;
|
supportRenameSqlObject?: boolean;
|
||||||
supportSchemas?: boolean;
|
supportSchemas?: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const crypto = require('crypto');
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
|
|
||||||
const { DatabaseAnalyser, isTypeString, isTypeNumeric } = global.DBGATE_PACKAGES['dbgate-tools'];
|
const { DatabaseAnalyser, isTypeString, isTypeNumeric } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||||
@@ -54,6 +55,8 @@ function getColumnInfo({
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
defaultConstraint,
|
defaultConstraint,
|
||||||
computedExpression,
|
computedExpression,
|
||||||
|
columnComment,
|
||||||
|
objectId,
|
||||||
}) {
|
}) {
|
||||||
const fullDataType = getFullDataTypeName({
|
const fullDataType = getFullDataTypeName({
|
||||||
dataType,
|
dataType,
|
||||||
@@ -71,6 +74,7 @@ function getColumnInfo({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
objectId,
|
||||||
columnName,
|
columnName,
|
||||||
dataType: fullDataType,
|
dataType: fullDataType,
|
||||||
notNull: !isNullable,
|
notNull: !isNullable,
|
||||||
@@ -79,9 +83,36 @@ function getColumnInfo({
|
|||||||
defaultConstraint,
|
defaultConstraint,
|
||||||
computedExpression: simplifyComutedExpression(computedExpression),
|
computedExpression: simplifyComutedExpression(computedExpression),
|
||||||
hasAutoValue: !!(dataType == 'timestamp' || dataType == 'rowversion' || 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 {
|
class MsSqlAnalyser extends DatabaseAnalyser {
|
||||||
constructor(dbhan, driver, version) {
|
constructor(dbhan, driver, version) {
|
||||||
super(dbhan, driver, version);
|
super(dbhan, driver, version);
|
||||||
@@ -104,6 +135,9 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
const tablesRows = await this.analyserQuery('tables', ['tables']);
|
const tablesRows = await this.analyserQuery('tables', ['tables']);
|
||||||
this.feedback({ analysingMessage: 'DBGM-00206 Loading columns' });
|
this.feedback({ analysingMessage: 'DBGM-00206 Loading columns' });
|
||||||
const columnsRows = await this.analyserQuery('columns', ['tables']);
|
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' });
|
this.feedback({ analysingMessage: 'DBGM-00207 Loading primary keys' });
|
||||||
const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']);
|
const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']);
|
||||||
this.feedback({ analysingMessage: 'DBGM-00208 Loading foreign keys' });
|
this.feedback({ analysingMessage: 'DBGM-00208 Loading foreign keys' });
|
||||||
@@ -142,8 +176,8 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
this.feedback({ analysingMessage: 'DBGM-00217 Finalizing DB structure' });
|
this.feedback({ analysingMessage: 'DBGM-00217 Finalizing DB structure' });
|
||||||
const tables = tablesRows.rows.map(row => ({
|
const tables = tablesRows.rows.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
contentHash: createObjectContentHash('tables', row, baseColumns),
|
||||||
columns: columnsRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
|
columns: columns.filter(col => col.objectId == row.objectId),
|
||||||
primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows),
|
primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows),
|
||||||
foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows),
|
foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows),
|
||||||
indexes: indexesRows.rows
|
indexes: indexesRows.rows
|
||||||
@@ -171,7 +205,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
|
|
||||||
const views = viewsRows.rows.map(row => ({
|
const views = viewsRows.rows.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
contentHash: createObjectContentHash('views', row, baseColumns),
|
||||||
createSql: getCreateSql(row),
|
createSql: getCreateSql(row),
|
||||||
columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
|
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')
|
.filter(x => x.sqlObjectType.trim() == 'P')
|
||||||
.map(row => ({
|
.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
contentHash: createObjectContentHash('procedures', row),
|
||||||
createSql: getCreateSql(row),
|
createSql: getCreateSql(row),
|
||||||
parameters: prodceureToParameters[row.objectId],
|
parameters: prodceureToParameters[row.objectId],
|
||||||
}));
|
}));
|
||||||
@@ -213,14 +247,14 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
.filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
|
.filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
|
||||||
.map(row => ({
|
.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
contentHash: createObjectContentHash('functions', row),
|
||||||
createSql: getCreateSql(row),
|
createSql: getCreateSql(row),
|
||||||
parameters: functionToParameters[row.objectId],
|
parameters: functionToParameters[row.objectId],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const triggers = triggerRows.rows.map(row => ({
|
const triggers = triggerRows.rows.map(row => ({
|
||||||
objectId: `triggers:${row.objectId}`,
|
objectId: `triggers:${row.objectId}`,
|
||||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
contentHash: createObjectContentHash('triggers', row),
|
||||||
createSql: row.definition,
|
createSql: row.definition,
|
||||||
triggerTiming: row.triggerTiming,
|
triggerTiming: row.triggerTiming,
|
||||||
eventType: row.eventType,
|
eventType: row.eventType,
|
||||||
@@ -241,17 +275,19 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
|||||||
|
|
||||||
async _getFastSnapshot() {
|
async _getFastSnapshot() {
|
||||||
const modificationsQueryData = await this.analyserQuery('modifications');
|
const modificationsQueryData = await this.analyserQuery('modifications');
|
||||||
|
const baseColumnsRows = await this.analyserQuery('baseColumns', ['tables']);
|
||||||
|
const baseColumns = baseColumnsRows.rows;
|
||||||
const tableSizes = await this.analyserQuery('tableSizes');
|
const tableSizes = await this.analyserQuery('tableSizes');
|
||||||
|
|
||||||
const res = DatabaseAnalyser.createEmptyStructure();
|
const res = DatabaseAnalyser.createEmptyStructure();
|
||||||
for (const item of modificationsQueryData.rows) {
|
for (const item of modificationsQueryData.rows) {
|
||||||
const { type, objectId, modifyDate, schemaName, pureName } = item;
|
const { type, objectId, schemaName, pureName } = item;
|
||||||
const field = objectTypeToField(type);
|
const field = objectTypeToField(type);
|
||||||
if (!field || !res[field]) continue;
|
if (!field || !res[field]) continue;
|
||||||
|
|
||||||
res[field].push({
|
res[field].push({
|
||||||
objectId,
|
objectId,
|
||||||
contentHash: modifyDate && modifyDate.toISOString(),
|
contentHash: createObjectContentHash(field, item, baseColumns),
|
||||||
schemaName,
|
schemaName,
|
||||||
pureName,
|
pureName,
|
||||||
});
|
});
|
||||||
|
|||||||
11
plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js
Normal file
11
plugins/dbgate-plugin-mssql/src/backend/sql/baseColumns.js
Normal 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
|
||||||
|
`;
|
||||||
@@ -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_PRECISION as numericPrecision,
|
||||||
col.NUMERIC_SCALE as numericScale,
|
col.NUMERIC_SCALE as numericScale,
|
||||||
-- TODO only if version >= 2008
|
-- TODO only if version >= 2008
|
||||||
c.is_sparse as isSparse
|
c.is_sparse as isSparse,
|
||||||
|
ep.value as columnComment
|
||||||
from sys.columns c
|
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.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
|
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
|
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.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.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
|
where o.type = 'U' and o.object_id =OBJECT_ID_CONDITION and u.name =SCHEMA_NAME_CONDITION
|
||||||
order by c.column_id
|
order by c.column_id
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const viewColumns = require('./viewColumns');
|
|||||||
const indexes = require('./indexes');
|
const indexes = require('./indexes');
|
||||||
const indexcols = require('./indexcols');
|
const indexcols = require('./indexcols');
|
||||||
const triggers = require('./triggers');
|
const triggers = require('./triggers');
|
||||||
|
const baseColumns = require('./baseColumns');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
columns,
|
columns,
|
||||||
@@ -30,4 +31,5 @@ module.exports = {
|
|||||||
indexcols,
|
indexcols,
|
||||||
tableSizes,
|
tableSizes,
|
||||||
triggers,
|
triggers,
|
||||||
|
baseColumns,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
module.exports = `
|
module.exports = `
|
||||||
select
|
select
|
||||||
o.name as pureName, s.name as schemaName, o.object_id as objectId,
|
o.name as pureName,
|
||||||
o.create_date as createDate, o.modify_date as modifyDate
|
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
|
from sys.tables o
|
||||||
inner join sys.schemas s on o.schema_id = s.schema_id
|
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`;
|
||||||
|
|||||||
@@ -124,8 +124,110 @@ class MsSqlDumper extends SqlDumper {
|
|||||||
this.putCmd("^execute sp_rename '%f.%i', '%s', 'COLUMN'", column, column.columnName, newcol);
|
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 || 'dbo');
|
||||||
|
this.put("@level1type = N'TABLE', @level1name = '%s&<'", 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) this.dropColumnCommentIfExists(newcol);
|
||||||
|
if (newcol.columnComment) this.createColumnComment(newcol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('dbgate-types').ColumnInfo} 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',&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'&<&n", columnName);
|
||||||
|
this.put('^end');
|
||||||
|
this.endCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('dbgate-types').ColumnInfo} column
|
||||||
|
*/
|
||||||
|
createColumnComment(column) {
|
||||||
|
const { schemaName, columnName, pureName, columnComment } = column;
|
||||||
|
if (!columnComment) return;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('dbgate-types').TableInfo} table
|
||||||
|
*/
|
||||||
|
createTable(table) {
|
||||||
|
super.createTable(table);
|
||||||
|
|
||||||
|
for (const column of table.columns || []) {
|
||||||
|
this.createColumnComment(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changeColumn(oldcol, newcol, constraints) {
|
changeColumn(oldcol, newcol, constraints) {
|
||||||
if (testEqualColumns(oldcol, newcol, false, false)) {
|
if (testEqualColumns(oldcol, newcol, false, false, { ignoreComments: true })) {
|
||||||
this.dropDefault(oldcol);
|
this.dropDefault(oldcol);
|
||||||
if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName);
|
if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName);
|
||||||
this.createDefault(newcol);
|
this.createDefault(newcol);
|
||||||
@@ -140,6 +242,8 @@ class MsSqlDumper extends SqlDumper {
|
|||||||
this.endCommand();
|
this.endCommand();
|
||||||
this.createDefault(newcol);
|
this.createDefault(newcol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.changeColumnComment(oldcol, newcol);
|
||||||
}
|
}
|
||||||
|
|
||||||
specialColumnOptions(column) {
|
specialColumnOptions(column) {
|
||||||
@@ -163,6 +267,44 @@ class MsSqlDumper extends SqlDumper {
|
|||||||
this.put('^select ^scope_identity()');
|
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
|
||||||
|
* @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) {
|
callableTemplate(func) {
|
||||||
const putParameters = (parameters, delimiter) => {
|
const putParameters = (parameters, delimiter) => {
|
||||||
this.putCollection(delimiter, parameters || [], param => {
|
this.putCollection(delimiter, parameters || [], param => {
|
||||||
@@ -207,8 +349,8 @@ MsSqlDumper.prototype.changeProcedureSchema = MsSqlDumper.prototype.changeObject
|
|||||||
|
|
||||||
MsSqlDumper.prototype.renameFunction = MsSqlDumper.prototype.renameObject;
|
MsSqlDumper.prototype.renameFunction = MsSqlDumper.prototype.renameObject;
|
||||||
MsSqlDumper.prototype.changeFunctionSchema = MsSqlDumper.prototype.changeObjectSchema;
|
MsSqlDumper.prototype.changeFunctionSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||||
|
|
||||||
MsSqlDumper.prototype.renameTrigger = MsSqlDumper.prototype.renameObject;
|
MsSqlDumper.prototype.renameTrigger = MsSqlDumper.prototype.renameObject;
|
||||||
|
|
||||||
MsSqlDumper.prototype.changeTriggerSchema = MsSqlDumper.prototype.changeObjectSchema;
|
MsSqlDumper.prototype.changeTriggerSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||||
|
|
||||||
MsSqlDumper.prototype.renameTable = MsSqlDumper.prototype.renameObject;
|
MsSqlDumper.prototype.renameTable = MsSqlDumper.prototype.renameObject;
|
||||||
|
|||||||
@@ -45,10 +45,13 @@ const dialect = {
|
|||||||
namedDefaultConstraint: true,
|
namedDefaultConstraint: true,
|
||||||
|
|
||||||
columnProperties: {
|
columnProperties: {
|
||||||
|
columnComment: true,
|
||||||
isSparse: true,
|
isSparse: true,
|
||||||
isPersisted: true,
|
isPersisted: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
safeCommentChanges: true,
|
||||||
|
|
||||||
predefinedDataTypes: [
|
predefinedDataTypes: [
|
||||||
'bigint',
|
'bigint',
|
||||||
'bit',
|
'bit',
|
||||||
@@ -111,6 +114,18 @@ const dialect = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getTableFormOptions(intent) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Comment',
|
||||||
|
name: 'objectComment',
|
||||||
|
sqlFormatString: '^comment = %v',
|
||||||
|
allowEmptyValue: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {import('dbgate-types').EngineDriver} */
|
/** @type {import('dbgate-types').EngineDriver} */
|
||||||
|
|||||||
Reference in New Issue
Block a user