On metalink,
Oracle can't do that ...
connect developer@dev begin UpdateServer('prod'); end; / create table a as select * from dual; declare v_id char:='Y'; v_cnt number; begin select count(rownum) into v_cnt from a; if v_cnt = 1 then insert into a values (v_id); end if; end; / begin CommitUpdate; end; /
begin UpdateFilials; end; /
connect system/***@orcl Connected. select banner from v$version; BANNER -------------------------------------------------------------------------- Oracle Database 11g Release 11.2.0.1.0 - 64bit Production
create user upd identified by pass; User created.
grant dba to upd; Grant succeded. connect upd/pass Connected.
set linesize 90 begin raise_application_error(-20000, 'Find me'); end; / select sql_id from v$sql where sql_text like '%error(-20000, ''Find%'; SQL_ID ------------- 753c9f808k8hh 1 row selected.
connect system/*** begin raise_application_error(-20000, 'Find me'); end; / select sql_id from v$sql where sql_text like '%error(-20000, ''Find%'; SQL_ID ------------- 753c9f808k8hh 1 row selected.
connect upd/pass Connected.
select sid, sql_id, prev_sql_id from v$session where sid = userenv('sid'); SID SQL_ID PREV_SQL_ID ---------- ------------- ------------- 95 54mqd9bcxw8nh 753c9f808k8hh
select sql_id, prev_sql_id from v$session;
CREATE TABLE UPD.UPD$SESSION_TARGETS (SID NUMBER); Table created. CREATE TABLE UPD.UPD$SESSION_DATA ( KSUSENUM NUMBER, KSUSEUNM VARCHAR2 (30 BYTE), KSUSEMNM VARCHAR2 (64 BYTE), KSUSESQI VARCHAR2 (13 BYTE), KSUSEPSI VARCHAR2 (13 BYTE) ); Table created.
CREATE OR REPLACE procedure UPD.UPD$JobTask is v_cnt number; begin loop select count(rownum) into v_cnt from upd.upd$session_targets; if (v_cnt = 0) then select count(ksusenum) into v_cnt from upd.upd$session_data; if (v_cnt > 0) then execute immediate 'truncate table upd.upd$session_data'; end if; continue; end if; INSERT INTO upd.upd$session_data (KSUSENUM, KSUSEUNM, KSUSEMNM, KSUSESQI, KSUSEPSI) SELECT ksusenum, ksuseunm, ksusemnm, ksusesqi, ksusepsi FROM sys.x_$ksuse WHERE ksusenum IN (SELECT ust.sid FROM upd.upd$session_targets ust) MINUS SELECT ksusenum, ksuseunm, ksusemnm, ksusesqi, ksusepsi FROM upd.upd$session_data; commit; end loop; end UPD$JobTask; / Procedure created.
DECLARE X NUMBER; BEGIN SYS.DBMS_JOB.SUBMIT ( job => X ,what => 'begin /*UPD$SESSION_JOB*/ UPD$JobTask; end;' ,next_date => SYSDATE ,interval => 'SYSDATE + 1/1444' ,no_parse => FALSE ); COMMIT; END; / PL/SQL procedure successfully completed. insert into upd.upd$session_targets values (userenv('sid')); 1 row created. begin raise_application_error(-20000, 'Find me'); end; / Error at line 3 ORA-20000: Find me ORA-06512: at line 2 truncate table upd.upd$session_targets; Table truncated. select KSUSEPSI from upd.upd$session_data; KSUSEPSI ------------- 753c9f808k8hh 1 row selected. select sql_text from v$sql where sql_id = '753c9f808k8hh'; SQL_TEXT ---------------------------------------------------------------------------- begin raise_application_error(-20000, 'Find me'); end; 1 row selected.
CREATE GLOBAL TEMPORARY TABLE upd.UPD$BUF ( ALIAS_OBJ VARCHAR2 (500 CHAR), SQLTEXT CLOB, OBJNAME VARCHAR2 (30 BYTE) ) ON COMMIT PRESERVE ROWS; Table created. CREATE OR REPLACE PROCEDURE upd.T_PROC_UPD$DDL AUTHID DEFINER AS osuser varchar2(30); machine varchar2(64); cnt number; V_SQL_OUT ORA_NAME_LIST_T; V_SQL_STATEMENT CLOB; V_NUM NUMBER; v_sqlerrm varchar2(2000); BEGIN SELECT count(rownum) INTO cnt FROM upd$session_targets ust WHERE ust.sid = userenv('sid'); if cnt = 0 then return; end if; V_NUM := ORA_SQL_TXT(V_SQL_OUT); FOR I IN 1 .. V_NUM LOOP V_SQL_STATEMENT := V_SQL_STATEMENT || V_SQL_OUT(I); END LOOP; INSERT INTO UPD$BUF (ALIAS_OBJ, SQLTEXT, OBJNAME) VALUES (NULL, V_SQL_STATEMENT, ora_dict_obj_name); EXCEPTION WHEN OTHERS THEN raise_application_error(-20000, SQLERRM); END T_PROC_UPD$DDL; / Procedure created. CREATE OR REPLACE TRIGGER upd.T_UPD$DDL AFTER DDL ON DATABASE BEGIN T_PROC_UPD$DDL; END; / Trigger created.
CREATE SEQUENCE UPD.UPD$SEQ_LOG START WITH 0 MAXVALUE 9999999999999999999999999999 MINVALUE 0 NOCYCLE NOCACHE NOORDER; Sequence created. CREATE SEQUENCE UPD.UPD$SEQ_REV START WITH 0 MAXVALUE 9999999999999999999999999999 MINVALUE 0 NOCYCLE NOCACHE NOORDER; Sequence created. CREATE TABLE UPD.UPD$LOG ( ID_LOG NUMBER, DAT_LOG DATE, FQDN_UNAME_OBJ VARCHAR2 (1000 CHAR), ALIAS_OBJ VARCHAR2 (500 CHAR), SQL_TEXT CLOB, ID_REV NUMBER, SQLERRM_LOG VARCHAR2 (2000 CHAR) ); Table created. CREATE TABLE UPD.UPD$SERVERS ( ALIAS_OBJ VARCHAR2 (500 CHAR), DBLINK_OBJ VARCHAR2 (500 CHAR), USERNAME VARCHAR2 (64 CHAR), CALLBACK_DBLINK_OBJ VARCHAR2 (500 CHAR) ); Table created. CREATE OR REPLACE PACKAGE UPD$ AUTHID CURRENT_USER AS procedure BeginUpdateChannel(u_alias varchar2); procedure PrepareUpdateChannel; procedure EndUpdateChannel; procedure CancelUpdate; END UPD$; / Package created. CREATE OR REPLACE PACKAGE BODY UPD$ AS pkg_active_alias varchar2(500); pkg_prepared_alias varchar2(500):=null; pkg_session number:=null; pkg_dblink varchar2(500):=null; pkg_callback_dblink varchar2(500):=null; procedure SetSession(u_sid number, u_remove boolean default false) as pragma autonomous_transaction; l_sid_count number; begin if u_sid is null then raise_application_error(-20550, 'Needless to set'); end if; select count(rownum) into l_sid_count from upd.upd$session_targets ust where ust.sid = u_sid; if l_sid_count = 0 then insert into upd.upd$session_targets (sid) values (u_sid); commit; pkg_session:=u_sid; elsif u_remove then delete from upd.upd$session_targets ust where ust.sid = u_sid; commit; pkg_session:=null; end if; end SetSession; function JobNumber return number as l_jobid number; begin SELECT a.job INTO l_jobid FROM dba_jobs a WHERE a.what like '%/*UPD$SESSION_JOB*/%'; return l_jobid; EXCEPTION WHEN NO_DATA_FOUND THEN return 0; WHEN OTHERS THEN raise; end JobNumber; procedure JobRun as --TODO: run job is it stopped --v_cnt number:=0; v_job number; begin v_job:=JobNumber; if v_job = 0 then raise_application_error(-20560, 'Unable to find updating job'); end if; --select count(rownum) -- into v_cnt -- from dba_jobs_running a where a.job = v_job; --if v_cnt = 0 then -- dbms_job.run(v_job); -- commit; --end if; end JobRun; procedure SetChannel(u_alias varchar2) as begin SELECT dblink_obj, callback_dblink_obj INTO pkg_dblink, pkg_callback_dblink FROM upd.upd$servers a WHERE upper(a.alias_obj) = upper(u_alias) AND upper(username) = upper(USER); exception when no_data_found then raise_application_error(-20501, 'Unable set channel. Alias '||u_alias||' not found'); when others then raise_application_error(-20500, 'Unable set channel for alias '||u_alias||SQLERRM); end SetChannel; procedure CancelUpdate is begin pkg_active_alias:=null; pkg_prepared_alias:=null; pkg_session:=null; pkg_dblink:=null; execute immediate 'truncate table upd.upd$buf'; delete from upd.upd$session_targets ust where ust.sid = userenv('sid'); end CancelUpdate; procedure BeginUpdateChannel(u_alias varchar2) is begin if pkg_active_alias is not null then raise_application_error(-20500, 'Unable begin update channel. Alias '||u_alias||' allready active.'); end if; SetChannel(u_alias); execute immediate 'truncate table upd.upd$buf'; JobRun; SetSession(userenv('sid'), false); pkg_active_alias:=u_alias; pkg_prepared_alias:=null; end BeginUpdateChannel; procedure PrepareUpdateChannel is begin if pkg_prepared_alias is not null then raise_application_error(-20500, 'Already prepared'); end if; if pkg_active_alias is null then raise_application_error(-20500, 'Needless to prepare'); end if; INSERT INTO upd.upd$buf (ALIAS_OBJ, SQLTEXT) SELECT pkg_active_alias, b.sql_fulltext FROM (select distinct ksusenum, ksusesqi from upd.upd$session_data) a, sys.v_$sql b WHERE a.ksusenum = pkg_session AND a.ksusesqi = b.sql_id AND (trim(upper(sql_text)) not like 'INSERT%' and trim(upper(sql_text)) not like 'UPDATE%' and trim(upper(sql_text)) not like 'DELETE%' and trim(upper(sql_text)) not like 'SELECT%' and trim(upper(sql_text)) not like '%UPD$%' and trim(upper(sql_text)) not like '%AW_TRUNC_PROC%' and trim(upper(sql_text)) not like '%XDB.XDB_PITRIG_PKG%' and sql_text not like '%:B%' and sql_text not like '%:1%' ); SetSession(pkg_session, true); pkg_prepared_alias:=pkg_active_alias; pkg_active_alias:=null; end PrepareUpdateChannel; procedure DropObject(object_name varchar2) is l_owner varchar2(30); l_type varchar2(19); l_purge varchar2(6); begin SELECT OWNER, OBJECT_TYPE, CASE when object_type = 'TABLE' then ' purge' else null end INTO l_owner, l_type, l_purge FROM all_objects WHERE upper(object_name) = upper(DropObject.object_name); execute immediate 'drop '||l_type||' '||DropObject.object_name||l_purge; exception when no_data_found then null; when others then raise; end DropObject; procedure ExecRemote(u_sql varchar2) is c number; r number; begin execute immediate 'begin :1:=dbms_sql.open_cursor@'||pkg_dblink||'(); end;' using out c; execute immediate 'begin dbms_sql.parse@'||pkg_dblink||'(:1, :2, dbms_sql.native); end;' using in c, in u_sql; execute immediate 'begin dbms_sql.close_cursor@'||pkg_dblink||'(:1); end;' using in out c; end ExecRemote; procedure EndUpdateChannel is l_alias varchar2(5000); l_dblink varchar2(500); l_sql varchar2(32000); l_osuser varchar2(30); l_machine varchar2(64); l_log_id number:=null; l_rev_id number:=null; l_error_stack varchar2(30000):=null; l_tmp_tab varchar2(500):=DBMS_RANDOM.STRING('', 8); l_tmp_proc varchar2(500); begin if (pkg_active_alias is null) and (pkg_prepared_alias is null) then raise_application_error(-20500, 'Needless to end'); end if; if pkg_prepared_alias is null then raise_application_error(-20500, 'You must execute PrepareUpdateChannel first'); end if; l_tmp_proc:='up_$proc_'||l_tmp_tab; l_tmp_tab:='up_$tab_'||l_tmp_tab; l_alias:=pkg_prepared_alias; pkg_prepared_alias:=null; begin execute immediate 'create table '||l_tmp_tab||' as select ub.* from upd.upd$buf ub'; execute immediate 'grant select on '||l_tmp_tab||' to '||USER; l_sql:='create table '||l_tmp_tab||' as select * from upd.'||l_tmp_tab||'@'||pkg_callback_dblink; ExecRemote(l_sql); DropObject(l_tmp_tab); l_sql:='create or replace procedure '||l_tmp_proc||' is c number; r number; l_objname varchar2(30); l_sqlforerr varchar2(200); l_error_stack varchar2(30000); begin for c_exec in (select * from '||l_tmp_tab||') loop l_objname:=c_exec.objname; l_sqlforerr:=dbms_lob.substr(c_exec.sqltext, 200); c := dbms_sql.open_cursor(); dbms_sql.parse(c, c_exec.sqltext, dbms_sql.native); r := dbms_sql.execute(c); dbms_sql.close_cursor(c); l_objname:=null; end loop; execute immediate ''drop table '||l_tmp_tab||' purge''; exception when others then execute immediate ''drop table '||l_tmp_tab||' purge''; if l_objname is not null then select replace(wm_concat(text), '','', chr(10)) into l_error_stack from user_errors where name = l_objname; end if; raise_application_error(-20000, ''Obj: ''||l_objname||chr(10)||''SQLERRM: ''||SQLERRM||chr(10)||''Show errors: ''||l_error_stack||chr(10)||''Code: ''||l_sqlforerr); end;'; ExecRemote(l_sql); begin execute immediate 'begin '||l_tmp_proc||'@'||pkg_dblink||'; end;'; commit; exception when others then l_error_stack:=SQLERRM; end; l_sql:='drop procedure '||l_tmp_proc; ExecRemote(l_sql); if l_error_stack is not null then raise_application_error(-20590, null); end if; exception when others then l_dblink:=pkg_dblink; CancelUpdate; DropObject(l_tmp_tab); if sqlcode = -20550 then raise; elsif sqlcode = -20590 then raise_application_error(-20555, 'Error when executing remote SQL'||chr(10)|| 'Compilation errors: ['||l_error_stack||']'); else raise; end if; end; SELECT distinct osuser, machine INTO l_osuser, l_machine FROM v$session WHERE sid = USERENV('sid'); l_rev_id:=UPD$SEQ_REV.NEXTVAL; INSERT INTO upd.upd$log (ID_LOG, DAT_LOG, FQDN_UNAME_OBJ, ALIAS_OBJ, SQL_TEXT, ID_REV, SQLERRM_LOG) SELECT upd$seq_log.nextval, sysdate, l_machine||'\'||l_osuser, pkg_prepared_alias, sqltext, l_rev_id, null FROM upd.upd$buf ub; execute immediate 'truncate table upd.upd$buf'; end EndUpdateChannel; procedure ErrorEnumAccess is begin null; end ErrorEnumAccess; END UPD$; / Package body created.
procedure SetSession(u_sid number, u_remove boolean default false)
- using a stand-alone transaction, writes the current session identifier to the table that initiates the listen.function JobNumber return number
- gets the ID of the listener.procedure JobRun
- checks for the existence of a job.procedure SetChannel(u_alias varchar2)
- receives remote connection settings and writes them to local variables of the package.procedure CancelUpdate
- clears settings and clears temporary tables.procedure BeginUpdateChannel(u_alias varchar2)
- combines preparatory procedure calls and starts listening.procedure PrepareUpdateChannel
- finishes listening and appends session requests collected by a job to the table buffer. For my own needs, I don’t try too hard, discarding DML, select and service commands encountered during the test, as well as a call to the PrepareUpdateChannel procedure, which is also recorded in the session log.procedure DropObject
is an auxiliary procedure for cleaning.procedure ExecRemote
- block execution on a remote server. This procedure implements one of the key moments of the mechanism. Here the dbms_sql package is called on the remote server.procedure EndUpdateChannel
— apply the update. And about this separately. create or replace procedure ChannelUpdate(u_alias varchar2) AUTHID DEFINER is begin upd$.BeginUpdateChannel(u_alias); end ChannelUpdate; create or replace procedure ChannelPrepare AUTHID DEFINER is begin upd$.PrepareUpdateChannel; end ChannelPrepare; create or replace procedure ChannelApply AUTHID DEFINER is begin upd$.EndUpdateChannel; end ChannelApply; create or replace procedure ChannelCancel AUTHID DEFINER is begin upd$.CancelUpdate; end ChannelCancel; grant execute on ChannelUpdate to developer; grant execute on ChannelPrepare to developer; grant execute on ChannelApply to developer; grant execute on ChannelCancel to developer; grant select on upd$log to developer;
Source: https://habr.com/ru/post/239397/
All Articles