mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-12 17:39:42 +08:00
🐛 fix(oracle): 修复表结构注释读取与保存报错
- 补齐 Oracle 表字段注释元数据读取 - 在表结构 DDL 中追加表和字段注释信息 - 规范表设计器 Oracle DDL 执行前的分号处理 Refs #482
This commit is contained in:
@@ -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} 条语句执行失败: ` : '执行失败: ';
|
||||
|
||||
33
frontend/src/components/tableDesignerExecutionSql.test.ts
Normal file
33
frontend/src/components/tableDesignerExecutionSql.test.ts
Normal 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("员工'表");
|
||||
});
|
||||
});
|
||||
37
frontend/src/components/tableDesignerExecutionSql.ts
Normal file
37
frontend/src/components/tableDesignerExecutionSql.ts
Normal 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] || '');
|
||||
};
|
||||
Reference in New Issue
Block a user