-- Oracle procedure to export table data with TAB-delimited columns
-- Parameters:
--   p_table_name: Name of the table to export
--   p_skip_rows: Number of rows to skip (0-based, so 0 means start from first row)
--   p_file_size_limit: Maximum number of characters to export (file size limit)
--   p_max_lines_per_file: Maximum number of lines (records) per batch (default: 30000)
-- Usage: 
--   SET SERVEROUTPUT ON SIZE UNLIMITED
--   EXEC export_job_info('job_info_1', 0, 1000000, 30000);

-- Drop existing objects if they exist
BEGIN
    EXECUTE IMMEDIATE 'DROP FUNCTION export_job_info';
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE != -4043 THEN  -- ORA-04043: object does not exist
            RAISE;
        END IF;
END;
/

BEGIN
    EXECUTE IMMEDIATE 'DROP PROCEDURE export_job_info';
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE != -4043 THEN  -- ORA-04043: object does not exist
            RAISE;
        END IF;
END;
/

CREATE OR REPLACE PROCEDURE export_job_info(
    p_table_name IN VARCHAR2,
    p_skip_rows IN NUMBER DEFAULT 0,
    p_file_size_limit IN NUMBER DEFAULT 10000000,
    p_max_lines_per_file IN NUMBER DEFAULT 30000
)
IS
    DELIMITER_START VARCHAR2(10) := '#@45>';
    DELIMITER_END VARCHAR2(10) := '<45@#';
    DELIMITER_EOL VARCHAR2(10) := '[#@!]';
    
    TYPE rec_cursor IS REF CURSOR;
    v_cursor rec_cursor;
    
    v_order_id VARCHAR2(30);
    v_ctm_id NUMBER;
    v_rerun_counter NUMBER;
    v_folder_id NUMBER;
    v_job_name VARCHAR2(74);
    v_application_id NUMBER;
    v_sub_app_id NUMBER;
    v_library_id NUMBER;
    v_owner VARCHAR2(40);
    v_rba VARCHAR2(16);
    v_node_group_id NUMBER;
    v_node_id_id NUMBER;
    v_end_time TIMESTAMP;
    v_ended_status NUMBER;
    v_run_time_sec NUMBER;
    v_stat_cal_ctm VARCHAR2(30);
    v_stat_cal VARCHAR2(40);
    v_stat_period VARCHAR2(11);
    v_cpu_time NUMBER;
    v_agent_elapsed_time NUMBER;
    v_workloads VARCHAR2(535);
    v_order_date VARCHAR2(18);
    v_deletion_date NUMBER;
    v_collection_time NUMBER;
    v_job_id VARCHAR2(27);
    v_mem_name VARCHAR2(74);
    v_mem_lib VARCHAR2(265);
    v_critical VARCHAR2(11);
    v_cyclic VARCHAR2(11);
    v_description VARCHAR2(4010);
    v_nje_node VARCHAR2(18);
    v_cmd_line VARCHAR2(522);
    v_rule_name VARCHAR2(266);
    v_output_table VARCHAR2(90);
    v_output_size NUMBER;
    v_log_size NUMBER;
    v_log_table VARCHAR2(90);
    v_task_type NUMBER;
    v_application_type_id NUMBER;
    v_app_form_id NUMBER;
    v_cm_data CLOB;
    v_collection_status NUMBER;
    v_failure_state VARCHAR2(27);
    
    v_ctm_lookup VARCHAR2(1024);
    v_folder_lookup VARCHAR2(1024);
    v_application_lookup VARCHAR2(1024);
    v_sub_app_lookup VARCHAR2(1024);
    v_library_lookup VARCHAR2(1024);
    v_node_group_lookup VARCHAR2(1024);
    v_node_id_lookup VARCHAR2(1024);
    v_application_type_lookup VARCHAR2(1024);
    v_app_form_lookup VARCHAR2(1024);
    
    v_lookup_table VARCHAR2(90);
    v_output_text CLOB;
    v_log_text CLOB;
    v_dynamic_query VARCHAR2(4000);
    
    v_record CLOB;
    v_total_chars NUMBER := 0;
    v_line_count NUMBER := 0;
    v_current_line_length NUMBER;
    v_query VARCHAR2(4000);
    v_row_num NUMBER := 0;
    v_chunk_size NUMBER := 32000;  -- Safe chunk size under DBMS_OUTPUT.PUT limit (32767)
    v_chunk_start NUMBER;
    v_remaining_length NUMBER;
    v_chunk VARCHAR2(32767);
BEGIN
    -- Get the lookup table name from partitions table, default to 'lookup' if not found
    BEGIN
        SELECT lookup_table INTO v_lookup_table FROM partitions WHERE table_name = p_table_name AND ROWNUM = 1;
        IF v_lookup_table IS NULL OR LENGTH(v_lookup_table) = 0 THEN
            v_lookup_table := 'lookup';
        END IF;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN v_lookup_table := 'lookup';
    END;
    
    -- Build the dynamic query to fetch all fields in same order as arc_pg_jobinfo_export.sql
    -- IMPORTANT: ORDER BY is required for cursor stability during pagination!
    -- Without ORDER BY, row skipping can miss rows or return duplicates
    -- Column order matches primary key (order_id, ctm_id, rerun_counter) for index usage
    v_query := 'SELECT order_id, ctm_id, rerun_counter, folder_id, job_name, 
                       application_id, sub_app_id, library_id, owner, rba, 
                       node_group_id, node_id_id, end_time, ended_status, run_time_sec, 
                       stat_cal_ctm, stat_cal, stat_period, cpu_time, agent_elapsed_time, 
                       workloads, order_date, deletion_date, collection_time, job_id, 
                       mem_name, mem_lib, critical, cyclic, description, 
                       nje_node, cmd_line, rule_name, output_table, output_size, 
                       log_size, log_table, task_type, application_type_id, app_form_id, 
                       cm_data, collection_status, failure_state
                FROM ' || p_table_name || '
                WHERE EXISTS (SELECT 1 FROM rules r WHERE r.name = ' || p_table_name || '.rule_name)
                ORDER BY order_id, ctm_id, rerun_counter';
    
    -- Open cursor for the query
    OPEN v_cursor FOR v_query;
    
    -- Fetch rows until we reach the file size limit
    LOOP
        FETCH v_cursor INTO v_order_id, v_ctm_id, v_rerun_counter, v_folder_id, v_job_name,
                           v_application_id, v_sub_app_id, v_library_id, v_owner, v_rba,
                           v_node_group_id, v_node_id_id, v_end_time, v_ended_status, v_run_time_sec,
                           v_stat_cal_ctm, v_stat_cal, v_stat_period, v_cpu_time, v_agent_elapsed_time,
                           v_workloads, v_order_date, v_deletion_date, v_collection_time, v_job_id,
                           v_mem_name, v_mem_lib, v_critical, v_cyclic, v_description,
                           v_nje_node, v_cmd_line, v_rule_name, v_output_table, v_output_size,
                           v_log_size, v_log_table, v_task_type, v_application_type_id, v_app_form_id,
                           v_cm_data, v_collection_status, v_failure_state;
        EXIT WHEN v_cursor%NOTFOUND;
        
        -- Skip the specified number of rows
        v_row_num := v_row_num + 1;
        IF v_row_num <= p_skip_rows THEN
            CONTINUE;
        END IF;
        
        -- Get output_text from dynamic output table using cursor loop instead of XMLAGG
        IF v_output_table IS NULL THEN
            v_output_text := '';
        ELSE
            BEGIN
                v_output_text := '';
                v_dynamic_query := 'SELECT chunk_text FROM ' || v_output_table || ' ' ||
                                  'WHERE ctm_id = :1 AND order_id = :2 AND rerun_counter = :3 ' ||
                                  'ORDER BY chunk_no';
                DECLARE
                    TYPE chunk_cursor IS REF CURSOR;
                    c_chunk chunk_cursor;
                    v_chunk_text CLOB;
                    v_chunk_count NUMBER := 0;
                    v_chunk_varchar VARCHAR2(32767);
                    v_clob_len NUMBER;
                    v_clob_offset NUMBER;
                BEGIN
                    v_output_text := '';
                    OPEN c_chunk FOR v_dynamic_query USING v_ctm_id, v_order_id, v_rerun_counter;
                    LOOP
                        FETCH c_chunk INTO v_chunk_text;
                        EXIT WHEN c_chunk%NOTFOUND OR v_chunk_count >= 3;
                        IF v_chunk_text IS NOT NULL THEN
                            v_clob_len := DBMS_LOB.GETLENGTH(v_chunk_text);
                            v_clob_offset := 1;
                            WHILE v_clob_offset <= v_clob_len LOOP
                                v_chunk_varchar := DBMS_LOB.SUBSTR(v_chunk_text, 4000, v_clob_offset);
                                v_output_text := v_output_text || v_chunk_varchar;
                                v_clob_offset := v_clob_offset + 4000;
                            END LOOP;
                        END IF;
                        v_chunk_count := v_chunk_count + 1;
                    END LOOP;
                    CLOSE c_chunk;
                END;
            EXCEPTION
                WHEN NO_DATA_FOUND THEN v_output_text := '';
                WHEN OTHERS THEN 
                    RAISE_APPLICATION_ERROR(-20002, 
                        'Error getting output_text for order_id=' || v_order_id || 
                        ', ctm_id=' || v_ctm_id || ', rerun_counter=' || v_rerun_counter || 
                        ', output_table=' || v_output_table || 
                        ': ' || SQLERRM);
            END;
        END IF;
        
        -- Get log_text from dynamic log table
        -- Retrieve only the last ~0.9MB of log data (keep highest chunk_no values)
        -- Single pass: iterate DESC and prepend each chunk to get correct order
        IF v_log_table IS NULL THEN
            v_log_text := '';
        ELSE
            BEGIN
                v_dynamic_query := 'SELECT chunk_text FROM ' || v_log_table || ' 
                    WHERE ctm_id = :1 AND order_id = :2 AND rerun_counter = :3 
                    ORDER BY chunk_no DESC';
                DECLARE
                    TYPE chunk_cursor IS REF CURSOR;
                    c_chunk chunk_cursor;
                    v_chunk_text CLOB;
                    v_total_size NUMBER := 0;
                    v_max_size NUMBER := 943718; -- 0.9MB in bytes
                    v_chunk_len NUMBER;
                BEGIN
                    v_log_text := '';
                    OPEN c_chunk FOR v_dynamic_query USING v_ctm_id, v_order_id, v_rerun_counter;
                    LOOP
                        FETCH c_chunk INTO v_chunk_text;
                        EXIT WHEN c_chunk%NOTFOUND;
                        
                        IF v_chunk_text IS NOT NULL THEN
                            v_chunk_len := DBMS_LOB.GETLENGTH(v_chunk_text);
                            
                            -- Check if adding this chunk would exceed the limit
                            IF v_total_size + v_chunk_len > v_max_size AND v_total_size > 0 THEN
                                EXIT;  -- Stop - we've collected enough from the newest chunks
                            END IF;
                            
                            -- Prepend this chunk (since we're iterating DESC, prepending gives ASC order)
                            v_log_text := v_chunk_text || v_log_text;
                            v_total_size := v_total_size + v_chunk_len;
                        END IF;
                    END LOOP;
                    CLOSE c_chunk;
                END;
            EXCEPTION
                WHEN NO_DATA_FOUND THEN v_log_text := '';
                WHEN OTHERS THEN 
                    RAISE_APPLICATION_ERROR(-20003, 
                        'Error getting log_text for order_id=' || v_order_id || 
                        ', ctm_id=' || v_ctm_id || ', rerun_counter=' || v_rerun_counter || 
                        ', log_table=' || v_log_table || 
                        ': ' || SQLERRM);
            END;
        END IF;
        
        -- Get lookup values for all foreign key fields (with exception handling for missing data)
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_ctm_lookup USING v_ctm_id, 'ctm_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_ctm_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_folder_lookup USING v_folder_id, 'folder_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_folder_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_application_lookup USING v_application_id, 'application_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_application_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_sub_app_lookup USING v_sub_app_id, 'sub_app_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_sub_app_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_library_lookup USING v_library_id, 'library_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_library_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_node_group_lookup USING v_node_group_id, 'node_group_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_node_group_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_node_id_lookup USING v_node_id_id, 'node_id_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_node_id_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_application_type_lookup USING v_application_type_id, 'application_type_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_application_type_lookup := '';
        END;
        
        BEGIN
            EXECUTE IMMEDIATE 'SELECT field_value FROM ' || v_lookup_table || ' WHERE field_id = :1 AND field_name = :2 AND ROWNUM = 1' INTO v_app_form_lookup USING v_app_form_id, 'app_form_id';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN v_app_form_lookup := '';
        END;
        
        -- Build the final record with all fields in same order as arc_pg_jobinfo_export.sql
        v_record := 
            DELIMITER_START || NVL(v_order_id, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_ctm_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_rerun_counter), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_folder_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_job_name, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_application_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_sub_app_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_library_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_owner, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_rba, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_node_group_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_node_id_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_end_time, 'YYYY-MM-DD HH24:MI:SS'), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_ended_status), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_run_time_sec), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_stat_cal_ctm, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_stat_cal, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_stat_period, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_cpu_time), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_agent_elapsed_time), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_workloads, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_order_date, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_deletion_date), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_collection_time), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_job_id, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_mem_name, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_mem_lib, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_critical, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_cyclic, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(REPLACE(v_description, '''', ''''''), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_nje_node, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(REPLACE(v_cmd_line, '''', ''''''), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_rule_name, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_output_table, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_output_size), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_log_size), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_log_table, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_task_type), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_application_type_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_app_form_lookup, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_cm_data, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(TO_CHAR(v_collection_status), '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_failure_state, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_log_text, '') || DELIMITER_END || CHR(9) ||
            DELIMITER_START || NVL(v_output_text, '') || DELIMITER_END || CHR(9) || DELIMITER_EOL;
        
        -- Calculate length of current line (including newline character)
        v_current_line_length := DBMS_LOB.GETLENGTH(v_record) + 1;
        
        -- Output the record via DBMS_OUTPUT in chunks with NEW_LINE after each chunk
        v_chunk_start := 1;
        v_remaining_length := DBMS_LOB.GETLENGTH(v_record);
        
        WHILE v_chunk_start <= v_remaining_length LOOP
            v_chunk := DBMS_LOB.SUBSTR(v_record, LEAST(v_chunk_size, v_remaining_length - v_chunk_start + 1), v_chunk_start);
            DBMS_OUTPUT.PUT(v_chunk);
            DBMS_OUTPUT.NEW_LINE;
            v_chunk_start := v_chunk_start + v_chunk_size;
        END LOOP;
        
        v_total_chars := v_total_chars + v_current_line_length;
        v_line_count := v_line_count + 1;
        
        -- Check if adding the next record would exceed the batch limit (file size or line count)
        IF v_total_chars > p_file_size_limit THEN
            EXIT;
        END IF;
        IF v_line_count >= p_max_lines_per_file THEN
            EXIT;
        END IF;
    END LOOP;
    
    CLOSE v_cursor;
END export_job_info;
/
