Compare commits

...

12 Commits

Author SHA1 Message Date
Pavel
4f2d94069b v6.6.1-beta.16 2025-08-05 14:34:34 +02:00
Pavel
ba303c5b41 feat: error mode warn 2025-08-05 14:34:16 +02:00
Pavel
74c4586be3 feat: svelte5 2025-08-05 14:10:16 +02:00
Pavel
d4e8d1026d v6.6.1-beta.15 2025-08-05 12:46:15 +02:00
Pavel
3d6722ebfa fix: wrap <tr> into <tbody> 2025-08-01 01:31:14 +02:00
Pavel
2d28046149 feat: updgrade svlete to v4 2025-08-01 01:24:59 +02:00
Pavel
0e91437e59 feat: add add comment to table teest 2025-07-31 18:35:30 +02:00
Pavel
c67ca42f57 feat: add add comment to column test 2025-07-31 18:30:20 +02:00
Pavel
c391a675f0 feat: add test for table creation with comments 2025-07-31 17:47:12 +02:00
Pavel
d5d182a9db fix:fix: removeu duplicate method, simplify changeColumnComment 2025-07-31 17:46:51 +02:00
Pavel
63de984d76 fix: correctly parse table comment when creating a table 2025-07-31 16:32:21 +02:00
Pavel
8779285408 feat: ms_description for tables, upd columns 2025-07-31 15:01:45 +02:00
14 changed files with 364 additions and 902 deletions

View File

@@ -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(
createEnginesColumnsSource(engines.filter(x => !x.skipDropColumn)).filter(
([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk')

View File

@@ -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) => {

View File

@@ -443,6 +443,8 @@ const sqlServerEngine = {
supportSchemas: true,
supportRenameSqlObject: true,
defaultSchemaName: 'dbo',
supportTableComments: true,
supportColumnComments: true,
// skipSeparateSchemas: true,
triggers: [
{

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "6.6.1-beta.7",
"version": "6.6.1-beta.16",
"name": "dbgate-all",
"workspaces": [
"packages/*",

View File

@@ -74,6 +74,12 @@ export interface SqlDialect {
predefinedDataTypes: string[];
columnProperties?: {
columnName?: boolean;
isSparse?: true;
isPersisted?: true;
};
// create sql-tree expression
createColumnViewExpression(
columnName: string,

View File

@@ -56,6 +56,9 @@ export type TestEngineInfo = {
useTextTypeForStrings?: boolean;
supportTableComments?: boolean;
supportColumnComments?: boolean;
supportRenameSqlObject?: boolean;
supportSchemas?: boolean;

View File

@@ -46,11 +46,11 @@
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"rollup-plugin-svelte": "^7.2.2",
"rollup-plugin-terser": "^7.0.2",
"sirv-cli": "^1.0.0",
"sql-formatter": "^3.1.0",
"svelte": "^3.46.4",
"svelte": "^5.37.3",
"svelte-check": "^1.0.0",
"svelte-markdown": "^0.1.4",
"svelte-preprocess": "^4.9.5",

View File

@@ -88,6 +88,7 @@ export default [
compilerOptions: {
// enable run-time checks when not in production
dev: !production,
errorMode: 'warn',
},
onwarn: (warning, handler) => {
const ignoreWarnings = [

View File

@@ -13,20 +13,22 @@
<div>
<table>
<tr>
<td
use:resizeObserver={true}
on:resize={e => {
// @ts-ignore
$dataGridRowHeight = e.detail.height + 1;
}}
>
title
<InlineButton square>
<FontIcon icon="icon chevron-down" />
</InlineButton>
</td>
</tr>
<tbody>
<tr>
<td
use:resizeObserver={true}
on:resize={e => {
// @ts-ignore
$dataGridRowHeight = e.detail.height + 1;
}}
>
title
<InlineButton square>
<FontIcon icon="icon chevron-down" />
</InlineButton>
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -5,6 +5,8 @@ const Analyser = require('./Analyser');
const isPromise = require('is-promise');
const { MongoClient, ObjectId, AbstractCursor, Long } = require('mongodb');
const { EJSON } = require('bson');
const { NodeDriverServiceProvider } = require('@mongosh/service-provider-node-driver');
const { ElectronRuntime } = require('@mongosh/browser-runtime-electron');
const { serializeJsTypesForJsonStringify, deserializeJsTypesFromJsonParse } = require('dbgate-tools');
const createBulkInsertStream = require('./createBulkInsertStream');
const {
@@ -30,24 +32,10 @@ function serializeMongoData(row) {
);
}
async function readCursor(cursor, options) {
options.recordset({ __isDynamicStructure: true });
await cursor.forEach((row) => {
options.row(serializeMongoData(row));
});
}
function deserializeMongoData(value) {
return deserializeJsTypesFromJsonParse(EJSON.deserialize(value));
}
function findArrayResult(resValue) {
if (!_.isPlainObject(resValue)) return null;
const arrays = _.values(resValue).filter((x) => _.isArray(x));
if (arrays.length == 1) return arrays[0];
return null;
}
async function getScriptableDb(dbhan) {
const db = dbhan.getDatabase();
db.getCollection = (name) => db.collection(name);
@@ -123,7 +111,7 @@ const driver = {
};
},
// @ts-ignore
async query(dbhan, sql) {
async query(_dbhan, _sql) {
return {
rows: [],
columns: [],
@@ -158,7 +146,7 @@ const driver = {
if (isPromise(res)) await res;
}
},
async operation(dbhan, operation, options) {
async operation(dbhan, operation, _options) {
const { type } = operation;
switch (type) {
case 'createCollection':
@@ -357,7 +345,7 @@ const driver = {
const db = await getScriptableDb(dbhan);
await db.command({ profile: old.was, slowms: old.slowms });
},
async readQuery(dbhan, sql, structure) {
async readQuery(dbhan, sql, _structure) {
try {
const json = JSON.parse(sql);
if (json && json.pureName) {
@@ -498,7 +486,7 @@ const driver = {
res.replaced.push(resdoc._id);
}
} else {
const set = deserializeMongoData(_.pickBy(update.fields, (v, k) => !v?.$$undefined$$));
const set = deserializeMongoData(_.pickBy(update.fields, (v, _k) => !v?.$$undefined$$));
const unset = _.fromPairs(
Object.keys(update.fields)
.filter((k) => update.fields[k]?.$$undefined$$)
@@ -581,7 +569,7 @@ const driver = {
}
},
readJsonQuery(dbhan, select, structure) {
readJsonQuery(dbhan, select, _structure) {
const { collection, condition, sort } = select;
const db = dbhan.getDatabase();

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

@@ -124,49 +124,95 @@ 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 || 'dbo');
this.put("@level1type = N'TABLE', @level1name = '%s&<'", pureName);
this.endCommand();
}
/**
* @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);
}
changeColumnComment(oldcol, newcol) {
if (oldcol.columnComment === newcol.columnComment) return;
if (oldcol.columnComment) this.dropColumnComment(newcol);
if (newcol.columnComment) 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("@name = N'MS_Description',");
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();
}
/**
* @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);
@@ -184,7 +230,7 @@ class MsSqlDumper extends SqlDumper {
this.createDefault(newcol);
}
this.changeColumnDescription(oldcol, newcol);
this.changeColumnComment(oldcol, newcol);
}
specialColumnOptions(column) {
@@ -208,6 +254,44 @@ 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
* @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 => {

View File

@@ -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} */

989
yarn.lock

File diff suppressed because it is too large Load Diff