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; / /