mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-05 18:11:32 +08:00
🐛 fix(frontend/ci): 修复对象修改卡顿与 Windows ARM 驱动校验失败
- QueryEditor 为对象修改标签增加 object-edit 轻量模式,跳过重型元数据抓取和对象装饰扫描 - DefinitionViewer 与 TriggerViewer 打开的对象修改标签统一透传 queryMode,避免重新进入普通查询链路 - TriggerViewer 补全 MySQL/Oracle 类触发器 DDL 重建逻辑,修复对象修改打开语法不完整 - 补充对象修改与触发器 DDL 回归测试,覆盖轻量模式和元数据补全场景 - verify-driver-agent-revisions 脚本改为跨架构校验,避免在 x64 runner 直接执行 windows/arm64 二进制 - 新增 Windows ARM CI 校验追踪文档,保留架构校验与 host-native probe 证据
This commit is contained in:
@@ -114,6 +114,7 @@ describe('DefinitionViewer object edit entry', () => {
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
query: expect.stringContaining('CREATE OR REPLACE VIEW reporting.active_users AS'),
|
||||
}));
|
||||
expect(storeState.addTab.mock.calls[0][0].query).toContain('SELECT id, name FROM users;');
|
||||
@@ -172,6 +173,7 @@ describe('DefinitionViewer object edit entry', () => {
|
||||
expect(storeState.addTab).toHaveBeenCalledWith(expect.objectContaining({
|
||||
title: '修改函数/存储过程: reporting.refresh_stats',
|
||||
type: 'query',
|
||||
queryMode: 'object-edit',
|
||||
query: expect.stringContaining('CREATE OR REPLACE FUNCTION reporting.refresh_stats()'),
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -612,6 +612,7 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
|
||||
connectionId: tab.connectionId,
|
||||
dbName,
|
||||
query,
|
||||
queryMode: 'object-edit',
|
||||
});
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
|
||||
@@ -1331,6 +1331,30 @@ describe('QueryEditor external SQL save', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('skips heavy autocomplete metadata fetch for object edit query tabs', async () => {
|
||||
autoFetchState.visible = true;
|
||||
backendApp.DBGetDatabases.mockResolvedValueOnce({ success: true, data: [{ Database: 'main' }, { Database: 'analytics' }] });
|
||||
|
||||
await act(async () => {
|
||||
create(<QueryEditor tab={createTab({
|
||||
query: 'CREATE OR REPLACE VIEW reporting.active_users AS SELECT * FROM users;',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
})} />);
|
||||
});
|
||||
await act(async () => {
|
||||
for (let i = 0; i < 6; i += 1) {
|
||||
await Promise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
expect(backendApp.DBGetDatabases).toHaveBeenCalledTimes(1);
|
||||
expect(backendApp.DBGetTables).not.toHaveBeenCalled();
|
||||
expect(backendApp.DBGetAllColumns).not.toHaveBeenCalled();
|
||||
expect(backendApp.DBQuery).not.toHaveBeenCalled();
|
||||
expect(editorState.editor.deltaDecorations).toHaveBeenCalledWith([], []);
|
||||
});
|
||||
|
||||
it('keeps the editor empty when a tab draft is externally synced to an empty query', async () => {
|
||||
let renderer!: ReactTestRenderer;
|
||||
|
||||
|
||||
@@ -1912,6 +1912,7 @@ const resolveQueryLocatorPlan = async ({
|
||||
const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isActive = true }) => {
|
||||
const [query, setQuery] = useState(getInitialEditorQuery(tab));
|
||||
const isExternalSQLFileTab = Boolean(String(tab.filePath || '').trim());
|
||||
const isObjectEditQueryTab = tab.type === 'query' && tab.queryMode === 'object-edit';
|
||||
|
||||
type ResultSet = {
|
||||
key: string;
|
||||
@@ -2127,6 +2128,11 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
return;
|
||||
}
|
||||
|
||||
if (isObjectEditQueryTab) {
|
||||
objectDecorationIdsRef.current = editor.deltaDecorations(objectDecorationIdsRef.current, []);
|
||||
return;
|
||||
}
|
||||
|
||||
const text = getQueryEditorDecorationModelTextIfLightweight(model, maxTextLength);
|
||||
if (text === null) {
|
||||
objectDecorationIdsRef.current = editor.deltaDecorations(objectDecorationIdsRef.current, []);
|
||||
@@ -2173,7 +2179,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
}
|
||||
|
||||
objectDecorationIdsRef.current = editor.deltaDecorations(objectDecorationIdsRef.current, decorations);
|
||||
}, []);
|
||||
}, [isObjectEditQueryTab]);
|
||||
|
||||
const showObjectInfoAtPosition = useCallback((position?: { lineNumber: number; column: number } | null) => {
|
||||
const editor = editorRef.current;
|
||||
@@ -2380,7 +2386,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
|
||||
// Fetch Metadata for Autocomplete (Cross-database)
|
||||
useEffect(() => {
|
||||
if (!autoFetchVisible) {
|
||||
if (!autoFetchVisible || isObjectEditQueryTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2584,7 +2590,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
refreshObjectDecorations();
|
||||
};
|
||||
void fetchMetadata();
|
||||
}, [autoFetchVisible, currentConnectionId, connections, dbList, isActive, refreshObjectDecorations]); // dbList 变化时触发重新加载
|
||||
}, [autoFetchVisible, currentConnectionId, connections, dbList, isActive, isObjectEditQueryTab, refreshObjectDecorations]); // dbList 变化时触发重新加载
|
||||
|
||||
// Query ID management helpers
|
||||
const setQueryId = (id: string) => {
|
||||
|
||||
@@ -112,6 +112,7 @@ describe('TriggerViewer object edit entry', () => {
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
query: expect.stringContaining('CREATE TRIGGER users_bi BEFORE INSERT'),
|
||||
}));
|
||||
});
|
||||
@@ -172,6 +173,99 @@ describe('TriggerViewer object edit entry', () => {
|
||||
expect(query).not.toContain('请补全 CREATE TRIGGER 语句');
|
||||
});
|
||||
|
||||
it('rebuilds mysql trigger ddl from metadata rows when only action statement is available', async () => {
|
||||
storeState.connections[0].config.type = 'mysql';
|
||||
backendApp.DBQuery.mockResolvedValue({
|
||||
success: true,
|
||||
data: [{
|
||||
TRIGGER_NAME: 'users_bi',
|
||||
TRIGGER_SCHEMA: 'main',
|
||||
EVENT_OBJECT_SCHEMA: 'audit',
|
||||
EVENT_OBJECT_TABLE: 'users',
|
||||
ACTION_TIMING: 'BEFORE',
|
||||
EVENT_MANIPULATION: 'INSERT',
|
||||
ACTION_ORIENTATION: 'ROW',
|
||||
ACTION_STATEMENT: 'SET NEW.created_at = NOW()',
|
||||
}],
|
||||
});
|
||||
|
||||
let renderer: any;
|
||||
await act(async () => {
|
||||
renderer = create(<TriggerViewer tab={tab} />);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const button = renderer.root.findAll((node: any) => node.type === 'button' && findButtonText(node).includes('对象修改'))[0];
|
||||
|
||||
await act(async () => {
|
||||
button.props.onClick();
|
||||
});
|
||||
|
||||
const query = storeState.addTab.mock.calls[0][0].query;
|
||||
expect(query).toContain('CREATE TRIGGER `main`.`users_bi`');
|
||||
expect(query).toContain('BEFORE INSERT ON `audit`.`users`');
|
||||
expect(query).toContain('FOR EACH ROW');
|
||||
expect(query).toContain('SET NEW.created_at = NOW();');
|
||||
expect(query).not.toContain('请补全 CREATE TRIGGER 语句');
|
||||
});
|
||||
|
||||
it('rebuilds oracle trigger ddl from metadata rows when body query returns fragments only', async () => {
|
||||
storeState.connections[0].config.type = 'oracle';
|
||||
backendApp.DBQuery
|
||||
.mockResolvedValueOnce({ success: true, data: [] })
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: [{
|
||||
OWNER: 'AUDIT',
|
||||
TABLE_OWNER: 'AUDIT',
|
||||
TABLE_NAME: 'USERS',
|
||||
TRIGGER_NAME: 'USERS_BU',
|
||||
TRIGGER_TYPE: 'BEFORE EACH ROW',
|
||||
TRIGGERING_EVENT: 'UPDATE',
|
||||
WHEN_CLAUSE: 'NEW.UPDATED_AT IS NULL',
|
||||
TRIGGER_BODY: 'BEGIN\n :NEW.UPDATED_AT := SYSDATE;\nEND;',
|
||||
}],
|
||||
})
|
||||
.mockResolvedValueOnce({ success: true, data: [] })
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: [{
|
||||
OWNER: 'AUDIT',
|
||||
TABLE_OWNER: 'AUDIT',
|
||||
TABLE_NAME: 'USERS',
|
||||
TRIGGER_NAME: 'USERS_BU',
|
||||
TRIGGER_TYPE: 'BEFORE EACH ROW',
|
||||
TRIGGERING_EVENT: 'UPDATE',
|
||||
WHEN_CLAUSE: 'NEW.UPDATED_AT IS NULL',
|
||||
TRIGGER_BODY: 'BEGIN\n :NEW.UPDATED_AT := SYSDATE;\nEND;',
|
||||
}],
|
||||
});
|
||||
|
||||
let renderer: any;
|
||||
await act(async () => {
|
||||
renderer = create(<TriggerViewer tab={{
|
||||
...tab,
|
||||
id: 'trigger-conn-1-main-audit.users_bu',
|
||||
title: '触发器: audit.users_bu',
|
||||
triggerName: 'audit.users_bu',
|
||||
}} />);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const button = renderer.root.findAll((node: any) => node.type === 'button' && findButtonText(node).includes('对象修改'))[0];
|
||||
|
||||
await act(async () => {
|
||||
button.props.onClick();
|
||||
});
|
||||
|
||||
const query = storeState.addTab.mock.calls[0][0].query;
|
||||
expect(query).toContain('CREATE OR REPLACE TRIGGER AUDIT.USERS_BU');
|
||||
expect(query).toContain('BEFORE UPDATE ON AUDIT.USERS');
|
||||
expect(query).toContain('FOR EACH ROW');
|
||||
expect(query).toContain('WHEN (NEW.UPDATED_AT IS NULL)');
|
||||
expect(query).toContain(':NEW.UPDATED_AT := SYSDATE;');
|
||||
});
|
||||
|
||||
it('reloads the latest trigger definition before opening object edit', async () => {
|
||||
backendApp.DBQuery
|
||||
.mockResolvedValueOnce({
|
||||
|
||||
@@ -19,6 +19,92 @@ const ensureSqlStatementTerminator = (sql: string): string => {
|
||||
return /;\s*$/.test(normalized) ? normalized : `${normalized};`;
|
||||
};
|
||||
|
||||
const getCaseInsensitiveRawValue = (row: Record<string, any>, keys: string[]): any => {
|
||||
const normalizedKeyMap = new Map<string, string>();
|
||||
Object.keys(row || {}).forEach((key) => normalizedKeyMap.set(key.toLowerCase(), key));
|
||||
for (const key of keys) {
|
||||
const matchedKey = normalizedKeyMap.get(String(key || '').toLowerCase());
|
||||
if (!matchedKey) continue;
|
||||
const value = row[matchedKey];
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const buildMySQLTriggerDDLFromMetadata = (
|
||||
row: Record<string, any>,
|
||||
fallbackTriggerName: string,
|
||||
fallbackTableName: string,
|
||||
): string => {
|
||||
const triggerName = String(
|
||||
getCaseInsensitiveRawValue(row, ['trigger_name', 'Trigger', 'TRIGGER_NAME'])
|
||||
|| splitQualifiedNameLast(fallbackTriggerName).objectName
|
||||
|| fallbackTriggerName,
|
||||
).trim();
|
||||
const triggerSchema = String(getCaseInsensitiveRawValue(row, ['trigger_schema', 'TRIGGER_SCHEMA']) || '').trim();
|
||||
const eventSchema = String(getCaseInsensitiveRawValue(row, ['event_object_schema', 'EVENT_OBJECT_SCHEMA']) || '').trim();
|
||||
const eventTable = String(
|
||||
getCaseInsensitiveRawValue(row, ['event_object_table', 'EVENT_OBJECT_TABLE', 'table_name', 'TABLE_NAME'])
|
||||
|| splitQualifiedNameLast(fallbackTableName).objectName
|
||||
|| fallbackTableName,
|
||||
).trim();
|
||||
const actionTiming = String(getCaseInsensitiveRawValue(row, ['action_timing', 'ACTION_TIMING']) || '').trim().toUpperCase();
|
||||
const eventManipulation = String(getCaseInsensitiveRawValue(row, ['event_manipulation', 'EVENT_MANIPULATION']) || '').trim().toUpperCase();
|
||||
const actionOrientation = String(getCaseInsensitiveRawValue(row, ['action_orientation', 'ACTION_ORIENTATION']) || '').trim().toUpperCase();
|
||||
const actionStatement = String(getCaseInsensitiveRawValue(row, ['action_statement', 'ACTION_STATEMENT']) || '').trim();
|
||||
|
||||
if (!triggerName || !eventTable || !actionTiming || !eventManipulation || !actionStatement) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const qualifiedTriggerName = triggerSchema ? `\`${triggerSchema.replace(/`/g, '``')}\`.\`${triggerName.replace(/`/g, '``')}\`` : `\`${triggerName.replace(/`/g, '``')}\``;
|
||||
const qualifiedTableName = eventSchema ? `\`${eventSchema.replace(/`/g, '``')}\`.\`${eventTable.replace(/`/g, '``')}\`` : `\`${eventTable.replace(/`/g, '``')}\``;
|
||||
const orientationClause = actionOrientation === 'ROW' ? '\nFOR EACH ROW' : '';
|
||||
return `CREATE TRIGGER ${qualifiedTriggerName}\n${actionTiming} ${eventManipulation} ON ${qualifiedTableName}${orientationClause}\n${actionStatement}`;
|
||||
};
|
||||
|
||||
const buildOracleLikeTriggerDDLFromMetadata = (
|
||||
row: Record<string, any>,
|
||||
fallbackTriggerName: string,
|
||||
fallbackTableName: string,
|
||||
): string => {
|
||||
const triggerName = String(
|
||||
getCaseInsensitiveRawValue(row, ['trigger_name', 'TRIGGER_NAME'])
|
||||
|| splitQualifiedNameLast(fallbackTriggerName).objectName
|
||||
|| fallbackTriggerName,
|
||||
).trim();
|
||||
const owner = String(getCaseInsensitiveRawValue(row, ['owner', 'OWNER']) || splitQualifiedNameLast(fallbackTriggerName).parentPath || '').trim();
|
||||
const tableOwner = String(getCaseInsensitiveRawValue(row, ['table_owner', 'TABLE_OWNER']) || splitQualifiedNameLast(fallbackTableName).parentPath || '').trim();
|
||||
const tableName = String(
|
||||
getCaseInsensitiveRawValue(row, ['table_name', 'TABLE_NAME'])
|
||||
|| splitQualifiedNameLast(fallbackTableName).objectName
|
||||
|| fallbackTableName,
|
||||
).trim();
|
||||
const triggerType = String(getCaseInsensitiveRawValue(row, ['trigger_type', 'TRIGGER_TYPE']) || '').trim();
|
||||
const triggeringEvent = String(getCaseInsensitiveRawValue(row, ['triggering_event', 'TRIGGERING_EVENT']) || '').trim();
|
||||
const whenClause = String(getCaseInsensitiveRawValue(row, ['when_clause', 'WHEN_CLAUSE']) || '').trim();
|
||||
const triggerBody = String(getCaseInsensitiveRawValue(row, ['trigger_body', 'TRIGGER_BODY']) || '').trim();
|
||||
|
||||
if (!triggerName || !tableName || !triggerType || !triggeringEvent || !triggerBody) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const qualifiedTriggerName = owner ? `${owner}.${triggerName}` : triggerName;
|
||||
const qualifiedTableName = tableOwner ? `${tableOwner}.${tableName}` : tableName;
|
||||
const normalizedWhenClause = whenClause ? `\nWHEN (${whenClause.replace(/^\((.*)\)$/s, '$1')})` : '';
|
||||
const normalizedTriggerType = triggerType.replace(/\s+/g, ' ').trim();
|
||||
const triggerTypeMatch = normalizedTriggerType.match(/^(BEFORE|AFTER|INSTEAD OF)(?:\s+(EACH ROW|STATEMENT))?$/i);
|
||||
if (triggerTypeMatch) {
|
||||
const timing = String(triggerTypeMatch[1] || '').toUpperCase();
|
||||
const firingLevel = String(triggerTypeMatch[2] || '').toUpperCase();
|
||||
const forEachRowClause = firingLevel === 'EACH ROW' ? '\nFOR EACH ROW' : '';
|
||||
return `CREATE OR REPLACE TRIGGER ${qualifiedTriggerName}\n${timing} ${triggeringEvent} ON ${qualifiedTableName}${forEachRowClause}${normalizedWhenClause}\n${triggerBody}`;
|
||||
}
|
||||
return `CREATE OR REPLACE TRIGGER ${qualifiedTriggerName}\n${triggerType} ${triggeringEvent} ON ${qualifiedTableName}${normalizedWhenClause}\n${triggerBody}`;
|
||||
};
|
||||
|
||||
const buildEditableTriggerSql = (triggerName: string, triggerDefinition: string): string => {
|
||||
const normalizedName = String(triggerName || '').trim();
|
||||
const normalizedDefinition = String(triggerDefinition || '').trim();
|
||||
@@ -102,7 +188,7 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
|
||||
return [
|
||||
`SHOW CREATE TRIGGER \`${name.replace(/`/g, '``')}\``,
|
||||
safeDbName
|
||||
? `SELECT ACTION_STATEMENT AS trigger_definition FROM information_schema.triggers WHERE trigger_schema = '${safeDbName}' AND trigger_name = '${safeTriggerName}' LIMIT 1`
|
||||
? `SELECT TRIGGER_NAME, TRIGGER_SCHEMA, EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, ACTION_TIMING, EVENT_MANIPULATION, ACTION_ORIENTATION, ACTION_STATEMENT FROM information_schema.triggers WHERE trigger_schema = '${safeDbName}' AND trigger_name = '${safeTriggerName}' LIMIT 1`
|
||||
: '',
|
||||
safeDbName
|
||||
? `SHOW TRIGGERS FROM \`${dbName.replace(/`/g, '``')}\` LIKE '${safeTriggerName}'`
|
||||
@@ -125,12 +211,21 @@ LIMIT 1`];
|
||||
case 'oracle':
|
||||
case 'dm':
|
||||
if (schema) {
|
||||
return [`SELECT TRIGGER_BODY FROM ALL_TRIGGERS WHERE OWNER = '${escapeSQLLiteral(schema).toUpperCase()}' AND TRIGGER_NAME = '${safeTriggerName.toUpperCase()}'`];
|
||||
return [
|
||||
`SELECT DBMS_METADATA.GET_DDL('TRIGGER', '${safeTriggerName.toUpperCase()}', '${escapeSQLLiteral(schema).toUpperCase()}') AS trigger_definition FROM DUAL`,
|
||||
`SELECT OWNER, TABLE_OWNER, TABLE_NAME, TRIGGER_NAME, TRIGGER_TYPE, TRIGGERING_EVENT, WHEN_CLAUSE, TRIGGER_BODY FROM ALL_TRIGGERS WHERE OWNER = '${escapeSQLLiteral(schema).toUpperCase()}' AND TRIGGER_NAME = '${safeTriggerName.toUpperCase()}'`,
|
||||
];
|
||||
}
|
||||
if (!safeDbName) {
|
||||
return [`SELECT TRIGGER_BODY FROM USER_TRIGGERS WHERE TRIGGER_NAME = '${safeTriggerName.toUpperCase()}'`];
|
||||
return [
|
||||
`SELECT DBMS_METADATA.GET_DDL('TRIGGER', '${safeTriggerName.toUpperCase()}') AS trigger_definition FROM DUAL`,
|
||||
`SELECT TABLE_NAME, TRIGGER_NAME, TRIGGER_TYPE, TRIGGERING_EVENT, WHEN_CLAUSE, TRIGGER_BODY FROM USER_TRIGGERS WHERE TRIGGER_NAME = '${safeTriggerName.toUpperCase()}'`,
|
||||
];
|
||||
}
|
||||
return [`SELECT TRIGGER_BODY FROM ALL_TRIGGERS WHERE OWNER = '${safeDbName.toUpperCase()}' AND TRIGGER_NAME = '${safeTriggerName.toUpperCase()}'`];
|
||||
return [
|
||||
`SELECT DBMS_METADATA.GET_DDL('TRIGGER', '${safeTriggerName.toUpperCase()}', '${safeDbName.toUpperCase()}') AS trigger_definition FROM DUAL`,
|
||||
`SELECT OWNER, TABLE_OWNER, TABLE_NAME, TRIGGER_NAME, TRIGGER_TYPE, TRIGGERING_EVENT, WHEN_CLAUSE, TRIGGER_BODY FROM ALL_TRIGGERS WHERE OWNER = '${safeDbName.toUpperCase()}' AND TRIGGER_NAME = '${safeTriggerName.toUpperCase()}'`,
|
||||
];
|
||||
case 'sqlite':
|
||||
return [`SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = '${safeTriggerName}'`];
|
||||
case 'duckdb':
|
||||
@@ -202,21 +297,25 @@ LIMIT 1`];
|
||||
return '';
|
||||
};
|
||||
|
||||
const extractTriggerDefinition = (dialect: string, data: any[]): string => {
|
||||
const extractTriggerDefinition = (dialect: string, data: any[], fallbackTriggerName: string, fallbackTableName: string): string => {
|
||||
if (!data || data.length === 0) {
|
||||
return '-- 未找到触发器定义';
|
||||
}
|
||||
|
||||
const row = data[0];
|
||||
const row = data[0] as Record<string, any>;
|
||||
|
||||
switch (dialect) {
|
||||
case 'mysql':
|
||||
case 'starrocks': {
|
||||
// MySQL SHOW CREATE TRIGGER returns: Trigger, sql_mode, SQL Original Statement, ...
|
||||
const keys = Object.keys(row);
|
||||
const metadataDDL = buildMySQLTriggerDDLFromMetadata(row, fallbackTriggerName, fallbackTableName);
|
||||
if (row.trigger_definition || row.TRIGGER_DEFINITION) {
|
||||
return String(row.trigger_definition || row.TRIGGER_DEFINITION);
|
||||
}
|
||||
if (metadataDDL) {
|
||||
return metadataDDL;
|
||||
}
|
||||
if (row.ACTION_STATEMENT || row.action_statement) {
|
||||
return String(row.ACTION_STATEMENT || row.action_statement);
|
||||
}
|
||||
@@ -243,6 +342,14 @@ LIMIT 1`];
|
||||
}
|
||||
case 'oracle':
|
||||
case 'dm': {
|
||||
const ddl = String(row.trigger_definition || row.TRIGGER_DEFINITION || '').trim();
|
||||
if (ddl) {
|
||||
return ddl;
|
||||
}
|
||||
const metadataDDL = buildOracleLikeTriggerDDLFromMetadata(row, fallbackTriggerName, fallbackTableName);
|
||||
if (metadataDDL) {
|
||||
return metadataDDL;
|
||||
}
|
||||
return row.trigger_body || row.TRIGGER_BODY || Object.values(row)[0] || '';
|
||||
}
|
||||
case 'sqlite': {
|
||||
@@ -287,7 +394,10 @@ LIMIT 1`];
|
||||
const result = await runQueryCandidates(config, dbName, queries);
|
||||
|
||||
if (result.success && Array.isArray(result.data) && result.data.length > 0) {
|
||||
return { success: true, definition: extractTriggerDefinition(dialect, result.data) };
|
||||
return {
|
||||
success: true,
|
||||
definition: extractTriggerDefinition(dialect, result.data, triggerName, String(tab.triggerTableName || '')),
|
||||
};
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
@@ -391,6 +501,7 @@ LIMIT 1`];
|
||||
connectionId: tab.connectionId,
|
||||
dbName,
|
||||
query: buildEditableTriggerSql(triggerName, latestDefinition),
|
||||
queryMode: 'object-edit',
|
||||
});
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
|
||||
Reference in New Issue
Block a user