Files
mars/MARS_Packages/REL02_POST/MARS-1409/rollback_version/ENV_MANAGER.pkb
2026-02-27 07:32:21 +01:00

1172 lines
53 KiB
Plaintext

create or replace PACKAGE BODY CT_MRDS.ENV_MANAGER
AS
----------------------------------------------------------------------------------------------------
PROCEDURE INIT_ERRORS IS
BEGIN
Errors(CODE_EMPTY_FILEURI_AND_RECKEY) := Error_Record(CODE_EMPTY_FILEURI_AND_RECKEY, MSG_EMPTY_FILEURI_AND_RECKEY); -- -20001
Errors(CODE_NO_CONFIG_MATCH_FOR_FILEURI) := Error_Record(CODE_NO_CONFIG_MATCH_FOR_FILEURI, MSG_NO_CONFIG_MATCH_FOR_FILEURI); -- -20002
Errors(CODE_MULTIPLE_MATCH_FOR_SRCFILE) := Error_Record(CODE_MULTIPLE_MATCH_FOR_SRCFILE, MSG_MULTIPLE_MATCH_FOR_SRCFILE); -- -20003
Errors(CODE_MISSING_COLUMN_DATE_FORMAT) := Error_Record(CODE_MISSING_COLUMN_DATE_FORMAT, MSG_MISSING_COLUMN_DATE_FORMAT); -- -20004
Errors(CODE_MULTIPLE_COLUMN_DATE_FORMAT) := Error_Record(CODE_MULTIPLE_COLUMN_DATE_FORMAT, MSG_MULTIPLE_COLUMN_DATE_FORMAT); -- -20005
Errors(CODE_DIDNT_GET_LOAD_OPERATION_ID) := Error_Record(CODE_DIDNT_GET_LOAD_OPERATION_ID, MSG_DIDNT_GET_LOAD_OPERATION_ID); -- -20006
Errors(CODE_NO_CONFIG_FOR_RECEIVED_FILE) := Error_Record(CODE_NO_CONFIG_FOR_RECEIVED_FILE, MSG_NO_CONFIG_FOR_RECEIVED_FILE); -- -20007
Errors(CODE_MULTI_CONFIG_FOR_RECEIVED_FILE) := Error_Record(CODE_MULTI_CONFIG_FOR_RECEIVED_FILE, MSG_MULTI_CONFIG_FOR_RECEIVED_FILE); -- -20008
Errors(CODE_FILE_NOT_FOUND_ON_CLOUD) := Error_Record(CODE_FILE_NOT_FOUND_ON_CLOUD, MSG_FILE_NOT_FOUND_ON_CLOUD); -- -20009
Errors(CODE_FILE_VALIDATION_FAILED) := Error_Record(CODE_FILE_VALIDATION_FAILED, MSG_FILE_VALIDATION_FAILED); -- -20010
Errors(CODE_EXCESS_COLUMNS_DETECTED) := Error_Record(CODE_EXCESS_COLUMNS_DETECTED, MSG_EXCESS_COLUMNS_DETECTED); -- -20011
Errors(CODE_NO_CONFIG_MATCH) := Error_Record(CODE_NO_CONFIG_MATCH, MSG_NO_CONFIG_MATCH); -- -20012
Errors(CODE_UNKNOWN_PREFIX) := Error_Record(CODE_UNKNOWN_PREFIX, MSG_UNKNOWN_PREFIX); -- -20013
Errors(CODE_TABLE_NOT_EXISTS) := Error_Record(CODE_TABLE_NOT_EXISTS, MSG_TABLE_NOT_EXISTS); -- -20014
Errors(CODE_COLUMN_NOT_EXISTS) := Error_Record(CODE_COLUMN_NOT_EXISTS, MSG_COLUMN_NOT_EXISTS); -- -20015
Errors(CODE_UNSUPPORTED_DATA_TYPE) := Error_Record(CODE_UNSUPPORTED_DATA_TYPE, MSG_UNSUPPORTED_DATA_TYPE); -- -20016
Errors(CODE_MISSING_SOURCE_KEY) := Error_Record(CODE_MISSING_SOURCE_KEY, MSG_MISSING_SOURCE_KEY); -- -20017
Errors(CODE_NULL_SOURCE_FILE_CONFIG_KEY) := Error_Record(CODE_NULL_SOURCE_FILE_CONFIG_KEY, MSG_NULL_SOURCE_FILE_CONFIG_KEY); -- -20018
Errors(CODE_DUPLICATED_SOURCE_KEY) := Error_Record(CODE_DUPLICATED_SOURCE_KEY, MSG_DUPLICATED_SOURCE_KEY); -- -20019
Errors(CODE_MISSING_CONTAINER_CONFIG) := Error_Record(CODE_MISSING_CONTAINER_CONFIG, MSG_MISSING_CONTAINER_CONFIG); -- -20020
Errors(CODE_MULTIPLE_CONTAINER_ENTRIES) := Error_Record(CODE_MULTIPLE_CONTAINER_ENTRIES, MSG_MULTIPLE_CONTAINER_ENTRIES); -- -20021
Errors(CODE_WRONG_DESTINATION_PARAM) := Error_Record(CODE_WRONG_DESTINATION_PARAM, MSG_WRONG_DESTINATION_PARAM); -- -20022
Errors(CODE_FILE_NOT_EXISTS_ON_CLOUD) := Error_Record(CODE_FILE_NOT_EXISTS_ON_CLOUD, MSG_FILE_NOT_EXISTS_ON_CLOUD); -- -20023
Errors(CODE_FILE_ALREADY_REGISTERED) := Error_Record(CODE_FILE_ALREADY_REGISTERED, MSG_FILE_ALREADY_REGISTERED); -- -20024
Errors(CODE_WRONG_DATE_TIMESTAMP_FORMAT) := Error_Record(CODE_WRONG_DATE_TIMESTAMP_FORMAT, MSG_WRONG_DATE_TIMESTAMP_FORMAT); -- -20025
Errors(CODE_ENVIRONMENT_NOT_SET) := Error_Record(CODE_ENVIRONMENT_NOT_SET, MSG_ENVIRONMENT_NOT_SET); -- -20026
Errors(CODE_CONFIG_VARIABLE_NOT_SET) := Error_Record(CODE_CONFIG_VARIABLE_NOT_SET, MSG_CONFIG_VARIABLE_NOT_SET); -- -20027
Errors(CODE_NOT_INPUT_SOURCE_FILE_TYPE) := Error_Record(CODE_NOT_INPUT_SOURCE_FILE_TYPE, MSG_NOT_INPUT_SOURCE_FILE_TYPE); -- -20028
Errors(CODE_EXP_DATA_FOR_ARCH_FAILED) := Error_Record(CODE_EXP_DATA_FOR_ARCH_FAILED, MSG_EXP_DATA_FOR_ARCH_FAILED); -- -20029
Errors(CODE_RESTORE_FILE_FROM_TRASH) := Error_Record(CODE_RESTORE_FILE_FROM_TRASH, MSG_RESTORE_FILE_FROM_TRASH); -- -20030
Errors(CODE_CHANGE_STAT_TO_ARCHIVED_FAILED):= Error_Record(CODE_CHANGE_STAT_TO_ARCHIVED_FAILED, MSG_CHANGE_STAT_TO_ARCHIVED_FAILED); -- -20031
Errors(CODE_MOVE_FILE_TO_TRASH_FAILED) := Error_Record(CODE_MOVE_FILE_TO_TRASH_FAILED, MSG_MOVE_FILE_TO_TRASH_FAILED); -- -20032
Errors(CODE_DROP_EXPORTED_FILES_FAILED) := Error_Record(CODE_DROP_EXPORTED_FILES_FAILED, MSG_DROP_EXPORTED_FILES_FAILED); -- -20033
Errors(CODE_INVALID_BUCKET_AREA) := Error_Record(CODE_INVALID_BUCKET_AREA, MSG_INVALID_BUCKET_AREA); -- -20034
Errors(CODE_INVALID_PARALLEL_DEGREE) := Error_Record(CODE_INVALID_PARALLEL_DEGREE, MSG_INVALID_PARALLEL_DEGREE); -- -20110
Errors(CODE_PARALLEL_EXECUTION_FAILED) := Error_Record(CODE_PARALLEL_EXECUTION_FAILED, MSG_PARALLEL_EXECUTION_FAILED); -- -20111
Errors(CODE_UNKNOWN) := Error_Record(CODE_UNKNOWN, MSG_UNKNOWN); -- -20999
END INIT_ERRORS;
----------------------------------------------------------------------------------------------------
FUNCTION GET_DEFAULT_ENV
RETURN VARCHAR2
IS
vDefaultEnv CT_MRDS.a_file_manager_config.config_variable_value%TYPE;
BEGIN
select config_variable_value
into vDefaultEnv
from CT_MRDS.a_file_manager_config
where lower(environment_id)='default'
and lower(config_variable)='environmentid';
RETURN vDefaultEnv;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
PROCEDURE INIT_VARIABLES(
pEnv VARCHAR2
) IS
BEGIN
for rec in (
select
ENVIRONMENT_ID
,REGION
,NAMESPACE
,INBOXBUCKETNAME
,DATABUCKETNAME
,ARCHIVEBUCKETNAME
,CREDENTIALNAME
,LOGGINGENABLED
,MINLOGLEVEL
,DEFAULTDATEFORMAT
,CONSOLELOGGINGENABLED
from (
select environment_id, config_variable, config_variable_value from CT_MRDS.A_FILE_MANAGER_CONFIG
where environment_id=pEnv
)
pivot (
min(config_variable_value)
for config_variable in (
'Region' as Region
,'NameSpace' as NameSpace
,'InboxBucketName' as InboxBucketName
,'DataBucketName' as DataBucketName
,'ArchiveBucketName' as ArchiveBucketName
,'CredentialName' as CredentialName
,'LoggingEnabled' as LoggingEnabled
,'MinLogLevel' as MinLogLevel
,'DefaultDateFormat' as DefaultDateFormat
,'ConsoleLoggingEnabled' as ConsoleLoggingEnabled)
)
) loop
if (rec.NAMESPACE is NULL
or rec.REGION is NULL
or rec.NAMESPACE is NULL
or rec.INBOXBUCKETNAME is NULL
or rec.DATABUCKETNAME is NULL
or rec.ARCHIVEBUCKETNAME is NULL
or rec.CREDENTIALNAME is NULL
) THEN
vgMsgTmp := MSG_CONFIG_VARIABLE_NOT_SET
||cgBL||' '||'Details about existing Configuration Variables where environment_id='||pEnv||': '
||cgBL||' '||'-------------------------'
||cgBL||' '||'Region = '||rec.Region
||cgBL||' '||'NameSpace = '||rec.NameSpace
||cgBL||' '||'InboxBucketName = '||rec.InboxBucketName
||cgBL||' '||'DataBucketName = '||rec.DataBucketName
||cgBL||' '||'ArchiveBucketName = '||rec.ArchiveBucketName
||cgBL||' '||'CredentialName = '||rec.CredentialName
;
LOG_PROCESS_EVENT(vgMsgTmp, 'ERROR');
RAISE_APPLICATION_ERROR(CODE_CONFIG_VARIABLE_NOT_SET, vgMsgTmp);
elsif (rec.LOGGINGENABLED is NULL
or rec.MINLOGLEVEL is NULL
or rec.DEFAULTDATEFORMAT is NULL
) THEN
vgMsgTmp := 'Missing configuration variables'
||cgBL||' '||'Details about existing Configuration Variables where environment_id='||pEnv||': '
||cgBL||' '||'-------------------------'
||cgBL||' '||'LoggingEnabled = '||rec.LoggingEnabled
||cgBL||' '||'MinLogLevel = '||rec.MinLogLevel
||cgBL||' '||'DefaultDateFormat = '||rec.DefaultDateFormat
;
LOG_PROCESS_EVENT(vgMsgTmp, 'WARNING');
else
gvNameSpace := rec.NAMESPACE;
gvRegion := rec.REGION;
gvInboxBucketName := rec.INBOXBUCKETNAME;
gvDataBucketName := rec.DATABUCKETNAME;
gvArchiveBucketName := rec.ARCHIVEBUCKETNAME;
gvCredentialName := rec.CREDENTIALNAME;
gvInboxBucketUri := 'https://objectstorage.'||rec.REGION||'.oraclecloud.com/n/'||rec.NAMESPACE||'/b/'||rec.INBOXBUCKETNAME||'/o/';
gvDataBucketUri := 'https://objectstorage.'||rec.REGION||'.oraclecloud.com/n/'||rec.NAMESPACE||'/b/'||rec.DATABUCKETNAME||'/o/';
gvArchiveBucketUri := 'https://objectstorage.'||rec.REGION||'.oraclecloud.com/n/'||rec.NAMESPACE||'/b/'||rec.ARCHIVEBUCKETNAME||'/o/';
gvLoggingEnabled := rec.LOGGINGENABLED;
gvMinLogLevel := rec.MINLOGLEVEL;
gvDefaultDateFormat := rec.DEFAULTDATEFORMAT;
gvConsoleLoggingEnabled := NVL(rec.CONSOLELOGGINGENABLED, 'ON');
end if;
end loop;
EXCEPTION
WHEN NO_DATA_FOUND THEN
vgMsgTmp := MSG_CONFIG_VARIABLE_NOT_SET
||cgBL||' '||'No configuration found for environment_id='||pEnv||' in A_FILE_MANAGER_CONFIG table';
LOG_PROCESS_EVENT(vgMsgTmp, 'ERROR', 'pEnv='||pEnv);
RAISE_APPLICATION_ERROR(CODE_CONFIG_VARIABLE_NOT_SET, vgMsgTmp);
WHEN OTHERS THEN
vgMsgTmp := 'Unexpected error while initializing variables for environment: '||pEnv
||cgBL||' '||'SQLERRM: '||SQLERRM;
LOG_PROCESS_EVENT(vgMsgTmp, 'ERROR', 'pEnv='||pEnv);
RAISE;
END INIT_VARIABLES;
----------------------------------------------------------------------------------------------------
FUNCTION GET_ERROR_MESSAGE(
pCode PLS_INTEGER
) RETURN VARCHAR2
IS
BEGIN
RETURN Errors(pCode).message;
EXCEPTION
WHEN NO_DATA_FOUND THEN
LOG_PROCESS_EVENT('No error message found for pCode='||pCode , 'WARNING', 'pCode='||pCode);
LOG_PROCESS_EVENT('Update ENV_MANAGER package header with new code.' , 'WARNING', 'pCode='||pCode);
RETURN NULL;
WHEN OTHERS THEN
LOG_PROCESS_EVENT(MSG_UNKNOWN , 'ERROR', 'pCode='||pCode);
RAISE_APPLICATION_ERROR(CODE_UNKNOWN, MSG_UNKNOWN);
END GET_ERROR_MESSAGE;
----------------------------------------------------------------------------------------------------
FUNCTION GET_ERROR_STACK(
pFormat VARCHAR2
,pCode PLS_INTEGER
,pSourceFileReceivedKey CT_MRDS.A_SOURCE_FILE_RECEIVED.A_SOURCE_FILE_RECEIVED_KEY%TYPE DEFAULT NULL
) RETURN VARCHAR2
IS
vFullErrorCore VARCHAR2(32000);
vFullErrorMsg VARCHAR2(32000);
BEGIN
-- vgErrorMessage := SQLERRM|| cgBL;
-- vgErrorStack := DBMS_UTILITY.FORMAT_ERROR_STACK;
-- vgErrorBacktrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
vFullErrorCore :='Error Message:'
||cgBL|| SQLERRM|| cgBL
||'-------------------------------------------------------'
||cgBL||'Error Stack:'
||cgBL|| DBMS_UTILITY.FORMAT_ERROR_STACK
||'-------------------------------------------------------'
||cgBL||'Error Backtrace:'
||cgBL|| DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
-- vFullErrorCore := REGEXP_REPLACE (vFullErrorCore, pCode||': ', pCode||': '||GET_ERROR_MESSAGE(pCode) , 1, 1);
IF (pFormat = 'TABLE') THEN
vFullErrorMsg := vFullErrorCore;
ELSE
vFullErrorMsg := cgBL||'------------------------------------------------------+'
||cgBL||vFullErrorCore
||'------------------------------------------------------+';
END IF;
-- IF pSourceFileReceivedKey is not null THEN
-- vFullErrorMsg := vFullErrorMsg ||cgBL||GET_DET_SOURCE_FILE_RECEIVED_INFO(pSourceFileReceivedKey,1,1,1);
-- END IF;
RETURN vFullErrorMsg;
EXCEPTION
WHEN OTHERS THEN
LOG_PROCESS_EVENT(MSG_UNKNOWN , 'ERROR', 'pFormat='||pFormat);
RETURN NULL;
END GET_ERROR_STACK;
----------------------------------------------------------------------------------------------------
FUNCTION FORMAT_PARAMETERS(
pParameterList SYS.ODCIVARCHAR2LIST
) RETURN VARCHAR2 IS
vResult VARCHAR2(10000);
BEGIN
FOR i IN 1 .. pParameterList.COUNT LOOP
-- dbms_output.put_line('pParameterList(i): '||pParameterList(i));
if i < pParameterList.COUNT then vResult := vResult || replace(pParameterList(i), '''NULL''', 'NULL') ||' ,'|| cgBL;
else vResult := vResult || replace(pParameterList(i), '''NULL''', 'NULL');
end if;
END LOOP;
RETURN vResult;
EXCEPTION
WHEN OTHERS THEN
LOG_PROCESS_EVENT('Error while formating parameters.' , 'WARNING');
RETURN NULL;
END FORMAT_PARAMETERS;
----------------------------------------------------------------------------------------------------
PROCEDURE LOG_PROCESS_EVENT (
pLogMessage VARCHAR2
,pLogLevel VARCHAR2 DEFAULT 'ERROR'
,pParameters VARCHAR2 DEFAULT NULL
,pProcessName VARCHAR2 DEFAULT 'FILE_MANAGER'
) IS
PRAGMA AUTONOMOUS_TRANSACTION;
vLoggingEnabled VARCHAR2(10);
vMinLogLevel VARCHAR2(10);
vCallStack VARCHAR2(10000);
vProcedureName VARCHAR2(100);
vProcedureLevel PLS_INTEGER;
vTotalLines PLS_INTEGER;
vCurrentLine PLS_INTEGER;
-- Map of priority level
TYPE logLevelMap IS TABLE OF NUMBER INDEX BY VARCHAR2(10);
vLogLevels logLevelMap;
BEGIN
-- Prority logging level (higher -> more important)
vLogLevels('DEBUG') := 1;
vLogLevels('INFO') := 2;
vLogLevels('WARNING') := 3;
vLogLevels('ERROR') := 4;
-- Check id logging is TURN-OFF
IF gvLoggingEnabled = 'OFF' THEN
RETURN;
END IF;
-- Check logging level
IF vLogLevels(pLogLevel) < vLogLevels(gvMinLogLevel) THEN
RETURN;
END IF;
vCallStack := DBMS_UTILITY.FORMAT_CALL_STACK;
vProcedureName := REGEXP_SUBSTR(vCallStack, 'package body\s+\w+\.(\w+\.\w+)', 1, 2, NULL, 1);
vTotalLines := REGEXP_COUNT(vCallStack, CHR(10)) + 1;
vCurrentLine := REGEXP_COUNT(SUBSTR(vCallStack, 1, INSTR(vCallStack, vProcedureName) - 1), CHR(10)) + 1;
vProcedureLevel := (vTotalLines - vCurrentLine + 1) - 3;
vProcedureName := LPAD(vProcedureName, LENGTH(vProcedureName) + 2*vProcedureLevel, ' ');
INSERT INTO CT_MRDS.A_PROCESS_LOG (guid, username, osuser, machine, module, process_name, procedure_name, procedure_parameters, log_level, log_message)
VALUES (guid, gvUsername, gvOsuser, gvMachine, gvModule, pProcessName, vProcedureName, pParameters, pLogLevel, pLogMessage);
COMMIT;
-- Also output to console for immediate visibility (if enabled)
IF gvConsoleLoggingEnabled = 'ON' THEN
DBMS_OUTPUT.PUT_LINE('[' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') || '] [' || pLogLevel || '] ' || vProcedureName || ': ' || pLogMessage);
END IF;
END LOG_PROCESS_EVENT;
----------------------------------------------------------------------------------------------------
PROCEDURE LOG_PROCESS_ERROR(
pLogMessage IN VARCHAR2,
pParameters IN VARCHAR2 DEFAULT NULL,
pProcessName IN VARCHAR2 DEFAULT 'FILE_MANAGER'
) IS
PRAGMA AUTONOMOUS_TRANSACTION;
vCallStack VARCHAR2(32767);
vErrorStack VARCHAR2(32767);
vErrorBacktrace VARCHAR2(32767);
vAdjustedBacktrace VARCHAR2(32767);
vErrorContext VARCHAR2(4000);
vProcName VARCHAR2(100);
vProcedureLevel PLS_INTEGER;
vTotalLines PLS_INTEGER;
vCurrentLine PLS_INTEGER;
vFullErrorMessage CLOB;
vTimestamp VARCHAR2(30);
vSessionInfo VARCHAR2(1000);
BEGIN
-- Check if logging is disabled
IF gvLoggingEnabled = 'OFF' THEN
RETURN;
END IF;
-- Capture all available error information
vErrorStack := DBMS_UTILITY.FORMAT_ERROR_STACK;
vErrorBacktrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
vCallStack := DBMS_UTILITY.FORMAT_CALL_STACK;
vTimestamp := TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS');
-- Capture session information for better context
vSessionInfo := 'Session ID: ' || SYS_CONTEXT('USERENV', 'SID') ||
', User: ' || SYS_CONTEXT('USERENV', 'SESSION_USER') ||
', Module: ' || SYS_CONTEXT('USERENV', 'MODULE') ||
', Client Info: ' || NVL(SYS_CONTEXT('USERENV', 'CLIENT_INFO'), 'N/A') ||
', Action: ' || NVL(SYS_CONTEXT('USERENV', 'ACTION'), 'N/A');
-- Build error context information
vErrorContext := 'Environment: ' || gvEnv ||
', Process: ' || NVL(pProcessName, 'UNKNOWN') ||
', Timestamp: ' || vTimestamp ||
', SQLCODE: ' || SQLCODE ||
', Transaction Active: ' || CASE WHEN DBMS_TRANSACTION.STEP_ID IS NOT NULL THEN 'YES' ELSE 'NO' END;
-- Extract procedure name and nesting level from call stack
-- Always extract actual procedure name from call stack for precise error location
vProcName := REGEXP_SUBSTR(vCallStack, 'package body\s+\w+\.(\w+\.\w+)', 1, 2, NULL, 1);
-- If we couldn't extract procedure name from call stack, use provided process name
IF vProcName IS NULL THEN
vProcName := NVL(pProcessName, 'UNKNOWN');
END IF;
vTotalLines := REGEXP_COUNT(vCallStack, CHR(10)) + 1;
vCurrentLine := REGEXP_COUNT(SUBSTR(vCallStack, 1, INSTR(vCallStack, vProcName) - 1), CHR(10)) + 1;
vProcedureLevel := (vTotalLines - vCurrentLine + 1) - 3;
vProcName := LPAD(vProcName, LENGTH(vProcName) + 2*vProcedureLevel, ' ');
-- Enhance line number display to show direct _BODY.sql file line numbers
-- Since packages are now split into separate _SPEC and _BODY files, line numbers map directly
vAdjustedBacktrace := REGEXP_REPLACE(vErrorBacktrace,
'at "CT_MRDS\.FILE_MANAGER", line ([0-9]+)',
'at "CT_MRDS.FILE_MANAGER", line \1 (-> FILE_MANAGER_BODY.sql:line \1)', 1, 0, 'i');
vAdjustedBacktrace := REGEXP_REPLACE(vAdjustedBacktrace,
'at "CT_MRDS\.ENV_MANAGER", line ([0-9]+)',
'at "CT_MRDS.ENV_MANAGER", line \1 (-> ENV_MANAGER_BODY.sql:line \1)', 1, 0, 'i');
-- Build comprehensive error message with professional formatting
vFullErrorMessage := 'ERROR REPORT' || cgBL ||
'-------------------------------------------------------' || cgBL ||
'ERROR SUMMARY' || cgBL ||
' Message: ' || pLogMessage || cgBL ||
' Context: ' || vErrorContext || cgBL ||
'-------------------------------------------------------' || cgBL ||
'SESSION INFORMATION' || cgBL ||
' ' || vSessionInfo || cgBL ||
'-------------------------------------------------------' || cgBL ||
'ERROR STACK (Oracle Internal)' || cgBL ||
vErrorStack ||
'-------------------------------------------------------' || cgBL ||
'BACKTRACE INFORMATION (Oracle Internal)' || cgBL ||
vErrorBacktrace ||
'-------------------------------------------------------' || cgBL ||
'CALL STACK (Execution Path)' || cgBL ||
vCallStack ||
'-------------------------------------------------------' || cgBL ||
'QUICK REFERENCE' || cgBL ||
' SQLCODE: ' || SQLCODE || cgBL ||
' SQLERRM: ' || SQLERRM || cgBL ||
' Timestamp: ' || vTimestamp || cgBL ||
' Parameters: ' || NVL(pParameters, 'None provided') || cgBL ||
'-------------------------------------------------------';
-- Insert comprehensive error record into log table
-- Note: LOG_MESSAGE is VARCHAR2(4000), so we'll truncate if needed but include key info
INSERT INTO CT_MRDS.A_PROCESS_LOG (guid, username, osuser, machine, module, process_name, procedure_name, procedure_parameters, log_level, log_message)
VALUES (guid, gvUsername, gvOsuser, gvMachine, gvModule, NVL(pProcessName, 'FILE_MANAGER'), vProcName, pParameters, 'ERROR',
CASE
WHEN LENGTH(vFullErrorMessage) <= 4000 THEN vFullErrorMessage
ELSE SUBSTR(vFullErrorMessage, 1, 3950) || '... [TRUNCATED]'
END);
COMMIT;
-- Enhanced console output for immediate visibility (if enabled)
IF gvConsoleLoggingEnabled = 'ON' THEN
DBMS_OUTPUT.PUT_LINE('=======================================================');
DBMS_OUTPUT.PUT_LINE('ERROR DETECTED AT: ' || vTimestamp);
DBMS_OUTPUT.PUT_LINE('PROCEDURE: ' || NVL(vProcName, 'UNKNOWN'));
DBMS_OUTPUT.PUT_LINE('MESSAGE: ' || pLogMessage);
DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE || ' | ENVIRONMENT: ' || gvEnv);
-- Extract and show the most relevant file and line number
IF INSTR(vAdjustedBacktrace, '-> ') > 0 THEN
DBMS_OUTPUT.PUT_LINE('SOURCE FILE LOCATION: ' || REGEXP_SUBSTR(vAdjustedBacktrace, '-> [^)]+'));
END IF;
DBMS_OUTPUT.PUT_LINE('FULL DETAILS: Query A_PROCESS_LOG table for complete diagnostic info');
DBMS_OUTPUT.PUT_LINE('QUERY (This Error): SELECT * FROM CT_MRDS.A_PROCESS_LOG WHERE GUID = ''' || guid || ''' ORDER BY LOG_TIMESTAMP DESC;');
DBMS_OUTPUT.PUT_LINE('QUERY (Recent All): SELECT * FROM CT_MRDS.A_PROCESS_LOG WHERE LOG_TIMESTAMP >= SYSDATE - 1/1440 ORDER BY LOG_TIMESTAMP DESC;');
DBMS_OUTPUT.PUT_LINE('=======================================================');
END IF;
END LOG_PROCESS_ERROR;
----------------------------------------------------------------------------------------------------
FUNCTION ANALYZE_VALIDATION_ERRORS(
pValidationLogTable VARCHAR2,
pTemplateSchema VARCHAR2,
pTemplateTable VARCHAR2,
pCsvFileUri VARCHAR2
) RETURN VARCHAR2
IS
vAnalysisReport CLOB := '';
vCsvHeader VARCHAR2(4000);
vExpectedOrder VARCHAR2(4000);
vCsvOrder VARCHAR2(4000);
vErrorDetails VARCHAR2(32000) := '';
vSolutions VARCHAR2(4000);
vColumnMismatch VARCHAR2(1000);
vErrorCount NUMBER := 0;
vFirstDataError VARCHAR2(1000);
vErrorColumn VARCHAR2(100);
vErrorValue VARCHAR2(500);
vExpectedType VARCHAR2(100);
vTemplateColCount NUMBER := 0;
vCsvColCount NUMBER := 0;
vExcessColumns VARCHAR2(2000);
vCsvFirstLine VARCHAR2(4000);
-- Cursor for template table columns
CURSOR c_template_columns IS
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_ID
FROM ALL_TAB_COLUMNS
WHERE OWNER = UPPER(REGEXP_SUBSTR(pTemplateSchema || '.' || pTemplateTable, '^([^.]+)'))
AND TABLE_NAME = UPPER(REGEXP_SUBSTR(pTemplateSchema || '.' || pTemplateTable, '\.(.+)$', 1, 1, NULL, 1))
ORDER BY COLUMN_ID;
BEGIN
-- Build expected column order from template table and count columns
FOR rec IN c_template_columns LOOP
IF vExpectedOrder IS NOT NULL THEN
vExpectedOrder := vExpectedOrder || ', ';
END IF;
vExpectedOrder := vExpectedOrder || rec.COLUMN_NAME;
vTemplateColCount := vTemplateColCount + 1;
END LOOP;
-- Parse validation log table for errors and CSV structure
BEGIN
-- Try to extract error information from the validation log table
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || pValidationLogTable ||
' WHERE record LIKE ''error processing column%'''
INTO vErrorCount;
-- Get first error details
IF vErrorCount > 0 THEN
EXECUTE IMMEDIATE 'SELECT record FROM ' || pValidationLogTable ||
' WHERE record LIKE ''error processing column%'' AND ROWNUM = 1'
INTO vFirstDataError;
-- Parse error to extract column name and error type
vErrorColumn := REGEXP_SUBSTR(vFirstDataError, 'error processing column ([A-Z_]+)', 1, 1, NULL, 1);
-- Try to get the actual error value from ORA-01722 message
BEGIN
EXECUTE IMMEDIATE 'SELECT record FROM ' || pValidationLogTable ||
' WHERE record LIKE ''ORA-01722%'' AND ROWNUM = 1'
INTO vFirstDataError;
vErrorValue := REGEXP_SUBSTR(vFirstDataError, 'string value containing ''([^'']+)''', 1, 1, NULL, 1);
EXCEPTION
WHEN NO_DATA_FOUND THEN
vErrorValue := 'unknown value';
WHEN OTHERS THEN
vErrorValue := 'parsing error';
END;
END IF;
-- Try to extract CSV structure from validation log field definitions
BEGIN
EXECUTE IMMEDIATE '
SELECT LISTAGG(
REGEXP_SUBSTR(record, ''^\s+([A-Z_]+)\s+'', 1, 1, NULL, 1),
'', ''
) WITHIN GROUP (ORDER BY ROWNUM)
FROM ' || pValidationLogTable || '
WHERE record LIKE '' %CHAR%''
AND record NOT LIKE ''%Fields in Data Source%''
AND REGEXP_SUBSTR(record, ''^\s+([A-Z_]+)\s+'') IS NOT NULL'
INTO vCsvOrder;
-- Count CSV columns from parsed structure
IF vCsvOrder IS NOT NULL THEN
vCsvColCount := REGEXP_COUNT(vCsvOrder, ',') + 1;
END IF;
EXCEPTION
WHEN OTHERS THEN
vCsvOrder := 'Unable to determine CSV column order from validation log';
END;
-- Alternative method: Try to read first line of CSV directly for column count
IF vCsvColCount = 0 THEN
BEGIN
-- This is a fallback - try to get CSV header from external source if possible
-- Note: This would require DBMS_CLOUD.GET_OBJECT or similar approach
-- For now, we'll rely on the validation log parsing
NULL;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
END IF;
EXCEPTION
WHEN OTHERS THEN
vErrorDetails := 'Error analyzing validation log: ' || SQLERRM;
END;
-- Detect column order mismatch and excess columns
IF vCsvOrder IS NOT NULL AND vExpectedOrder IS NOT NULL THEN
IF UPPER(REPLACE(vCsvOrder, ' ', '')) != UPPER(REPLACE(vExpectedOrder, ' ', '')) THEN
vColumnMismatch := 'YES';
ELSE
vColumnMismatch := 'NO';
END IF;
END IF;
-- Check for excess columns
IF vCsvColCount > vTemplateColCount THEN
-- Try to identify which columns are excess
IF vCsvOrder IS NOT NULL THEN
-- Parse CSV columns and compare with template
DECLARE
vCsvCols SYS.ODCIVARCHAR2LIST;
vTemplateCols SYS.ODCIVARCHAR2LIST;
vExcessFound VARCHAR2(1) := 'N';
i NUMBER;
BEGIN
-- Split CSV columns
SELECT TRIM(REGEXP_SUBSTR(vCsvOrder, '[^,]+', 1, LEVEL))
BULK COLLECT INTO vCsvCols
FROM DUAL
CONNECT BY REGEXP_SUBSTR(vCsvOrder, '[^,]+', 1, LEVEL) IS NOT NULL;
-- Split template columns
SELECT TRIM(REGEXP_SUBSTR(vExpectedOrder, '[^,]+', 1, LEVEL))
BULK COLLECT INTO vTemplateCols
FROM DUAL
CONNECT BY REGEXP_SUBSTR(vExpectedOrder, '[^,]+', 1, LEVEL) IS NOT NULL;
-- Find excess columns (those in CSV but not in template)
FOR i IN 1..vCsvCols.COUNT LOOP
DECLARE
vFoundInTemplate BOOLEAN := FALSE;
j NUMBER;
BEGIN
-- Check if CSV column exists in template
FOR j IN 1..vTemplateCols.COUNT LOOP
IF UPPER(TRIM(vCsvCols(i))) = UPPER(TRIM(vTemplateCols(j))) THEN
vFoundInTemplate := TRUE;
EXIT;
END IF;
END LOOP;
-- If not found in template, it's an excess column
IF NOT vFoundInTemplate THEN
IF vExcessFound = 'Y' THEN
vExcessColumns := vExcessColumns || ', ';
END IF;
vExcessColumns := vExcessColumns || vCsvCols(i);
vExcessFound := 'Y';
END IF;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
vExcessColumns := 'Unable to determine specific excess columns';
END;
END IF;
END IF;
-- Build comprehensive analysis report
vAnalysisReport := 'FILE VALIDATION FAILED - DETAILED ANALYSIS' || cgBL ||
'=================================================' || cgBL || cgBL;
-- Column structure analysis
vAnalysisReport := vAnalysisReport ||
'COLUMN STRUCTURE ANALYSIS:' || cgBL ||
'---------------------------------------------------' || cgBL ||
'Template Expected Order: ' || vExpectedOrder || cgBL ||
'Template Column Count: ' || vTemplateColCount || cgBL ||
'CSV Detected Order: ' || NVL(vCsvOrder, 'Unknown') || cgBL ||
'CSV Column Count: ' || vCsvColCount || cgBL || cgBL;
-- Report column count issues
IF vCsvColCount > vTemplateColCount THEN
vAnalysisReport := vAnalysisReport ||
'EXCESS COLUMNS DETECTED!' || cgBL ||
'CSV file has ' || (vCsvColCount - vTemplateColCount) || ' more columns than template allows.' || cgBL;
IF vExcessColumns IS NOT NULL THEN
vAnalysisReport := vAnalysisReport ||
'Excess columns found: ' || vExcessColumns || cgBL;
END IF;
vAnalysisReport := vAnalysisReport || cgBL;
END IF;
-- Report column order issues
IF vColumnMismatch = 'YES' THEN
vAnalysisReport := vAnalysisReport ||
'COLUMN ORDER MISMATCH DETECTED!' || cgBL ||
'CSV columns are in different order than template expects.' || cgBL || cgBL;
END IF;
-- Specific error analysis
IF vErrorCount > 0 THEN
vAnalysisReport := vAnalysisReport ||
'SPECIFIC ERRORS FOUND:' || cgBL ||
'---------------------------------------------------' || cgBL;
IF vErrorColumn IS NOT NULL THEN
-- Get expected data type for error column
FOR rec IN c_template_columns LOOP
IF rec.COLUMN_NAME = vErrorColumn THEN
vExpectedType := rec.DATA_TYPE;
EXIT;
END IF;
END LOOP;
vAnalysisReport := vAnalysisReport ||
'1. Column ' || vErrorColumn || ': Expected ' || vExpectedType ||
', received "' || NVL(vErrorValue, 'unknown value') || '" (TEXT)' || cgBL ||
' → CSV position contains different data type than expected' || cgBL;
END IF;
vAnalysisReport := vAnalysisReport ||
'Total validation errors found: ' || vErrorCount || cgBL || cgBL;
END IF;
-- Solutions section
vAnalysisReport := vAnalysisReport ||
'SUGGESTED SOLUTIONS:' || cgBL ||
'---------------------------------------------------' || cgBL;
-- Solutions for excess columns
IF vCsvColCount > vTemplateColCount THEN
vAnalysisReport := vAnalysisReport ||
'FOR EXCESS COLUMNS:' || cgBL ||
'• Remove extra columns from CSV file' || cgBL ||
'• Keep only these columns in this order: ' || vExpectedOrder || cgBL;
IF vExcessColumns IS NOT NULL THEN
vAnalysisReport := vAnalysisReport ||
'• Specifically remove: ' || vExcessColumns || cgBL;
END IF;
vAnalysisReport := vAnalysisReport || cgBL;
END IF;
-- Solutions for column order
IF vColumnMismatch = 'YES' THEN
vAnalysisReport := vAnalysisReport ||
'FOR COLUMN ORDER:' || cgBL ||
'• Reorder CSV columns to match template: ' || vExpectedOrder || cgBL ||
'• Or update template table column order to match CSV file' || cgBL || cgBL;
END IF;
-- General solutions
vAnalysisReport := vAnalysisReport ||
'GENERAL RECOMMENDATIONS:' || cgBL ||
'• Ensure CSV has exactly ' || vTemplateColCount || ' columns' || cgBL ||
'• Verify column names match template table exactly' || cgBL ||
'• Check data types in each column match expectations' || cgBL || cgBL;
-- Validation log reference
vAnalysisReport := vAnalysisReport ||
'TECHNICAL DETAILS:' || cgBL ||
'---------------------------------------------------' || cgBL ||
'Validation Log Table: ' || pValidationLogTable || cgBL ||
'Template Table: ' || pTemplateSchema || '.' || pTemplateTable || cgBL ||
'CSV File: ' || pCsvFileUri || cgBL ||
'Query validation details: SELECT * FROM ' || pValidationLogTable || ';' || cgBL;
RETURN vAnalysisReport;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Error generating validation analysis: ' || SQLERRM || cgBL ||
'Validation Log Table: ' || pValidationLogTable || cgBL ||
'Check table manually: SELECT * FROM ' || pValidationLogTable || ';';
END ANALYZE_VALIDATION_ERRORS;
----------------------------------------------------------------------------------------------------
-- PACKAGE VERSION MANAGEMENT FUNCTIONS IMPLEMENTATION
----------------------------------------------------------------------------------------------------
FUNCTION GET_VERSION
RETURN VARCHAR2
IS
BEGIN
RETURN PACKAGE_VERSION;
END GET_VERSION;
----------------------------------------------------------------------------------------------------
FUNCTION GET_BUILD_INFO
RETURN VARCHAR2
IS
BEGIN
RETURN GET_PACKAGE_VERSION_INFO(
pPackageName => 'ENV_MANAGER',
pVersion => PACKAGE_VERSION,
pBuildDate => PACKAGE_BUILD_DATE,
pAuthor => PACKAGE_AUTHOR
);
END GET_BUILD_INFO;
----------------------------------------------------------------------------------------------------
FUNCTION GET_VERSION_HISTORY
RETURN VARCHAR2
IS
BEGIN
RETURN FORMAT_VERSION_HISTORY(
pPackageName => 'ENV_MANAGER',
pVersionHistory => VERSION_HISTORY
);
END GET_VERSION_HISTORY;
----------------------------------------------------------------------------------------------------
FUNCTION GET_PACKAGE_VERSION_INFO(
pPackageName VARCHAR2,
pVersion VARCHAR2,
pBuildDate VARCHAR2,
pAuthor VARCHAR2
) RETURN VARCHAR2
IS
BEGIN
RETURN 'Package: ' || pPackageName || cgBL ||
'Version: ' || pVersion || cgBL ||
'Build Date: ' || pBuildDate || cgBL ||
'Author: ' || pAuthor;
END GET_PACKAGE_VERSION_INFO;
----------------------------------------------------------------------------------------------------
FUNCTION FORMAT_VERSION_HISTORY(
pPackageName VARCHAR2,
pVersionHistory VARCHAR2
) RETURN VARCHAR2
IS
BEGIN
RETURN pPackageName || ' Version History:' || cgBL || pVersionHistory;
END FORMAT_VERSION_HISTORY;
----------------------------------------------------------------------------------------------------
-- PACKAGE HASH + CHANGE DETECTION FUNCTIONS IMPLEMENTATION
----------------------------------------------------------------------------------------------------
FUNCTION CALCULATE_PACKAGE_HASH(
pPackageOwner VARCHAR2,
pPackageName VARCHAR2,
pPackageType VARCHAR2
) RETURN VARCHAR2
IS
vSourceCode CLOB;
vHash VARCHAR2(64);
vRawHash RAW(32);
BEGIN
-- Build complete source code from ALL_SOURCE using XMLAGG (no 4000 char limit)
-- CRITICAL: Cannot use LISTAGG due to VARCHAR2 limit
SELECT XMLAGG(XMLELEMENT(E, TEXT) ORDER BY LINE).GETCLOBVAL()
INTO vSourceCode
FROM ALL_SOURCE
WHERE OWNER = UPPER(pPackageOwner)
AND NAME = UPPER(pPackageName)
AND TYPE = UPPER(pPackageType);
-- If empty, return NULL
IF vSourceCode IS NULL OR DBMS_LOB.GETLENGTH(vSourceCode) = 0 THEN
RETURN NULL;
END IF;
-- Calculate SHA256 hash directly from CLOB
-- DBMS_CRYPTO.HASH has overload for CLOB in Oracle 19c+
vRawHash := DBMS_CRYPTO.HASH(
src => vSourceCode,
typ => DBMS_CRYPTO.HASH_SH256
);
-- Convert to hex string
vHash := LOWER(RAWTOHEX(vRawHash));
RETURN vHash;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
WHEN OTHERS THEN
LOG_PROCESS_ERROR('Error calculating package hash: ' || SQLERRM,
'pPackageOwner=' || pPackageOwner || ', pPackageName=' || pPackageName);
RETURN NULL;
END CALCULATE_PACKAGE_HASH;
----------------------------------------------------------------------------------------------------
PROCEDURE TRACK_PACKAGE_VERSION(
pPackageOwner VARCHAR2,
pPackageName VARCHAR2,
pPackageVersion VARCHAR2,
pPackageBuildDate VARCHAR2,
pPackageAuthor VARCHAR2
)
IS
vHashSpec VARCHAR2(64);
vHashBody VARCHAR2(64);
vLastHashSpec VARCHAR2(64);
vLastHashBody VARCHAR2(64);
vLastVersion VARCHAR2(10);
vLineCountSpec NUMBER;
vLineCountBody NUMBER;
vChangeDetected CHAR(1) := 'N';
vChangeMessage VARCHAR2(4000);
vParameters VARCHAR2(4000);
BEGIN
vParameters := FORMAT_PARAMETERS(SYS.ODCIVARCHAR2LIST(
'pPackageOwner => ''' || pPackageOwner || '''',
'pPackageName => ''' || pPackageName || '''',
'pPackageVersion => ''' || pPackageVersion || ''''
));
LOG_PROCESS_EVENT('Start TRACK_PACKAGE_VERSION', 'INFO', vParameters);
-- Calculate current hashes
vHashSpec := CALCULATE_PACKAGE_HASH(pPackageOwner, pPackageName, 'PACKAGE');
vHashBody := CALCULATE_PACKAGE_HASH(pPackageOwner, pPackageName, 'PACKAGE BODY');
-- Get line counts
BEGIN
SELECT COUNT(*)
INTO vLineCountSpec
FROM ALL_SOURCE
WHERE OWNER = UPPER(pPackageOwner)
AND NAME = UPPER(pPackageName)
AND TYPE = 'PACKAGE';
EXCEPTION
WHEN NO_DATA_FOUND THEN
vLineCountSpec := 0;
END;
BEGIN
SELECT COUNT(*)
INTO vLineCountBody
FROM ALL_SOURCE
WHERE OWNER = UPPER(pPackageOwner)
AND NAME = UPPER(pPackageName)
AND TYPE = 'PACKAGE BODY';
EXCEPTION
WHEN NO_DATA_FOUND THEN
vLineCountBody := 0;
END;
-- Get last tracked version and hashes
BEGIN
SELECT PACKAGE_VERSION, SOURCE_CODE_HASH_SPEC, SOURCE_CODE_HASH_BODY
INTO vLastVersion, vLastHashSpec, vLastHashBody
FROM CT_MRDS.A_PACKAGE_VERSION_TRACKING
WHERE PACKAGE_OWNER = UPPER(pPackageOwner)
AND PACKAGE_NAME = UPPER(pPackageName)
ORDER BY TRACKING_DATE DESC
FETCH FIRST 1 ROW ONLY;
-- Check if hash changed but version didn't
IF (vHashSpec != vLastHashSpec OR NVL(vHashBody,'X') != NVL(vLastHashBody,'X'))
AND pPackageVersion = vLastVersion THEN
vChangeDetected := 'Y';
vChangeMessage := 'WARNING: Source code changed without version update!' || cgBL ||
'Last Version: ' || vLastVersion || cgBL ||
'Current Version: ' || pPackageVersion || cgBL;
IF vHashSpec != vLastHashSpec THEN
vChangeMessage := vChangeMessage ||
'SPEC Changed - Hash: ' || SUBSTR(vHashSpec, 1, 16) || '... (was: ' ||
SUBSTR(vLastHashSpec, 1, 16) || '...)' || cgBL;
END IF;
IF NVL(vHashBody,'X') != NVL(vLastHashBody,'X') THEN
vChangeMessage := vChangeMessage ||
'BODY Changed - Hash: ' || SUBSTR(vHashBody, 1, 16) || '... (was: ' ||
SUBSTR(NVL(vLastHashBody,'NULL'), 1, 16) || '...)' || cgBL;
END IF;
vChangeMessage := vChangeMessage ||
'RECOMMENDATION: Update PACKAGE_VERSION constant and PACKAGE_BUILD_DATE';
LOG_PROCESS_EVENT(vChangeMessage, 'WARNING', vParameters);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- First time tracking this package
vChangeDetected := 'N';
vChangeMessage := 'First tracking record for this package';
LOG_PROCESS_EVENT(vChangeMessage, 'INFO', vParameters);
END;
-- Insert tracking record
INSERT INTO CT_MRDS.A_PACKAGE_VERSION_TRACKING (
PACKAGE_OWNER,
PACKAGE_NAME,
PACKAGE_TYPE,
PACKAGE_VERSION,
PACKAGE_BUILD_DATE,
PACKAGE_AUTHOR,
SOURCE_CODE_HASH_SPEC,
SOURCE_CODE_HASH_BODY,
LINE_COUNT_SPEC,
LINE_COUNT_BODY,
DETECTED_CHANGE_WITHOUT_VERSION,
CHANGE_DETECTION_MESSAGE
) VALUES (
UPPER(pPackageOwner),
UPPER(pPackageName),
'BOTH',
pPackageVersion,
pPackageBuildDate,
pPackageAuthor,
vHashSpec,
vHashBody,
vLineCountSpec,
vLineCountBody,
vChangeDetected,
vChangeMessage
);
COMMIT;
LOG_PROCESS_EVENT('End TRACK_PACKAGE_VERSION - Record inserted', 'INFO', vParameters);
EXCEPTION
WHEN OTHERS THEN
LOG_PROCESS_ERROR('Error in TRACK_PACKAGE_VERSION: ' || SQLERRM, vParameters);
RAISE;
END TRACK_PACKAGE_VERSION;
----------------------------------------------------------------------------------------------------
FUNCTION CHECK_PACKAGE_CHANGES(
pPackageOwner VARCHAR2,
pPackageName VARCHAR2
) RETURN VARCHAR2
IS
vCurrentHashSpec VARCHAR2(64);
vCurrentHashBody VARCHAR2(64);
vLastHashSpec VARCHAR2(64);
vLastHashBody VARCHAR2(64);
vLastVersion VARCHAR2(10);
vLastTrackingDate TIMESTAMP;
vChangeReport VARCHAR2(4000);
vSpecChanged BOOLEAN := FALSE;
vBodyChanged BOOLEAN := FALSE;
BEGIN
-- Get current hashes
vCurrentHashSpec := CALCULATE_PACKAGE_HASH(pPackageOwner, pPackageName, 'PACKAGE');
vCurrentHashBody := CALCULATE_PACKAGE_HASH(pPackageOwner, pPackageName, 'PACKAGE BODY');
-- Get last tracked hashes
BEGIN
SELECT PACKAGE_VERSION, SOURCE_CODE_HASH_SPEC, SOURCE_CODE_HASH_BODY, TRACKING_DATE
INTO vLastVersion, vLastHashSpec, vLastHashBody, vLastTrackingDate
FROM CT_MRDS.A_PACKAGE_VERSION_TRACKING
WHERE PACKAGE_OWNER = UPPER(pPackageOwner)
AND PACKAGE_NAME = UPPER(pPackageName)
ORDER BY TRACKING_DATE DESC
FETCH FIRST 1 ROW ONLY;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Package ' || pPackageOwner || '.' || pPackageName || ' has never been tracked.' || cgBL ||
'Run TRACK_PACKAGE_VERSION to establish baseline.';
END;
-- Check for changes
IF vCurrentHashSpec != vLastHashSpec THEN
vSpecChanged := TRUE;
END IF;
IF NVL(vCurrentHashBody, 'X') != NVL(vLastHashBody, 'X') THEN
vBodyChanged := TRUE;
END IF;
-- Build report
IF vSpecChanged OR vBodyChanged THEN
vChangeReport := 'WARNING: Package ' || pPackageOwner || '.' || pPackageName || ' has changed!' || cgBL ||
'========================================' || cgBL ||
'Last Tracked Version: ' || vLastVersion || cgBL ||
'Last Tracked Date: ' || TO_CHAR(vLastTrackingDate, 'YYYY-MM-DD HH24:MI:SS') || cgBL ||
cgBL;
IF vSpecChanged THEN
vChangeReport := vChangeReport ||
'SPECIFICATION Changed:' || cgBL ||
' Current Hash: ' || SUBSTR(vCurrentHashSpec, 1, 16) || '...' || cgBL ||
' Last Hash: ' || SUBSTR(vLastHashSpec, 1, 16) || '...' || cgBL ||
cgBL;
END IF;
IF vBodyChanged THEN
vChangeReport := vChangeReport ||
'BODY Changed:' || cgBL ||
' Current Hash: ' || SUBSTR(NVL(vCurrentHashBody, 'NULL'), 1, 16) || '...' || cgBL ||
' Last Hash: ' || SUBSTR(NVL(vLastHashBody, 'NULL'), 1, 16) || '...' || cgBL ||
cgBL;
END IF;
vChangeReport := vChangeReport ||
'RECOMMENDATION:' || cgBL ||
'1. Update PACKAGE_VERSION constant' || cgBL ||
'2. Update PACKAGE_BUILD_DATE constant' || cgBL ||
'3. Add entry to VERSION_HISTORY' || cgBL ||
'4. Call TRACK_PACKAGE_VERSION to update tracking';
ELSE
vChangeReport := 'OK: Package ' || pPackageOwner || '.' || pPackageName || ' has not changed.' || cgBL ||
'Last Tracked: ' || TO_CHAR(vLastTrackingDate, 'YYYY-MM-DD HH24:MI:SS') || cgBL ||
'Version: ' || vLastVersion;
END IF;
RETURN vChangeReport;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Error checking package changes: ' || SQLERRM;
END CHECK_PACKAGE_CHANGES;
----------------------------------------------------------------------------------------------------
FUNCTION GET_PACKAGE_HASH_INFO(
pPackageOwner VARCHAR2,
pPackageName VARCHAR2
) RETURN VARCHAR2
IS
vCurrentHashSpec VARCHAR2(64);
vCurrentHashBody VARCHAR2(64);
vLastHashSpec VARCHAR2(64);
vLastHashBody VARCHAR2(64);
vLastVersion VARCHAR2(10);
vLastTrackingDate TIMESTAMP;
vLastChangeDetected CHAR(1);
vInfo VARCHAR2(4000);
BEGIN
-- Get current hashes
vCurrentHashSpec := CALCULATE_PACKAGE_HASH(pPackageOwner, pPackageName, 'PACKAGE');
vCurrentHashBody := CALCULATE_PACKAGE_HASH(pPackageOwner, pPackageName, 'PACKAGE BODY');
-- Get last tracking info
BEGIN
SELECT PACKAGE_VERSION,
SOURCE_CODE_HASH_SPEC,
SOURCE_CODE_HASH_BODY,
TRACKING_DATE,
DETECTED_CHANGE_WITHOUT_VERSION
INTO vLastVersion, vLastHashSpec, vLastHashBody, vLastTrackingDate, vLastChangeDetected
FROM CT_MRDS.A_PACKAGE_VERSION_TRACKING
WHERE PACKAGE_OWNER = UPPER(pPackageOwner)
AND PACKAGE_NAME = UPPER(pPackageName)
ORDER BY TRACKING_DATE DESC
FETCH FIRST 1 ROW ONLY;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Package: ' || pPackageOwner || '.' || pPackageName || cgBL ||
'Status: Never tracked' || cgBL ||
'Current Hash (SPEC): ' || SUBSTR(vCurrentHashSpec, 1, 16) || '...' || cgBL ||
'Current Hash (BODY): ' || SUBSTR(NVL(vCurrentHashBody, 'NULL'), 1, 16) || '...';
END;
-- Build info report
vInfo := 'Package: ' || pPackageOwner || '.' || pPackageName || cgBL ||
'Current Version: ' || vLastVersion || cgBL ||
'Last Tracked: ' || TO_CHAR(vLastTrackingDate, 'YYYY-MM-DD HH24:MI:SS') || cgBL ||
cgBL ||
'Current Hash (SPEC): ' || SUBSTR(vCurrentHashSpec, 1, 32) || '...' || cgBL ||
'Last Hash (SPEC): ' || SUBSTR(vLastHashSpec, 1, 32) || '...' || cgBL;
IF vCurrentHashBody IS NOT NULL OR vLastHashBody IS NOT NULL THEN
vInfo := vInfo ||
'Current Hash (BODY): ' || SUBSTR(NVL(vCurrentHashBody, 'NULL'), 1, 32) || '...' || cgBL ||
'Last Hash (BODY): ' || SUBSTR(NVL(vLastHashBody, 'NULL'), 1, 32) || '...' || cgBL;
END IF;
vInfo := vInfo || cgBL;
-- Status
IF vCurrentHashSpec = vLastHashSpec AND NVL(vCurrentHashBody, 'X') = NVL(vLastHashBody, 'X') THEN
vInfo := vInfo || 'Status: OK - No changes detected';
ELSE
vInfo := vInfo || 'Status: CHANGED - Source code modified since last tracking';
END IF;
IF vLastChangeDetected = 'Y' THEN
vInfo := vInfo || cgBL || 'Last Tracking Warning: Change detected without version update';
END IF;
RETURN vInfo;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Error getting package hash info: ' || SQLERRM;
END GET_PACKAGE_HASH_INFO;
----------------------------------------------------------------------------------------------------
BEGIN
INIT_ERRORS;
guid := sys_guid();
gvUsername := SYS_CONTEXT('USERENV', 'SESSION_USER');
gvOsuser := SYS_CONTEXT('USERENV', 'OS_USER');
gvMachine := SYS_CONTEXT('USERENV', 'HOST');
gvModule := SYS_CONTEXT('USERENV', 'MODULE');
-- Get info about EnvironmentID. Without it package cannot proceed further.
-- Information about environment is needed to get proper configuration values
-- It can be set up in two different ways :
-- 1. Set it on session level: execute DBMS_SESSION.SET_IDENTIFIER (client_id => 'dev');
-- 2. Set it on configuration level: Insert into CT_MRDS.A_FILE_MANAGER_CONFIG (ENVIRONMENT_ID,CONFIG_VARIABLE,CONFIG_VARIABLE_VALUE) values ('default','environment_id','dev');
-- Session level setup (1.) takes precedence over configuration level one (2.)
gvEnv := nvl(SYS_CONTEXT ('USERENV', 'CLIENT_IDENTIFIER'), GET_DEFAULT_ENV());
if gvEnv is null then
dbms_output.put_line(MSG_ENVIRONMENT_NOT_SET);
LOG_PROCESS_EVENT(MSG_ENVIRONMENT_NOT_SET, 'ERROR');
RAISE_APPLICATION_ERROR(CODE_ENVIRONMENT_NOT_SET, MSG_ENVIRONMENT_NOT_SET);
else
dbms_output.put_line('EnvironmentID set to: '||gvEnv);
end if;
INIT_VARIABLES(pEnv => gvEnv);
END ENV_MANAGER;
/
/