diff --git a/frontend/src/components/DefinitionViewer.object-edit.test.tsx b/frontend/src/components/DefinitionViewer.object-edit.test.tsx
index fe99485..031eb7f 100644
--- a/frontend/src/components/DefinitionViewer.object-edit.test.tsx
+++ b/frontend/src/components/DefinitionViewer.object-edit.test.tsx
@@ -87,6 +87,7 @@ describe('DefinitionViewer object edit entry', () => {
storeState.addTab.mockReset();
storeState.setActiveContext.mockReset();
storeState.theme = 'light';
+ storeState.connections[0].config.type = 'postgres';
backendApp.DBQuery.mockResolvedValue({
success: true,
data: [{ view_definition: 'SELECT id, name FROM users' }],
@@ -117,6 +118,30 @@ describe('DefinitionViewer object edit entry', () => {
expect(storeState.addTab.mock.calls[0][0].query).toContain('SELECT id, name FROM users;');
});
+ it('adds CREATE OR REPLACE without duplicating view fragments returned without ddl prefix', async () => {
+ backendApp.DBQuery.mockResolvedValue({
+ success: true,
+ data: [{ view_definition: 'VIEW reporting.active_users AS\nSELECT id, name FROM users' }],
+ });
+
+ let renderer: any;
+ await act(async () => {
+ renderer = create();
+ 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 VIEW reporting.active_users AS');
+ expect(query).toContain('SELECT id, name FROM users;');
+ expect(query).not.toContain('AS\nVIEW reporting.active_users AS');
+ });
+
it('opens an editable query tab for routine definitions', async () => {
backendApp.DBQuery.mockResolvedValue({
success: true,
diff --git a/frontend/src/components/DefinitionViewer.tsx b/frontend/src/components/DefinitionViewer.tsx
index a83ae8a..703de86 100644
--- a/frontend/src/components/DefinitionViewer.tsx
+++ b/frontend/src/components/DefinitionViewer.tsx
@@ -48,6 +48,9 @@ const buildEditableDefinitionSql = (tab: TabData, definition: string, objectLabe
}
if (tab.type === 'view-def' && !/^\s*create\b/i.test(normalizedDefinition)) {
+ if (/^\s*view\b/i.test(normalizedDefinition)) {
+ return `${header}${ensureSqlStatementTerminator(normalizedDefinition.replace(/^\s*view\b/i, 'CREATE OR REPLACE VIEW'))}`;
+ }
return `${header}CREATE OR REPLACE VIEW ${objectName} AS\n${ensureSqlStatementTerminator(normalizedDefinition)}`;
}
diff --git a/frontend/src/components/TriggerViewer.object-edit.test.tsx b/frontend/src/components/TriggerViewer.object-edit.test.tsx
index 206ec1b..6f64db9 100644
--- a/frontend/src/components/TriggerViewer.object-edit.test.tsx
+++ b/frontend/src/components/TriggerViewer.object-edit.test.tsx
@@ -85,6 +85,7 @@ describe('TriggerViewer object edit entry', () => {
beforeEach(() => {
storeState.addTab.mockReset();
storeState.setActiveContext.mockReset();
+ storeState.connections[0].config.type = 'postgres';
backendApp.DBQuery.mockResolvedValue({
success: true,
data: [{ trigger_definition: 'CREATE TRIGGER users_bi BEFORE INSERT ON audit.users EXECUTE FUNCTION audit.audit_users();' }],
@@ -113,4 +114,60 @@ describe('TriggerViewer object edit entry', () => {
query: expect.stringContaining('CREATE TRIGGER users_bi BEFORE INSERT'),
}));
});
+
+ it('adds CREATE OR REPLACE for trigger source snippets returned without ddl prefix', async () => {
+ storeState.connections[0].config.type = 'oracle';
+ backendApp.DBQuery.mockResolvedValue({
+ success: true,
+ data: [{
+ TRIGGER_BODY: 'TRIGGER users_bi\nBEFORE INSERT ON audit.users\nFOR EACH ROW\nBEGIN\n :NEW.created_at := SYSDATE;\nEND;',
+ }],
+ });
+
+ let renderer: any;
+ await act(async () => {
+ renderer = create();
+ 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 users_bi');
+ expect(query).toContain('BEFORE INSERT ON audit.users');
+ expect(query).toContain(':NEW.created_at := SYSDATE;');
+ expect(query).not.toContain('请补全 CREATE TRIGGER 语句');
+ });
+
+ it('adds trigger name for trigger body snippets returned without ddl header', async () => {
+ storeState.connections[0].config.type = 'oracle';
+ backendApp.DBQuery.mockResolvedValue({
+ success: true,
+ data: [{
+ TRIGGER_BODY: 'BEFORE UPDATE ON audit.users\nFOR EACH ROW\nBEGIN\n :NEW.updated_at := SYSDATE;\nEND;',
+ }],
+ });
+
+ let renderer: any;
+ await act(async () => {
+ renderer = create();
+ 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_bi');
+ expect(query).toContain('BEFORE UPDATE ON audit.users');
+ expect(query).toContain(':NEW.updated_at := SYSDATE;');
+ expect(query).not.toContain('请补全 CREATE TRIGGER 语句');
+ });
});
diff --git a/frontend/src/components/TriggerViewer.tsx b/frontend/src/components/TriggerViewer.tsx
index 3a281ad..21ace7b 100644
--- a/frontend/src/components/TriggerViewer.tsx
+++ b/frontend/src/components/TriggerViewer.tsx
@@ -29,6 +29,12 @@ const buildEditableTriggerSql = (triggerName: string, triggerDefinition: string)
if (/^\s*create\s+(?:or\s+replace\s+)?trigger\b/i.test(normalizedDefinition)) {
return `${header}${ensureSqlStatementTerminator(normalizedDefinition)}`;
}
+ if (/^\s*trigger\b/i.test(normalizedDefinition)) {
+ return `${header}${ensureSqlStatementTerminator(normalizedDefinition.replace(/^\s*trigger\b/i, 'CREATE OR REPLACE TRIGGER'))}`;
+ }
+ if (/^\s*(?:before|after|instead\s+of)\b/i.test(normalizedDefinition)) {
+ return `${header}${ensureSqlStatementTerminator(`CREATE OR REPLACE TRIGGER ${normalizedName}\n${normalizedDefinition}`)}`;
+ }
return `${header}-- 当前数据源仅返回触发器定义片段,请补全 CREATE TRIGGER 语句后执行\n${ensureSqlStatementTerminator(normalizedDefinition)}`;
};