🐛 fix(oracle): 修复表结构注释读取与保存报错

- 补齐 Oracle 表字段注释元数据读取

- 在表结构 DDL 中追加表和字段注释信息

- 规范表设计器 Oracle DDL 执行前的分号处理

Refs #482
This commit is contained in:
Syngnat
2026-05-23 17:41:46 +08:00
parent b9c743d67e
commit 56b3112a07
6 changed files with 310 additions and 22 deletions

View File

@@ -11,6 +11,7 @@ import { DBGetColumns, DBGetIndexes, DBQuery, DBGetForeignKeys, DBGetTriggers, D
import { hasIndexFormChanged, normalizeIndexFormFromRow, shouldRestoreOriginalIndex, toggleIndexSelection as getNextIndexSelection, type IndexDisplaySnapshot } from './tableDesignerIndexUtils';
import { buildIndexCreateSqlPreview } from './tableDesignerIndexSql';
import { buildAlterTablePreviewSql, buildCreateTablePreviewSql, hasAlterTableDraftChanges, type StarRocksCreateTableOptions, type StarRocksDistributionType, type StarRocksKeyModel, type StarRocksTableKind } from './tableDesignerSchemaSql';
import { normalizeSchemaStatementForExecution, parseTableCommentFromDDL, splitSchemaExecutionStatements } from './tableDesignerExecutionSql';
import TableDesignerSqlPreview from './TableDesignerSqlPreview';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import { noAutoCapInputProps } from '../utils/inputAutoCap';
@@ -832,8 +833,7 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
if (ddlRes && ddlRes.success) {
const ddlText = String(ddlRes.data || '');
setDdl(ddlText);
const commentMatch = ddlText.replace(/\r?\n/g, ' ').match(/COMMENT\s*=\s*'((?:\\'|''|[^'])*)'/i);
const parsedTableComment = commentMatch ? commentMatch[1].replace(/\\'/g, "'").replace(/''/g, "'") : '';
const parsedTableComment = parseTableCommentFromDDL(ddlText);
setTableComment(parsedTableComment);
if (!isTableCommentModalOpen) {
setTableCommentDraft(parsedTableComment);
@@ -1617,10 +1617,10 @@ ${selectedTrigger.statement}`;
useSSH: conn.config.useSSH || false,
ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" }
};
const statements = sqlText.split(/;\s*\n/).map(s => s.trim()).filter(Boolean);
const dbType = resolveTableInfo().dbType;
const statements = splitSchemaExecutionStatements(sqlText);
for (let i = 0; i < statements.length; i++) {
let stmt = statements[i];
if (!stmt.endsWith(';')) stmt += ';';
const stmt = normalizeSchemaStatementForExecution(statements[i], dbType);
const res = await DBQuery(buildRpcConnectionConfig(config) as any, tab.dbName || '', stmt);
if (!res.success) {
const prefix = statements.length > 1 ? `${i + 1}/${statements.length} 条语句执行失败: ` : '执行失败: ';

View File

@@ -0,0 +1,33 @@
import { describe, expect, it } from 'vitest';
import {
normalizeSchemaStatementForExecution,
parseTableCommentFromDDL,
splitSchemaExecutionStatements,
} from './tableDesignerExecutionSql';
describe('tableDesignerExecutionSql', () => {
it('strips trailing semicolons before executing oracle schema statements', () => {
expect(
normalizeSchemaStatementForExecution(`COMMENT ON COLUMN "H2"."D_YS_MEMCARD_CX"."ID" IS 'ID';`, 'oracle'),
).toBe(`COMMENT ON COLUMN "H2"."D_YS_MEMCARD_CX"."ID" IS 'ID'`);
});
it('keeps trailing semicolons for non-oracle schema statements', () => {
expect(normalizeSchemaStatementForExecution('ALTER TABLE `users` ADD COLUMN `age` int', 'mysql'))
.toBe('ALTER TABLE `users` ADD COLUMN `age` int;');
});
it('splits generated schema SQL into individual statements', () => {
expect(splitSchemaExecutionStatements('ALTER TABLE users ADD age int;\nCOMMENT ON COLUMN users.age IS \'年龄\';'))
.toEqual(['ALTER TABLE users ADD age int', "COMMENT ON COLUMN users.age IS '年龄';"]);
});
it('parses mysql and oracle table comments from DDL', () => {
expect(parseTableCommentFromDDL("CREATE TABLE `users` (`id` int) COMMENT='用户\\'表';"))
.toBe("用户'表");
expect(parseTableCommentFromDDL(`CREATE TABLE "HR"."EMPLOYEES" ("ID" NUMBER);
COMMENT ON TABLE "HR"."EMPLOYEES" IS '员工''表';
COMMENT ON COLUMN "HR"."EMPLOYEES"."ID" IS '主键';`)).toBe("员工'表");
});
});

View File

@@ -0,0 +1,37 @@
import { isOracleLikeDialect } from '../utils/sqlDialect';
export const splitSchemaExecutionStatements = (sqlText: string): string[] => (
String(sqlText || '')
.replace(//g, ';')
.split(/;\s*\n/)
.map(statement => statement.trim())
.filter(Boolean)
);
export const normalizeSchemaStatementForExecution = (statement: string, dbType: string): string => {
const trimmed = String(statement || '').trim();
if (!trimmed) return '';
if (isOracleLikeDialect(dbType)) {
return trimmed.replace(/;+\s*$/, '').trim();
}
return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
};
const unescapeSqlComment = (text: string, mysqlBackslashEscapes = false): string => {
const unescaped = text.replace(/''/g, "'");
return mysqlBackslashEscapes ? unescaped.replace(/\\'/g, "'") : unescaped;
};
export const parseTableCommentFromDDL = (ddlText: string): string => {
const ddl = String(ddlText || '').replace(/\r?\n/g, ' ');
const mysqlMatch = ddl.match(/COMMENT\s*=\s*'((?:\\'|''|[^'])*)'/i);
if (mysqlMatch) {
return unescapeSqlComment(mysqlMatch[1], true);
}
const commentOnTableMatch = ddl.match(/\bCOMMENT\s+ON\s+TABLE\s+.+?\s+IS\s+(NULL|'((?:''|[^'])*)')/i);
if (!commentOnTableMatch || commentOnTableMatch[1].toUpperCase() === 'NULL') {
return '';
}
return unescapeSqlComment(commentOnTableMatch[2] || '');
};