Rem
Rem $Header: emcore/source/oracle/sysman/emdrep/sql/core/latest/ecm/ecm_delta_pkgbody.sql /st_emcore_10.2.0.4.4db11.2.0.3/1 2011/06/28 10:53:26 rajranja Exp $
Rem
Rem ecm_delta_pkgbody.sql
Rem
Rem Copyright (c) 2002, 2011, Oracle and/or its affiliates. 
Rem All rights reserved. 
Rem
Rem    NAME
Rem      ecm_delta_pkgbody.sql - <one-line expansion of the name>
Rem
Rem    DESCRIPTION
Rem      <short description of component this file declares/defines>
Rem
Rem    NOTES
Rem      <other useful comments, qualifications, etc.>
Rem
Rem    MODIFIED   (MM/DD/YY)
Rem    vkhizder    06/18/09 - XbranchMerge vkhizder_bug6503115_10g from
Rem                           st_emcore_10.2.0.1.0
Rem    rrawat      06/01/07 - Bug-6082851
Rem    rrawat      07/03/07 - Backport rrawat_6082851_rr from main
Rem    rrawat      06/01/07 - Bug-6082851
Rem    rrawat      06/27/07 - Backport rrawat_bug-6082851 from main
Rem    jsmoler     01/12/06 - fix bug 4902525 
Rem    asaraswa    12/08/05 - Backport asaraswa_bug-4200301 from main 
Rem    asaraswa    10/25/05 - fixing 4200301 
Rem    rrawat      06/20/05 - fix 4438657
Rem    agor        06/17/05 - fix 3967064 
Rem    agor        04/12/05 - fix 4148619 
Rem    agor        03/08/05 - take care of keys which can be null 
Rem    smalathe    11/26/04 - Bug#3903438: Modify query on MGMT_DELTA_IDS 
Rem    jsmoler     11/04/04 - fix bug 
Rem    smalathe    09/17/04 - Bug#3879246: Change GET_CURSOR function to 
Rem                           incorporate RAW datatype 
Rem    mgoodric    07/16/04 - Add NLS key lookup 
Rem    awarkar     12/05/03 - Bug Fix For Issue#3284443 
Rem    awarkar     11/15/03 - Bug Fix For Issue#3255739 
Rem    shuberma    11/10/03 - Making target purge policy system based 
Rem    shuberma    09/18/03 - Adding exception block 
Rem    shuberma    09/18/03 - Add purge policy callback 
Rem    vkhizder    08/06/03 - fix for history related to single row tables 
Rem    shuberma    07/11/03 - Fixing problem that prevents compilation of body
Rem    shuberma    07/08/03 - Fixing 2K database problem
Rem    shuberma    06/27/03 - Improvements to COMPARE_KEYS as per vitaliy''s comments/
Rem    shuberma    06/25/03 - Fixing the compare_keys procedure so it handles null key values
Rem    shuberma    06/11/03 - Save directly to the repository instead of accumulating diffs in memory
Rem    shuberma    04/17/03 - Adding new areg to the retrieve comparison procedure
Rem    shuberma    03/20/03 - Adding optional arguments to purge history
Rem    shuberma    01/27/03 - Add method for getting entire save comparison
Rem    shuberma    01/14/03 - Changing start_delta signature
Rem    shuberma    12/30/02 - Adding work around for plsql bug 2013606
Rem    shuberma    12/10/02 - adding new save comparison method
Rem    shuberma    11/20/02 - Making comparison use object for recording deltas
Rem    shuberma    11/19/02 - Modifying to allow use of delta query for both history can compare
Rem    shuberma    11/01/02 - Performance
Rem    shuberma    10/24/02 - Speeding up comparison
Rem    shuberma    10/17/02 - Adding PURGE_HISTORY procedure
Rem    shuberma    09/20/02 - passing two queries instead of one
Rem    shuberma    09/12/02 - Moving sql generation for compare into plsql
Rem    shuberma    09/05/02 - Adding method for doing comparisons and saving
Rem    shuberma    08/21/02 - add summary info to diff_query processing
Rem    shuberma    08/23/02 - Changes to allow for saving comparisons
Rem    shuberma    08/14/02 - fix bug when deltavalues are null for exist
Rem    shuberma    06/21/02 - Allow insert and deletes without values
Rem    shuberma    06/05/02 - Use delta values instead of name values for passing attributes..
Rem    shuberma    05/22/02 - .
Rem    shuberma    05/17/02 - Adding type specific methods..
Rem    rpinnama    05/15/02 - rpinnama_reorg_rep_scripts
Rem    rpinnama    05/15/02 - Created
Rem

CREATE OR REPLACE  PACKAGE BODY MGMT_DELTA                    
IS

-- First a couple of constants used as templates for the sql queries.

  EACH_QUERY_TEMPLATE CONSTANT VARCHAR2(100) :=
  'select <TC1> from <TC2> f where <TC3> <TC4>';

  -- <comma_separated_list_from_first_key_list_and_diff_list>
  TC1 CONSTANT VARCHAR2(5) := '<TC1>';
  -- <one_query_in_parens_or_table_name_or_view_name>
  TC2 CONSTANT VARCHAR2(5) := '<TC2>';
  -- <id_first_and_list>
  TC3 CONSTANT VARCHAR2(5) := '<TC3>';
  -- <order_by_comma_separated_match_character_col_list>
  TC4 CONSTANT VARCHAR2(5) := '<TC4>';

  TYPE T_ROW_GUID_TABLE IS TABLE OF MGMT_DELTA_ENTRY.ROW_GUID%TYPE;



-- Constants used when selected configuration-item-type-instances are compared (only applicable to host)
-- in these cases direct calls are made to DO_DIFF_QUERIES/DO_DIFF_QUERIES_AND_SAVE instead of
-- MGMT_ECM_SNAPSHOT_PKG.COMPARE_GEN_CONF_AND_SAVE

QUERY_PASSED_NOT_SIMPLE_NAME CONSTANT VARCHAR2(20) := 'QUERY_PASSED:';

QUERY_INDICATOR_LENGTH CONSTANT SMALLINT := length(QUERY_PASSED_NOT_SIMPLE_NAME);


-- Vendor Software Components (java flow is never hit so dont need to define here)
-- COMPARE_QUERY_CONSTANT_VSCI CONSTANT VARCHAR2(20) := QUERY_PASSED_NOT_SIMPLE_NAME || 'VSCI';
-- COMPARE_QUERY_VSCI CONSTANT VARCHAR2( 4000)  :=
--    'SELECT name, type, version, vendor_software_guid' || 
--    ' FROM  MGMT_HC_VENDOR_SW_COMPONENTS' ||
--    ' WHERE type = ';

-- Dependees For Oracle Component
COMPARE_QUERY_CONSTANT_DFOC CONSTANT VARCHAR2(20) := QUERY_PASSED_NOT_SIMPLE_NAME || 'DFOC';
COMPARE_QUERY_DFOC CONSTANT VARCHAR2( 4000)  :=
    ' ( ' ||
    'select unique ' ||
    '  component.name, ' ||
    '  ecm_util.CONCAT_COMPONENT_VERSION(component.name, ' ||
    '                                    component.container_guid) version, ' ||
    '  component.external_name, ' ||
    '  component_home.container_location, ' ||
    '  component_home.snapshot_guid ' ||
    'from ' ||
    '  mgmt_inv_component component, ' ||
    '  ( ' ||
    '    SELECT UNIQUE relationship.dependee_guid ' ||
    '    FROM mgmt_inv_dependency_rule relationship ' ||
    '    START WITH relationship.referencer_guid = ' ||
    '    ( SELECT component_guid ' ||
    '      FROM mgmt_inv_component r, mgmt_inv_container rh ' ||
    '      WHERE rh.snapshot_guid = :b1 ' ||
    '        AND rh.container_location = :b4 ' ||
    '        AND rh.container_guid = r.container_guid ' ||
    '        AND r.name = :b2 ' ||
    '        AND r.version = :b3) ' ||
    '    CONNECT BY relationship.referencer_guid = PRIOR relationship.dependee_guid ' ||
    '  ) rel , ' ||
      '  mgmt_inv_container component_home ' ||
    'where component.component_guid = rel.dependee_guid ' ||
    '  and component.container_guid = component_home.container_guid ' ||
    ' ) ';

-- Oracle PatchSets In Container
COMPARE_QUERY_CONSTANT_OPSIC CONSTANT VARCHAR2(20) := QUERY_PASSED_NOT_SIMPLE_NAME || 'OPSIC';
COMPARE_QUERY_OPSIC CONSTANT VARCHAR2( 4000)  :=
    ' ( ' ||
    ' SELECT home.snapshot_guid as snapshot_guid, ' ||
    ' home.container_location as location, ' ||
    ' p.external_name as name, p.version as version ' ||
    '  FROM MGMT_INV_PATCHSET  p, MGMT_INV_CONTAINER  home ' ||
    '  WHERE p.container_guid = home.container_guid ' ||
    ' ) ';

-- Oracle Interim-patches In Container
COMPARE_QUERY_CONSTANT_OIPIC CONSTANT VARCHAR2(20) := QUERY_PASSED_NOT_SIMPLE_NAME || 'OIPIC';
COMPARE_QUERY_OIPIC CONSTANT VARCHAR2( 4000)  :=
    ' ( ' ||
    ' SELECT home.snapshot_guid as snapshot_guid, ' ||
    ' home.container_location as location, p.id as patch_id' ||
    ' FROM MGMT_INV_PATCH  p, MGMT_INV_CONTAINER  home ' ||
    ' WHERE p.container_guid = home.container_guid ' ||
    ' ) ';

-----------------------------------------------------------------------
-- A convenience function that returns a string that is all the keys
-- of all the key's name-value pairs.  This can be used to compare keys.
--                    Example result "VAL1VAL2"
--
  PROCEDURE GET_KEY_STRINGS( v_key MGMT_NAME_VALUES,
                             v_key_string out VARCHAR2,
                             v_key_string_rest out VARCHAR2 )
  IS
    i NUMBER;
  BEGIN
    v_key_string := '';
    v_key_string_rest := '';
    IF v_key IS NULL OR v_key.COUNT = 0 THEN 
      v_key_string := 'DEFAULT KEY STRING FOR SINGLE ROW KEYLESS SNAPSHOT TABLES';
      RETURN; 
    END IF;
    i := v_key.FIRST;
    BEGIN
      WHILE i IS NOT NULL
      LOOP
        v_key_string := v_key_string || ' ' || LENGTH(v_key(i).VALUE) || ' ' || v_key(i).VALUE;
        i := v_key.NEXT(i);
      END LOOP;
    EXCEPTION
      WHEN VALUE_ERROR THEN
      WHILE i IS NOT NULL
      LOOP
        v_key_string_rest := v_key_string_rest || ' ' || LENGTH(v_key(i).VALUE) || ' ' || v_key(i).VALUE;
        i := v_key.NEXT(i);
      END LOOP;
    END;
  EXCEPTION
    WHEN VALUE_ERROR THEN       
    RETURN;  -- Truncated.
  END GET_KEY_STRINGs;
------------------------------------------------------------------------

-- Register an object key for a table.  It returns a unique key, independent from the
-- actual row values that identify the row.
-- If already registered, no action is taken except to return the delta guid id for the row.
--    v_collection_type   The name of the table for which to record delta operations  OR, an identifying
--                        category for the data being recorded, e.g. KERNAL_PARAMETERS.
--    v_new_objkey        A list (MGMT_NAME_VALUES) of name value pairs (MGMT_NAME_VALUE) that
--                        make of the key, or identifying information for the record being changed.  The name
--                        component of each is typically a column name.
--    RETURNS             A guid representing the delta guid id used to identify all delta entries
--                        for the associated history rows.
  PROCEDURE REGISTER_OBJECT( v_collection_type VARCHAR2,
                             v_new_objkey MGMT_NAME_VALUES,
                             v_new_entryguid RAW,
                             v_last_entryguid out RAW,
                             v_row_guid out RAW )
  IS
    l_tempkey MGMT_DELTA_IDS.KEY_STRING%TYPE;
    l_tempkeyrest MGMT_DELTA_IDS.KEY_STRING_REST%TYPE;
  BEGIN

    -- First see if the new obj is already registered.
    v_row_guid := NULL;
    v_last_entryguid := NULL;
    MGMT_DELTA.GET_KEY_STRINGS( v_new_objkey,
                                l_tempkey,
                                l_tempkeyrest );
    -- A row was found, and the old and new row ids are different.
    -- Update the registration.
    UPDATE MGMT_DELTA_IDS
      SET LAST_DELTA_ENTRY_GUID = v_new_entryguid, LAST_UPDATED_TIME = SYSDATE
      WHERE KEY_STRING = l_tempkey 
        AND COLLECTION_TYPE = v_collection_type
        AND nvl(KEY_STRING_REST, ' ') = nvl(l_tempkeyrest, ' ')
      RETURNING LAST_DELTA_ENTRY_GUID, ROW_GUID INTO v_last_entryguid, v_row_guid;

    IF SQL%NOTFOUND
    THEN
      INSERT INTO MGMT_DELTA_IDS( LAST_DELTA_ENTRY_GUID, 
                                  COLLECTION_TYPE, 
                                  KEY_STRING,
                                  KEY_STRING_REST,
                                  LAST_UPDATED_TIME )
        VALUES (v_new_entryguid, v_collection_type, l_tempkey, l_tempkeyrest,
                SYSDATE )
        RETURNING ROW_GUID into v_row_guid;

      --Add the entries only if the key is not null and are not 0
      IF (v_new_objkey IS NOT NULL AND v_new_objkey.COUNT > 0 ) THEN         
      FOR i IN v_new_objkey.FIRST .. v_new_objkey.LAST
      LOOP
        INSERT INTO MGMT_DELTA_ID_VALUES( DELTA_IDS_GUID, NAME, VALUE, TYPE )
          VALUES (v_row_guid, v_new_objkey(i).NAME, v_new_objkey(i).VALUE, v_new_objkey(i).TYPE );
      END LOOP;
     END IF;

    END IF;
  END REGISTER_OBJECT;

------------------------------------------------------------------------------
  PROCEDURE RECORD_DELTA_ENTRY_VALUES( v_delta_entryguid RAW,
                                       v_column_values MGMT_DELTA_VALUES )
  IS
  BEGIN
    IF v_column_values IS NULL OR v_column_values.COUNT <= 0 THEN RETURN; END IF;
    FOR i IN v_column_values.FIRST..v_column_values.LAST
    LOOP
      INSERT INTO MGMT_DELTA_ENTRY_VALUES( DELTA_ENTRY_GUID,
                                           NAME,
                                           VALUE,
                                           OLD_VALUE,
                                           TYPE )
        VALUES (v_delta_entryguid,
                v_column_values(i).NAME,
                v_column_values(i).LEFT_VALUE,
                v_column_values(i).RIGHT_VALUE,
                v_column_values(i).TYPE );
    END LOOP;
  END RECORD_DELTA_ENTRY_VALUES;

--------------------------------------------------------------------------
-- Record a peration on the named table.
--    v_collection_type:    The name of the table for which to record a delete operation  OR, an identifying
--                          category for the data being recorded, e.g. KERNAL_PARAMETERS.
--    v_objkey:            A list (MGMT_NAME_VALUES) of name value pairs (MGMT_NAME_VALUE) that
--                         make of the key, or identifying information for the record being deleted.  The name
--                         component of each is typically a column name.
--    v_column_values:     This is a set (MGMT_DELTA_VALUES) of MGMT_DELTA_VALUE
--                         (column_name-new_column_value-old_column_value triplets)
--                         that capture all the column values for the row being deleted.
--                         The new value is left null.  Typically, the
--                         name field is a column name and the values, the column value.  However, when the
--                         table information being captured is a NAME-VALUE pair type table, then the name field of
--                         MGMT_DELTA_VALUE may actually be the value of the source table's NAME column.
--    v_operation:         Typically INSERT_C, UPDATE_C or DELETE_C
  PROCEDURE RECORD_OPERATION( v_collection_type VARCHAR2,
                              v_objkey MGMT_NAME_VALUES,
                              v_column_values MGMT_DELTA_VALUES,
                              v_operation VARCHAR2,
                              v_time DATE := SYSDATE )
  IS
    l_row_guid RAW(16);
    l_deltaguid RAW(16);
    l_new_entryguid RAW(16);
    l_last_entryguid RAW(16);
  BEGIN
    -- First lookup the delta guid for the current transaction.
    l_deltaguid := GET_CURRENT_DELTA_GUID();
    l_new_entryguid := SYS_GUID();

    REGISTER_OBJECT( v_collection_type,
                     v_objkey,
                     l_new_entryguid,
                     l_last_entryguid,
                     l_row_guid );

    INSERT INTO MGMT_DELTA_ENTRY( DELTA_GUID,
                                  ROW_GUID,
                                  OPERATION,
                                  DELTA_ENTRY_GUID,
                                  PREV_DELTA_ENTRY_GUID,
                                  DELTA_TIME)
        VALUES (l_deltaguid,
                l_row_guid,
                v_operation,
                l_new_entryguid,
                l_last_entryguid,
                v_time );
    RECORD_DELTA_ENTRY_VALUES( l_new_entryguid,
                               v_column_values );
  END RECORD_OPERATION;

--------------------------------------------------------------------------
-- Record a delete operation on the named table.
--    v_collection_type:    The name of the table for which to record a delete operation  OR, an identifying
--                          category for the data being recorded, e.g. KERNAL_PARAMETERS.
--    v_objkey:            A list (MGMT_NAME_VALUES) of name value pairs (MGMT_NAME_VALUE) that
--                         make of the key, or identifying information for the record being deleted.  The name
--                         component of each is typically a column name.
--    v_old_column_values: This is a set (MGMT_DELTA_VALUES) of MGMT_DELTA_VALUE
--                         (column_name-new_column_value-old_column_value triplets)
--                         that capture all the column values for the row being deleted.
--                         The new value is left null.  Typically, the
--                         name field is a column name and the values, the column value.  However, when the
--                         table information being captured is a NAME-VALUE pair type table, then the name field of
--                         MGMT_DELTA_VALUE may actually be the value of the source table's NAME column.
--
  PROCEDURE RECORD_DELETE( v_collection_type VARCHAR2,
                           v_objkey MGMT_NAME_VALUES,
                           v_old_column_values MGMT_DELTA_VALUES,
                           v_time DATE := SYSDATE )
  IS
  BEGIN
    RECORD_OPERATION( v_collection_type, v_objkey, v_old_column_values, DELETE_C, v_time );
  END RECORD_DELETE;

-- Record an update operation on the named table.
--    v_collection_type:   The name of the table for which to record an update operation  OR, an identifying
--                         category for the data being recorded, e.g. KERNAL_PARAMETERS.
--    v_objkey:            A list (MGMT_NAME_VALUES) of name value pairs (MGMT_NAME_VALUE) that
--                         make of the key, or identifying information for the record being deleted.  The name
--                         component of each is typically a column name.
--    v_old_column_values: This is a set (MGMT_DELTA_VALUES) of MGMT_DELTA_VALUE
--                         (column_name-new_column_value-old_column_value triplets)
--                         that capture all the column values for the row being updated.  Typically, the
--                         name field is a column name and the values, the column value.  However, when the
--                         table information being captured is a NAME-VALUE pair type table, then the name field of
--                         MGMT_DELTA_VALUE may actually be the value of the source table's NAME column.
  PROCEDURE RECORD_UPDATE( v_collection_type VARCHAR2,
                           v_objkey MGMT_NAME_VALUES,
                           v_updated_column_values MGMT_DELTA_VALUES,
                           v_time DATE := SYSDATE )
  IS
  BEGIN
    IF v_updated_column_values IS NOT NULL AND v_updated_column_values.COUNT > 0
    THEN
      RECORD_OPERATION( v_collection_type, v_objkey, v_updated_column_values, UPDATE_C, v_time );
    END IF;
  END RECORD_UPDATE;

-- Record an insert operation on the named table.
--    v_collection_type:   The name of the table for which to record an insert operation  OR, an identifying
--                         category for the data being recorded, e.g. KERNAL_PARAMETERS.
--    v_objkey:            A list (MGMT_NAME_VALUES) of name value pairs (MGMT_NAME_VALUE) that
--                         make of the key, or identifying information for the record being inserted.  The name
--                         component of each is typically a column name.
--                         v_inserted_column_value    This is a set (MGMT_DELTA_VALUES) of
--                         MGMT_DELTA_VALUE (column_name-new_column_value-old_column_value triplets)
--                         that capture all the column values for the row being inserted.
--                         The old value field is left NULL.
--                         Typically, the name field is a column name and the values, the column value.
--                         However, when the table information being captured is a NAME-VALUE
--                         pair type table, then the name field of MGMT_DELTA_VALUE may actually
--                         be the value of the source table's NAME column.
  PROCEDURE RECORD_INSERT( v_collection_type VARCHAR2,
                           v_objkey MGMT_NAME_VALUES,
                           v_inserted_column_values MGMT_DELTA_VALUES,
                           v_time DATE := SYSDATE )
  IS
  BEGIN
    RECORD_OPERATION( v_collection_type, v_objkey, v_inserted_column_values, INSERT_C, v_time );
  END RECORD_INSERT;

------------------------------------------------------------------
-- This procedure is a convenience function that compares the old and new values and
-- only adds an entry to the MGMT_DELTA_VALUES list if the two are different.
--    v_updatetable    The list of delta values.  Could be passed in as null.  If
--                     null, it will be assigned a new MGMT_DELTA_VALUES object.
--    v_attribute_name The name of the attribute or column used to identify the value in
--                     the name-new_val-old_val triplet.
--    v_right_value      The old value of the attribute
--    v_left_value      The new value of the attribute.
--
  PROCEDURE TEST_AND_ADD_DELTA( v_updatetable in out MGMT_DELTA_VALUES,
                                v_attribute_name in VARCHAR2,
                                v_right_value in VARCHAR2,
                                v_left_value in VARCHAR2,
                                v_attribute_type in CHAR )
  IS
  BEGIN
    IF NVL(v_right_value,' ') <> NVL(v_left_value,' ')
    THEN
      IF v_updatetable IS NULL
      THEN
        v_updatetable := MGMT_DELTA_VALUES();
      END IF;
      v_updatetable.EXTEND;
      v_updatetable(v_updatetable.LAST) :=
          MGMT_DELTA_VALUE( v_attribute_name, 
                            v_left_value, 
                            v_right_value, 
                            DIFFERENT_C,
                            v_attribute_type );
    END IF;
  END TEST_AND_ADD_DELTA;

------------------------------------------------------------------------------------
-- DO_TEST
--    This function evaluates the p_pred rule that is passed in and returns the resulting
-- value.  If the predicate being passed in is null, then a default of an inequality comparison
-- is performed.  The condition is a sql conditional expression with the identifiers left and right
-- being used to represent the left and right values.
--
  FUNCTION DO_TEST( p_pred VARCHAR2,
                    left_value VARCHAR2,
                    right_value VARCHAR2 )
    RETURN boolean
  IS
    RES NUMBER;
    rule VARCHAR2(1000);
  BEGIN
    IF p_pred IS NULL
    THEN
      RETURN nvl(left_value, ' ') <> nvl(right_value, ' ');
    ELSE
      rule := 'declare left VARCHAR2(1000); right VARCHAR2(1000); BEGIN LEFT := :1; RIGHT := :2; select case when ' || EM_CHECK.NOOP(p_pred) || ' THEN 1 ELSE 0 END INTO :3 from dual; END;';
      EXECUTE IMMEDIATE rule USING left_value, right_value, out RES;
      IF RES = 1 THEN RETURN true; END IF;
    END IF;
    RETURN false;
  END;

  FUNCTION DO_TEST( p_pred VARCHAR2,
                    left_value TIMESTAMP,
                    right_value TIMESTAMP )
    RETURN boolean
  IS
    RES NUMBER;
    rule VARCHAR2(1000);
  BEGIN
    IF p_pred IS NULL
    THEN
      RETURN nvl(to_char(left_value), ' ') <> nvl(to_char(right_value), ' ');
    ELSE
      rule := 'declare left TIMESTAMP; right TIMESTAMP; BEGIN LEFT := :1; RIGHT := :2; select case when ' || EM_CHECK.NOOP(p_pred) || ' THEN 1 ELSE 0 END INTO :3 from dual; END;';
      EXECUTE IMMEDIATE rule USING left_value, right_value, out RES;
      IF RES = 1 THEN RETURN true; END IF;
    END IF;
    return false;
  END;

  FUNCTION DO_TEST( p_pred VARCHAR2,
                    left_value NUMBER,
                    right_value NUMBER )
    RETURN boolean
  IS
    RES NUMBER;
    rule VARCHAR2(1000);
  BEGIN
    IF p_pred IS NULL
    THEN
      RETURN nvl(left_value, 0) <> nvl(right_value, 0);
    ELSE
      rule := 'declare left NUMBER; right NUMBER; BEGIN LEFT := :1; RIGHT := :2; select case when ' || EM_CHECK.NOOP(p_pred) || ' THEN 1 ELSE 0 END INTO :3 from dual; END;';
      EXECUTE IMMEDIATE rule USING left_value, right_value, out RES;
      IF RES = 1 THEN RETURN true; END IF;
    END IF;
    return false;
  END;
------------------------------------------------------------------
-- This procedure is a convenience function that compares the old and new values and
-- only adds an entry to the MGMT_DELTA_VALUES list if the two are different.
--    v_updatetable    The list of delta values.  Could be passed in as null.  If
--                     null, it will be assigned a new MGMT_DELTA_VALUES object.
--    v_attribute      A name value pair, the name is the name of the attribute,
--                     and the value is a rule for testing.
--    v_left_value      The new value of the attribute
--    v_right_value      The old value of the attribute.
--    v_left_value      The new value of the attribute
--    v_right_value      The old value of the attribute.
  FUNCTION TEST_AND_ADD_DIFF( v_updatetable in out MGMT_DELTA_VALUES,
                              v_attribute in MGMT_DELTA_VALUE,
                              v_left_value in VARCHAR2,
                              v_right_value in VARCHAR2,
                              v_flags in SMALLINT  )
                               RETURN BOOLEAN
  IS
  BEGIN
    IF DO_TEST(v_attribute.LEFT_VALUE, v_left_value, v_right_value ) THEN
      IF v_updatetable IS NULL
      THEN
        v_updatetable := MGMT_DELTA_VALUES();
      END IF;
      v_updatetable.EXTEND;
      v_updatetable(v_updatetable.LAST) :=
          MGMT_DELTA_VALUE( v_attribute.name, 
                            v_left_value, 
                            v_right_value, 
                            DIFFERENT_C,
                            STRING_TYPE_C );
      RETURN TRUE;
    ELSE
      -- The old value of the attribute is an indicator that same should be returned.
      IF (v_attribute.RIGHT_VALUE IS NOT NULL) OR (v_flags = RETURN_ALL)
      THEN
        IF v_updatetable IS NULL
        THEN
          v_updatetable := MGMT_DELTA_VALUES();
        END IF;
        v_updatetable.EXTEND;
        -- Return both.  We will indicate same, but may be different because of the
        -- rules.
        v_updatetable(v_updatetable.LAST) :=
            MGMT_DELTA_VALUE( v_attribute.name, 
                              v_left_value, 
                              v_right_value, 
                              SAME_C,
                              STRING_TYPE_C );
      END IF;
    END IF;
    RETURN FALSE;
  END TEST_AND_ADD_DIFF;


  FUNCTION GET_UTC_TIMESTAMP_STRING( p_time TIMESTAMP ) 
              RETURN VARCHAR2
  IS
    l_charTime VARCHAR2(40);
  BEGIN
    l_charTime := NULL;
    IF p_time IS NOT NULL THEN 
-- work around for a plsql bug in 9.0.1, bug # 2013606    
--      l_charTime := to_char(SYS_EXTRACT_UTC(p_time), 'YYYY-MM-DD HH24:MI:SS.FF' );
      execute immediate
        'select to_char(SYS_EXTRACT_UTC(:p_time), ''YYYY-MM-DD HH24:MI:SS.FF'' ) from dual'
      into l_charTime using p_time;
    END IF;
    RETURN l_charTime;
  END GET_UTC_TIMESTAMP_STRING;


  FUNCTION TEST_AND_ADD_DIFF( v_updatetable in out MGMT_DELTA_VALUES,
                              v_attribute in MGMT_DELTA_VALUE,
                              v_left_value in TIMESTAMP,
                              v_right_value in TIMESTAMP,
                              v_flags in SMALLINT )
                               RETURN BOOLEAN
  IS
  BEGIN
    IF DO_TEST(v_attribute.LEFT_VALUE, v_left_value, v_right_value ) THEN
      IF v_updatetable IS NULL
      THEN
        v_updatetable := MGMT_DELTA_VALUES();
      END IF;
      v_updatetable.EXTEND;
      v_updatetable(v_updatetable.LAST) :=
          MGMT_DELTA_VALUE( v_attribute.name,  
                            GET_UTC_TIMESTAMP_STRING( v_left_value ), 
                            GET_UTC_TIMESTAMP_STRING( v_right_value ), 
                            DIFFERENT_C,
                            DATETIME_TYPE_C );
      RETURN TRUE;
    ELSE
      -- The old value of the attribute is an indicator that same should be returned.
      IF (v_attribute.RIGHT_VALUE IS NOT NULL) OR (v_flags = RETURN_ALL)
      THEN
        IF v_updatetable IS NULL
        THEN
          v_updatetable := MGMT_DELTA_VALUES();
        END IF;
        v_updatetable.EXTEND;
        -- Return both.  We will indicate same, but may be different because of the
        -- rules.
        v_updatetable(v_updatetable.LAST) :=
            MGMT_DELTA_VALUE( v_attribute.name, 
                              GET_UTC_TIMESTAMP_STRING(v_left_value ), 
                              GET_UTC_TIMESTAMP_STRING( v_right_value ), 
                              SAME_C,
                              DATETIME_TYPE_C );
      END IF;
    END IF;
    RETURN FALSE;
  END TEST_AND_ADD_DIFF;

  FUNCTION TEST_AND_ADD_DIFF( v_updatetable in out MGMT_DELTA_VALUES,
                              v_attribute in MGMT_DELTA_VALUE,
                              v_left_value in NUMBER,
                              v_right_value in NUMBER,
                              v_flags in SMALLINT )
                               RETURN BOOLEAN
  IS
  BEGIN
    IF DO_TEST(v_attribute.LEFT_VALUE, v_left_value, v_right_value ) THEN
      IF v_updatetable IS NULL
      THEN
        v_updatetable := MGMT_DELTA_VALUES();
      END IF;
      v_updatetable.EXTEND;
      v_updatetable(v_updatetable.LAST) :=
          MGMT_DELTA_VALUE( v_attribute.name, 
                            v_left_value, 
                            v_right_value, 
                            DIFFERENT_C,
                            NUMBER_TYPE_C );
      RETURN TRUE;
    ELSE
      -- The old value of the attribute is an indicator that same should be returned.
      IF (v_attribute.RIGHT_VALUE IS NOT NULL) OR (v_flags = RETURN_ALL)
      THEN
        IF v_updatetable IS NULL
        THEN
          v_updatetable := MGMT_DELTA_VALUES();
        END IF;
        v_updatetable.EXTEND;
        -- Return both.  We will indicate same, but may be different because of the
        -- rules.
        v_updatetable(v_updatetable.LAST) :=
            MGMT_DELTA_VALUE( v_attribute.name, 
                              v_left_value, 
                              v_right_value, 
                              SAME_C,
                              NUMBER_TYPE_C );
      END IF;
    END IF;
    RETURN FALSE;
  END TEST_AND_ADD_DIFF;


--------------------------------------------------------------------------------
  PROCEDURE ADD_NON_NULL_DELTA_VALUE( v_deltavalues in out MGMT_DELTA_VALUES,
                                      v_attribute_name in VARCHAR2,
                                      v_right_value in VARCHAR2,
                                      v_left_value in VARCHAR2,
                                      v_attribute_type in CHAR,
                                      p_side in VARCHAR2)
  IS
  BEGIN
      IF v_deltavalues IS NULL
      THEN
        v_deltavalues := MGMT_DELTA_VALUES();
      END IF;

      v_deltavalues.EXTEND;
      
      v_deltavalues(v_deltavalues.LAST) :=
          MGMT_DELTA_VALUE( v_attribute_name, 
                            v_left_value, 
                            v_right_value, 
                            p_side,
                            v_attribute_type );
                            
  END ADD_NON_NULL_DELTA_VALUE;


-- Register the start of a delta time slice.  Used to associated a set of IUDs as part of
-- the same delta.  The caller can supplied thier own delta guid or have a new delta guid
-- generated.  This allows the code that calls the RECORD_XXX methods, such as triggers,
-- to look up the delta guid for the current transaction.  Note that the transaction id, should be kept
-- up to date by calling the update_delta_XACT after any intermediate commits.  The GUID returned will be
-- a guid identifying the delta and is the same as v_delta_guid.
--    v_delta_guid:    A identifier of the current delta.  If not supplied, the default will generate
--                        one.
--  RETURNS:    The delta guid.
--
  FUNCTION START_DELTA( v_delta_guid RAW := SYS_GUID()) RETURN RAW
  IS
  BEGIN
    INSERT INTO MGMT_DELTA_SNAP( DELTA_GUID, TRANSACTION_ID )
      VALUES (v_delta_guid, DBMS_TRANSACTION.LOCAL_TRANSACTION_ID());
    RETURN v_delta_guid;
  END START_DELTA;

-- Register the start of a delta time slice.  Used to associated a set of IUDs as part of
-- the same delta.  The caller can supplied thier own delta guid or have a new delta guid
-- generated.  This allows the code that calls the RECORD_XXX methods, such as triggers,
-- to look up the delta guid for the current transaction.  Note that the transaction id, should be kept
-- up to date by calling the update_delta_XACT after any intermediate commits.  The GUID returned will be
-- a guid identifying the delta and is the same as v_delta_guid.
--    l_snapguid:     Snapshot_guid of the NEW or LEFT Snapshot.
--    l_timestamp:    The timestamp of the snapshot.
--    l_target_name:    Name of the left snapshot's target
--    r_snapguid:     Snapshot_guid of the OLD or RIGHT snapshot.
--    r_timestamp:    The timestamp of the snapshot.
--    r_target_name:    Name of the right snapshot's target
--    target_type:    Name of the targets' type
--    snapshot_type:    Name of the  snapshots' type
--    delta_type:     The type of delta.  (COMPARISON, HISTORY)
--    v_delta_guid:    A identifier of the current delta.  If not supplied, the default will generate
--                        one.
--  RETURNS:    The delta guid.
--
  FUNCTION START_DELTA( l_snapguid RAW,
                        l_timestamp DATE,
                        l_target_name VARCHAR2,
                        r_snapguid RAW,
                        r_timestamp DATE,
                        r_target_name VARCHAR2,
                        target_type VARCHAR2,
                        snapshot_type VARCHAR2,
                        delta_type VARCHAR2,
                        v_delta_guid RAW := SYS_GUID()) RETURN RAW
  IS
  BEGIN
    INSERT INTO MGMT_DELTA_SNAP( DELTA_GUID, TRANSACTION_ID, DELTA_TYPE,
                                 TARGET_TYPE, SNAPSHOT_TYPE,
                                 NEW_LEFT_SNAPSHOT_GUID, NEW_LEFT_TIMESTAMP,
                                 NEW_LEFT_TARGET_NAME,
                                 OLD_RIGHT_SNAPSHOT_GUID, OLD_RIGHT_TIMESTAMP,
                                 OLD_RIGHT_TARGET_NAME )
      VALUES (v_delta_guid, DBMS_TRANSACTION.LOCAL_TRANSACTION_ID(),
              delta_type, target_type, snapshot_type, l_snapguid, 
              l_timestamp, l_target_name,
              r_snapguid, r_timestamp, r_target_name );
    RETURN v_delta_guid;
  END START_DELTA;

------------------------------------------------------------------------
-- Update the delta identified by v_delta_guid.  This will updated the deltas transaction id
-- to be the current tranaction id.  Used when intermediate commits are performed.
--    v_delta_guid:    The identifier of the delta for which to update the transaction id.
--
  PROCEDURE UPDATE_DELTA_XACT( v_delta_guid RAW )
  IS
  BEGIN
    UPDATE MGMT_DELTA_SNAP SET TRANSACTION_ID = DBMS_TRANSACTION.LOCAL_TRANSACTION_ID
      WHERE DELTA_GUID = v_delta_guid;
  END UPDATE_DELTA_XACT;

-- Get the delta_guid for the current transaction
-- If none are recorded, i.e. not in a delta, then the delta guid will be "NO_DELTA";
--
  FUNCTION GET_CURRENT_DELTA_GUID RETURN RAW
  IS
    l_deltaguid RAW(16);
  BEGIN
    BEGIN
      SELECT DELTA_GUID INTO l_deltaguid FROM MGMT_DELTA_SNAP
        WHERE TRANSACTION_ID = DBMS_TRANSACTION.LOCAL_TRANSACTION_ID();
    EXCEPTION
      WHEN NO_DATA_FOUND THEN l_deltaguid := NULL;
    END;
    IF (l_deltaguid IS NULL)
    THEN
      l_deltaguid := NO_DELTA;
    END IF;
    RETURN l_deltaguid;
  END GET_CURRENT_DELTA_GUID;

-----------------------------------------------------------------------
-- A convenience function that returns a string that is a concatenated list
-- of all the key's name-value pairs.  This can be used to compare keys.
--                    Example result "NAME1=VAL1 NAME2=VAL2"
--
  FUNCTION GET_DELTA_KEY_DISPLAY_STRING( v_key_row_guid RAW ) RETURN VARCHAR2
  IS
    tmp VARCHAR2(4000);
    tmp1 VARCHAR2( 4000);
    l_name VARCHAR2(64);
    l_value VARCHAR2(4000);
    CURSOR c1 IS SELECT NAME, "VALUE"
                 FROM MGMT_DELTA_ID_VALUES
                 WHERE DELTA_IDS_GUID = v_key_row_guid;
  BEGIN
    tmp := NULL;
    OPEN C1;
    LOOP
      FETCH c1 INTO l_name, l_value;
      EXIT WHEN c1%NOTFOUND;
      tmp1 := l_name || '=' ||l_value;
      IF tmp IS NULL THEN
        tmp := tmp1;
      ELSE
        tmp := tmp || ' ' || tmp1;
      END IF;
    END LOOP;
    RETURN tmp;
  EXCEPTION
    WHEN VALUE_ERROR THEN RETURN tmp;  -- Truncated.
  END GET_DELTA_KEY_DISPLAY_STRING;
----------------------------------------------------------------------------------
-- A convenience function that returns a string that is a concatenated list of
-- the delte values' name-old_val-new_val triplets.
--    v_vals    The delta_entry_guid for the delta operation..
--    v_operation    INSERT_C, UPDATE_C, or DELETE_C.  Based on this, the
--                    old-val and new-vals are included or not.
--                    Example result  NAME(OLD_VAL=>NEW_VAL) for update
--                                    NAME(VAL) for INSERT or DELETE
-- Result is truncated is max size of VARCHAR2 is exceeded.  (VALUE_ERROR)
  FUNCTION GET_DELTA_VALUE_DISPLAY_STRING( v_delta_entryguid RAW,
                                           v_operation VARCHAR2 ) RETURN VARCHAR2
  IS
    result VARCHAR2(4000);
    comma VARCHAR2(3);
    l_name VARCHAR2(64);
    l_leftvalue VARCHAR2(4000);
    l_rightvalue VARCHAR2(4000);
    CURSOR c1 IS SELECT NAME, "VALUE", OLD_VALUE
                 FROM MGMT_DELTA_ENTRY_VALUES
                 WHERE DELTA_ENTRY_GUID = v_delta_entryguid;
  BEGIN
    result := '';
    comma := '';
    OPEN C1;
    LOOP
      FETCH c1 INTO l_name, l_leftvalue, l_rightvalue;
      EXIT WHEN c1%NOTFOUND;
      IF v_operation = UPDATE_C THEN
        result := result || comma || l_name || '(' || l_rightvalue || '=>' || l_leftvalue || ')';
      ELSE IF v_operation = INSERT_C THEN
        result := result || comma || l_name || '(' || l_leftvalue || ')';
      ELSE
        result := result || comma || l_name || '(' || l_rightvalue || ')';
      END IF; END IF;
      comma := ' ';
    END LOOP;
    RETURN result;
  EXCEPTION
    WHEN VALUE_ERROR THEN RETURN result; -- Truncated.
  END GET_DELTA_VALUE_DISPLAY_STRING;
----------------------------------------------------------------------------------
-- A convenience function that returns a string that is a concatenated list of
-- the delte values' name-old_val-new_val triplets.
--    v_vals    The name-old_val-new_val triplets.
--    v_operation    INSERT_C, UPDATE_C, or DELETE_C.  Based on this, the
--                    old-val and new-vals are included or not.
--                    Example result  NAME(OLD_VAL=>NEW_VAL) for update
--                                    NAME(VAL) for INSERT or DELETE.
--
  FUNCTION GET_DELTA_VALUE_DISPLAY_STRING( v_vals MGMT_DELTA_VALUES,
                                           v_operation VARCHAR2 ) RETURN VARCHAR2
  IS
    tmp VARCHAR2(4000);
    tmp1 VARCHAR2( 1000);
    i NUMBER;
  BEGIN
    tmp := NULL;
    IF v_vals IS NULL THEN RETURN NULL; END IF;
    i := v_vals.FIRST;
    WHILE i IS NOT NULL
    LOOP
      IF v_operation = UPDATE_C THEN
        tmp1 := v_vals(i).NAME || '(' || v_vals(i).RIGHT_VALUE || '=>' || v_vals(i).LEFT_VALUE || ')';
      ELSE IF v_operation = INSERT_C THEN
        tmp1 := v_vals(i).NAME || '(' || v_vals(i).LEFT_VALUE || ')';
      ELSE
        tmp1 := v_vals(i).NAME || '(' || v_vals(i).RIGHT_VALUE || ')';
      END IF; END IF;
      IF i = v_vals.FIRST THEN
        tmp := tmp1;
      ELSE
        tmp := tmp || ' ' || tmp1;
      END IF;
      i := v_vals.NEXT(i);
    END LOOP;
    RETURN tmp;
  END GET_DELTA_VALUE_DISPLAY_STRING;


---------------------------------------------------------------------------------
-- Function for getting key col list values.
  FUNCTION GET_KEYCOL_LIST_VALUES( p_keyList VARCHAR2_TABLE,
                                   p_curs INTEGER,
                                   p_rec_tab dbms_sql.desc_tab )
              RETURN MGMT_NAME_VALUES
  IS
    keyCols MGMT_NAME_VALUES;
    charVal VARCHAR2(4000);  -- Used to hold left side character column data.
    numVal NUMBER;           -- Used to hold left side number column data.
    timeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
  BEGIN
    -- Look through and create the keylist structure with the key columns.
    -- They are always the first in the list in the same order as select list.
    keyCols := MGMT_NAME_VALUES();
    IF p_keyList.COUNT > 0 THEN
      FOR i IN p_keyList.FIRST..p_keyList.LAST
      LOOP
        keyCols.EXTEND;
        CASE
          WHEN p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
            DBMS_SQL.COLUMN_VALUE(p_curs, i, numVal );
            keyCols(keyCols.LAST) := MGMT_NAME_VALUE( p_keyList(i), 
                                                      numVal, 
                                                      NUMBER_TYPE_C );
          WHEN p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_DATE OR
               p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_TZ OR
               p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_LTZ OR
               p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP THEN
            DBMS_SQL.COLUMN_VALUE(p_curs, i, timeVal );
            keyCols(keyCols.LAST) := MGMT_NAME_VALUE( p_keyList(i), 
                                                      GET_UTC_TIMESTAMP_STRING( timeVal ), 
                                                      DATETIME_TYPE_C );
          ELSE
            DBMS_SQL.COLUMN_VALUE(p_curs, i, charVal );
            keyCols(keyCols.LAST) := MGMT_NAME_VALUE( p_keyList(i), 
                                                      charVal,
                                                      STRING_TYPE_C );
        END CASE;
      END LOOP;
    END IF;
    RETURN keyCols;
  END GET_KEYCOL_LIST_VALUES;

  -----------------------------------------------------------------------------
  -- Procedure for getting left/right only entries.
  FUNCTION  GET_ONE_SIDE_DELTA( p_collectionType VARCHAR2,
                                p_keyCols MGMT_NAME_VALUES,
                                p_attrList MGMT_DELTA_VALUES,
                                p_curs INTEGER,
                                p_rec_tab dbms_sql.desc_tab,
                                p_side VARCHAR2,
                                p_deltaSummary in out MGMT_DELTA_SUMMARY,
                                p_flags SMALLINT  )
                RETURN MGMT_DELTA_ENTRY_OBJ
  IS
    deltaVals MGMT_DELTA_VALUES;
    firstk NUMBER;
    fCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    fNumVal NUMBER;           -- Used to hold left side number column data.
    fTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    sCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    sNumVal NUMBER;           -- Used to hold left side number column data.
    sTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    rightOnly BOOLEAN;
  BEGIN
    rightOnly := p_side = RIGHT_ONLY_C;
    -- Get Deltas, one side only.
    -- for each row, loop through the rec_tabs and process each column pair,
    -- one pair for each entry int he attrList.
    deltaVals := MGMT_DELTA_VALUES();
    FOR k IN 1..p_attrList.COUNT
    LOOP
      firstk := k + p_keyCols.COUNT;
      CASE
        WHEN p_rec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
          DBMS_SQL.COLUMN_VALUE(p_curs, firstk, fNumVal );
          IF rightOnly THEN
            sNumVal := fNumVal;
            fNumVal := null;
          ELSE
            sNumVal := null;
          END IF;
          MGMT_DELTA.ADD_NON_NULL_DELTA_VALUE( deltaVals,
                                               p_attrList(k).NAME,
                                               sNumVal,
                                               fNumVal,
                                               NUMBER_TYPE_C,
                                               p_side);
                                               
        WHEN p_rec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_DATE OR
            p_rec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP OR
            p_rec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_TZ OR
            p_rec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_LTZ THEN
          DBMS_SQL.COLUMN_VALUE(p_curs, firstk, fTimeVal );
          IF rightOnly THEN
            sTimeVal := fTimeVal;
            fTimeVal := null;
          ELSE
            sTimeVal := null;
          END IF;
          
          MGMT_DELTA.ADD_NON_NULL_DELTA_VALUE( deltaVals,
                                               p_attrList(k).NAME,
                                               GET_UTC_TIMESTAMP_STRING(sTimeVal),
                                               GET_UTC_TIMESTAMP_STRING(fTimeVal),
                                               DATETIME_TYPE_C,
                                               p_side);
        ELSE
          DBMS_SQL.COLUMN_VALUE(p_curs, firstk, fCharVal );
          IF rightOnly THEN
            sCharVal := fCharVal;
            fCharVal := null;
          ELSE
            sCharVal := null;
          END IF;
          MGMT_DELTA.ADD_NON_NULL_DELTA_VALUE( deltaVals,
                                               p_attrList(k).NAME,
                                               sCharVal,
                                               fCharVal,
                                               STRING_TYPE_C,
                                               p_side);
      END CASE;
    END LOOP;

    IF rightOnly THEN
      p_deltaSummary.RIGHT_OLD_COUNT := p_deltaSummary.RIGHT_OLD_COUNT + 1;
    ELSE
      p_deltaSummary.LEFT_NEW_COUNT := p_deltaSummary.LEFT_NEW_COUNT + 1;
    END IF;
    -- Done with the columns for this row, not add an entry to the
    -- result if there were any deltas and we are returning them.
    IF p_flags <> RETURN_NONE THEN
      RETURN MGMT_DELTA_ENTRY_OBJ( p_side,
                                   p_collectionType,
                                   p_keyCols,
                                   deltaVals );
    END IF;
    RETURN NULL;
  END GET_ONE_SIDE_DELTA;

  -----------------------------------------------------------------------------
  -- Procedure for getting diffs or sames.
  FUNCTION  GET_BOTH_SIDE_DELTA( p_collectionType VARCHAR2,
                                 p_keyCols MGMT_NAME_VALUES,
                                 p_attrList MGMT_DELTA_VALUES,
                                 p_fCurs INTEGER,
                                 p_sCurs INTEGER,
                                 p_frec_tab dbms_sql.desc_tab,
                                 p_srec_tab dbms_sql.desc_tab,
                                 p_deltaSummary in out MGMT_DELTA_SUMMARY,
                                 p_flags SMALLINT )
                  RETURN MGMT_DELTA_ENTRY_OBJ
  IS
    deltaVals MGMT_DELTA_VALUES;
    firstk NUMBER;
    fCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    fNumVal NUMBER;           -- Used to hold left side number column data.
    fTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    sCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    sNumVal NUMBER;           -- Used to hold left side number column data.
    sTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    diffFound BOOLEAN;
    testResult BOOLEAN;
    operation VARCHAR2(20);
  BEGIN
    -- for each row, loop through the rec_tabs and process each column pair,
    -- one pair for each entry in the attrList.
    deltaVals := MGMT_DELTA_VALUES();
    diffFound := false;
    FOR k IN 1..p_attrList.COUNT
    LOOP
      firstk := k + p_keyCols.COUNT;
      CASE
        WHEN p_frec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
          DBMS_SQL.COLUMN_VALUE(p_fCurs, firstk, fNumVal );
          DBMS_SQL.COLUMN_VALUE(p_sCurs, firstk, sNumVal );
          testResult := MGMT_DELTA.TEST_AND_ADD_DIFF( deltaVals,
                                                      p_attrList(k),
                                                      fNumVal,
                                                      sNumVal,
                                                      p_flags );
        WHEN p_frec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_DATE OR
             p_frec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP OR
             p_frec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_TZ OR
             p_frec_tab(firstk).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_LTZ THEN
          DBMS_SQL.COLUMN_VALUE(p_fCurs, firstk, fTimeVal );
          DBMS_SQL.COLUMN_VALUE(p_sCurs, firstk, sTimeVal );
          testResult := MGMT_DELTA.TEST_AND_ADD_DIFF( deltaVals,
                                                      p_attrList(k),
                                                      fTimeVal,
                                                      sTimeVal,
                                                      p_flags );
        ELSE
          DBMS_SQL.COLUMN_VALUE(p_fCurs, firstk, fCharVal );
          DBMS_SQL.COLUMN_VALUE(p_sCurs, firstk, sCharVal );
          testResult := MGMT_DELTA.TEST_AND_ADD_DIFF( deltaVals,
                                                      p_attrList(k),
                                                      fCharVal,
                                                      sCharVal,
                                                      p_flags );
      END CASE;
      diffFound := diffFound OR testResult;
    END LOOP;
    -- Done with the columns for this row, not add an entry to the
    -- result if there were any deltas.
    IF deltaVals IS NOT NULL
    THEN
      IF diffFound THEN
        operation := DIFFERENT_C;
        p_deltaSummary.DIFFERENT_COUNT := p_deltaSummary.DIFFERENT_COUNT + 1;
      ELSE
        operation := SAME_C;
      END IF;
      -- If we are returning something...then place in entry list.
      IF (p_flags <> RETURN_NONE AND diffFound) OR
         (p_flags = RETURN_ALL)
      THEN
        RETURN MGMT_DELTA_ENTRY_OBJ( operation,
                                     p_collectionType,
                                     p_keyCols,
                                     deltaVals );
      END IF;
    END IF;
    RETURN NULL;
  END GET_BOTH_SIDE_DELTA;


---------------------------------
  FUNCTION GET_KEY_COLS( p_curs INTEGER,
                         p_keyList VARCHAR2_TABLE,
                         p_rec_tab dbms_sql.desc_tab)
              RETURN MGMT_NAME_VALUES
  IS
    keyCols MGMT_NAME_VALUES;
  BEGIN
    keyCols := NULL;
    IF DBMS_SQL.FETCH_ROWS(p_curs) > 0 THEN
      keyCols := GET_KEYCOL_LIST_VALUES( p_keyList, p_curs, p_rec_tab );
    END IF;
    RETURN keyCols;
  END GET_KEY_COLS;

-------------------------------------------
  FUNCTION COMPARE_KEYS( fKeyCols MGMT_NAME_VALUES, 
                         sKeyCols MGMT_NAME_VALUES ) 
                    RETURN NUMBER 
  IS 
    result NUMBER; 
  BEGIN 
    IF fKeyCols IS NULL OR fKeyCols.COUNT <= 0 THEN RETURN 0; END IF; 
    FOR i IN fKeyCols.FIRST..fKeyCols.LAST 
    LOOP
      -- If both are null, then continue with next keys in loop.  We will consider nulls equal.
      -- If one is null and the other is not, then the null is considered greater than the other. 
      IF fKeyCols(i).VALUE IS NOT NULL 
      THEN 
        -- First of the two is not null. 
        IF sKeyCols(i).VALUE IS NULL OR fKeyCols(i).VALUE < sKeyCols(i).VALUE THEN
          -- either second key is null or first key is less than the second key 
          RETURN -1; 
        ELSE IF fKeyCols(i).VALUE > sKeyCols(i).VALUE THEN 
          -- The second isn't null and is less than the first key 
          RETURN 1; 
        END IF; END IF;
      ELSE IF sKeyCols(i).VALUE IS NOT NULL 
      THEN 
        -- second key is not null and the first key is null 
        RETURN 1; 
      END IF; END IF; 
    END LOOP;    -- No difference found. 
    RETURN 0; 
  END COMPARE_KEYS; 

------------------------------------------------------------------------------------
-- This function opens a cursor using the dbms_sql package.  It uses this package so
-- it can get the desc_tab that describes the columns and their datatypes.
-- If we decide to keep the datatype information in the repository, then we can
-- modify this so it doesn't need to use the dbms_sql.
--  p_queryText     Sql query to for which to open a cursor on.
--  p_idList        A list of MGMT_DELTA_VALUE objects that describe the id columns.
--  p_bindRight     True if we want to use the rightside id values for bind.  Otherwise
--                  use the leftside id values.
--  p_rec_tab       A structure describing the results.
--    Returns a cursor id.
  FUNCTION GET_CURSOR( p_queryText VARCHAR2,
                      p_idList MGMT_DELTA_VALUES,
                      p_bindRight BOOLEAN,
                      p_rec_tab out dbms_sql.desc_tab )
              RETURN INTEGER
  IS
    curs INTEGER;     -- Local cursor id
    numrows INTEGER;  -- Used to hold the results of the EXECUTE call.
    col_cnt NUMBER;   -- Holds the number of columns return from describe_columns.
    numVal NUMBER;    -- Used in the define_column call for number columns.
    charVal VARCHAR2(4000); -- Used in the define_column call for char columns.
    timeVal TIMESTAMP;-- Used in the define_column call for date/time columns.
    rawVal RAW(4000); -- Used in the define_column call for RAW columns.
  BEGIN
    -- Open a dynamic cursor;
    curs := DBMS_SQL.OPEN_CURSOR;
    -- Parse the sql that was passed in.
    DBMS_SQL.PARSE( curs, p_queryText, DBMS_SQL.NATIVE );
    -- Bind the id columns.
    FOR i IN p_idList.FIRST..p_idList.LAST
    LOOP
      IF p_bindRight
      THEN
        charVal := p_idList(i).right_value;
      ELSE
        charVal := p_idList(i).left_value;
      END IF;

      IF p_idList(i).type = NUMBER_TYPE_C THEN
        numVal := charVal;
        DBMS_SQL.BIND_VARIABLE(curs, ':b'||i, numVal);
      ELSE IF p_idList(i).type = DATETIME_TYPE_C THEN
        timeVal := charVal;
        DBMS_SQL.BIND_VARIABLE(curs, ':b'||i, timeVal);
      ELSE IF p_idList(i).type = RAW_TYPE_C THEN
        rawVal := charVal;
        DBMS_SQL.BIND_VARIABLE_RAW(curs, ':b'||i, rawVal);
      ELSE
        DBMS_SQL.BIND_VARIABLE(curs, ':b'||i, charVal);
      END IF;
      END IF;
      END IF;

END LOOP;

    -- Execute the sql statement
    numrows := DBMS_SQL.EXECUTE(curs);

    -- We want to examine the metadata to get datatypes.
    DBMS_SQL.DESCRIBE_COLUMNS( curs, col_cnt, p_rec_tab );

    -- Loop throught the record that describes the columns and call the
    -- define column procedure.
    FOR i in p_rec_tab.first..p_rec_tab.last
    LOOP
      CASE
        WHEN p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
          DBMS_SQL.DEFINE_COLUMN( curs, i, numVal );
        WHEN p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_DATE OR
             p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP OR
             p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_TZ OR
             p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_LTZ THEN
          DBMS_SQL.DEFINE_COLUMN( curs, i, timeVal );
        ELSE
          DBMS_SQL.DEFINE_COLUMN( curs, i, charVal, p_rec_tab(i).col_max_len );
      END CASE;
    END LOOP;
    RETURN curs;
  END GET_CURSOR;

  -- Given a table/view name OR query-constant 
  -- This returns the table/view name ENQUOTED OR the query associated with constant
  -- stored in pkg-def
  FUNCTION GET_TC2_STRING( p_table_view_inner_query VARCHAR2)
      RETURN VARCHAR2
  IS
  BEGIN
      IF substr(p_table_view_inner_query, 1, QUERY_INDICATOR_LENGTH ) != QUERY_PASSED_NOT_SIMPLE_NAME THEN
        return DBMS_ASSERT.ENQUOTE_NAME(p_table_view_inner_query);
      -- do not need to handle VSCI constant: that code is never hit in java: we never compare 2 selected vendor software components
      --ELSIF substr( p_table_view_inner_query, 1, length(COMPARE_QUERY_CONSTANT_VSCI)) = COMPARE_QUERY_CONSTANT_VSCI THEN
      --  return ' ( ' || COMPARE_QUERY_VSCI || DBMS_ASSERT.ENQUOTE_LITERAL (substr( p_table_view_inner_query, length(COMPARE_QUERY_CONSTANT_VSCI) + 1 )) || ' ) ';
      ELSIF p_table_view_inner_query = COMPARE_QUERY_CONSTANT_DFOC THEN
        return COMPARE_QUERY_DFOC;
      ELSIF p_table_view_inner_query = COMPARE_QUERY_CONSTANT_OPSIC THEN
        return COMPARE_QUERY_OPSIC;
      ELSIF p_table_view_inner_query = COMPARE_QUERY_CONSTANT_OIPIC THEN
        return COMPARE_QUERY_OIPIC;
      END IF;
      -- 2 returns with same so that history flow is terminates at first condition
      -- this return is  unlikely but if happens will give better errors (than returning null)
      return DBMS_ASSERT.ENQUOTE_NAME(p_table_view_inner_query);

  END GET_TC2_STRING;


---------------------------------------------------------------------------
-- This function creates a query that can be used to get a cursor that contains
-- rows with duplicate keys. The result set will be sorted on all the columns
-- to ensure a consistent sort order
-- p_queryTest  The original text of the query
-- p_attrList   The lsit of attributes
-- p_keyCols    The list of keys
  FUNCTION GET_SUBLIST_QUERY(p_queryText VARCHAR2, 
                             p_attrList MGMT_DELTA_VALUES, 
                             p_keyCols MGMT_NAME_VALUES )
    RETURN VARCHAR2
  IS
    newQuery VARCHAR2(1000) := 'select * from ('  || p_queryText || ')';
    orderByString VARCHAR2(10) := ' ORDER BY ';
    andString VARCHAR2(10) := '';
    commaString VARCHAR2(2) := ' ';
  BEGIN
    IF p_keyCols IS NOT NULL AND p_keyCols.COUNT > 0
    THEN
      newQuery := newQuery || ' WHERE ';
      FOR i IN p_keyCols.FIRST..p_keyCols.LAST
      LOOP
        newQuery := newQuery || andString ||  DBMS_ASSERT.ENQUOTE_NAME(p_keyCols(i).name)  ||
                    ' = ' || ':x' || i;
        andString := ' and ';
      END LOOP;
      newQuery := newQuery || orderByString;
      FOR i IN p_keyCols.FIRST..p_keyCols.LAST
      LOOP
        newQuery := newQuery || commaString ||  DBMS_ASSERT.ENQUOTE_NAME(p_keyCols(i).name) ;
        commaString := ', ';
      END LOOP;
      orderByString := '';
    END IF;
    
    IF p_attrList IS NOT NULL AND P_attrList.COUNT > 0
    THEN
      newQuery := newQuery || orderByString;
      FOR i IN p_attrList.FIRST..p_attrList.LAST
      LOOP
        newQuery := newQuery || commaString || DBMS_ASSERT.ENQUOTE_NAME(p_attrList(i).NAME) ;
        commaString := ', ';
      END LOOP;
    END IF;
    
    RETURN newQuery;
  END GET_SUBLIST_QUERY;
------------------------------------------------------------------------------------
-- This function opens a cursor using the dbms_sql package.  It uses this package so
-- it can get the desc_tab that describes the columns and their datatypes.
-- If we decide to keep the datatype information in the repository, then we can
-- modify this so it doesn't need to use the dbms_sql.
--  p_queryText     Sql query to for which to open a cursor on.
--  p_idList        A list of MGMT_DELTA_VALUE objects that describe the id columns.
--  p_extraBindList A list of values for additional bind variables
--  p_bindRight     True if we want to use the rightside id values for bind.  Otherwise
--                  use the leftside id values.
--  p_rec_tab       A structure describing the results.
--    Returns a cursor id.
  FUNCTION GET_CURSOR_EXTRA_BINDS( p_queryText VARCHAR2,
                      p_idList MGMT_DELTA_VALUES,
                      p_extraBindList MGMT_NAME_VALUES,
                      p_bindRight BOOLEAN,
                      p_rec_tab out dbms_sql.desc_tab )
              RETURN INTEGER
  IS
    curs INTEGER;     -- Local cursor id
    numrows INTEGER;  -- Used to hold the results of the EXECUTE call.
    col_cnt NUMBER;   -- Holds the number of columns return from describe_columns.
    numVal NUMBER;    -- Used in the define_column call for number columns.
    charVal VARCHAR2(4000); -- Used in the define_column call for char columns.
    timeVal TIMESTAMP;-- Used in the define_column call for date/time columns.
    rawVal RAW(4000); -- Used in the define_column call for RAW columns.

  BEGIN
    -- Open a dynamic cursor;
    curs := DBMS_SQL.OPEN_CURSOR;

    -- Parse the sql that was passed in.
    DBMS_SQL.PARSE( curs, p_queryText, DBMS_SQL.NATIVE );
    -- Bind the id columns.
    FOR i IN p_idList.FIRST..p_idList.LAST
    LOOP
      IF p_bindRight
      THEN
        charVal := p_idList(i).right_value;
      ELSE
        charVal := p_idList(i).left_value;
      END IF;

      IF p_idList(i).type = NUMBER_TYPE_C THEN
        numVal := charVal;
        DBMS_SQL.BIND_VARIABLE(curs, ':b'||i, numVal);
      ELSE IF p_idList(i).type = DATETIME_TYPE_C THEN
        timeVal := charVal;
        DBMS_SQL.BIND_VARIABLE(curs, ':b'||i, timeVal);
      ELSE IF p_idList(i).type = RAW_TYPE_C THEN
        rawVal := charVal;
        DBMS_SQL.BIND_VARIABLE_RAW(curs, ':b'||i, rawVal);
      ELSE
        DBMS_SQL.BIND_VARIABLE(curs, ':b'||i, charVal);
      END IF;
      END IF;
      END IF;

    END LOOP;

    --Now we do EXACTLY what we just did above. The catch is that we are binding
    --the "additional" values. We also don't need to worry about the left v.
    --right stuff
    FOR i IN p_extraBindList.FIRST..p_extraBindList.LAST
    LOOP
      charVal := p_extraBindList(i).value;

      IF p_extraBindList(i).type = NUMBER_TYPE_C THEN
        numVal := charVal;
        DBMS_SQL.BIND_VARIABLE(curs, ':x'||i, numVal);
      ELSE IF p_extraBindList(i).type = DATETIME_TYPE_C THEN
        timeVal := charVal;
        DBMS_SQL.BIND_VARIABLE(curs, ':x'||i, timeVal);
      ELSE IF p_extraBindList(i).type = RAW_TYPE_C THEN
        rawVal := charVal;
        DBMS_SQL.BIND_VARIABLE_RAW(curs, ':x'||i, rawVal);
      ELSE
        DBMS_SQL.BIND_VARIABLE(curs, ':x'||i, charVal);
      END IF;
      END IF;
      END IF;

    END LOOP;
    

    -- Execute the sql statement
    numrows := DBMS_SQL.EXECUTE(curs);

    -- We want to examine the metadata to get datatypes.
    DBMS_SQL.DESCRIBE_COLUMNS( curs, col_cnt, p_rec_tab );

    -- Loop throught the record that describes the columns and call the
    -- define column procedure.
    FOR i in p_rec_tab.first..p_rec_tab.last
    LOOP
      CASE
        WHEN p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
          DBMS_SQL.DEFINE_COLUMN( curs, i, numVal );
        WHEN p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_DATE OR
             p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP OR
             p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_TZ OR
             p_rec_tab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP_LTZ THEN
          DBMS_SQL.DEFINE_COLUMN( curs, i, timeVal );
        ELSE
          DBMS_SQL.DEFINE_COLUMN( curs, i, charVal, p_rec_tab(i).col_max_len );
      END CASE;
    END LOOP;
    RETURN curs;
  END GET_CURSOR_EXTRA_BINDS;


--------------------------------------------------------------------------------
-- This procedure takes first and second (left and right, new and old) queries
-- and various pieces of metadata to perform the query
-- in and to add any results to the p_deltaEntries parameter.
--  p_firstQueryText   A query that returns a list of key columns, followed by a list of
--        column pairs. Each key column corresponds to a column in the key list and each
--        column pair corresponds to a column in the attrList.  They must be in the same
--        order.
--  p_secondQueryText   A query that returns a list of key columns, followed by a list of
--        column pairs. Each key column corresponds to a column in the key list and each
--        column pair corresponds to a column in the attrList.  They must be in the same
--        order.
--  p_keyList     A list of the column names of the key matching columns.
--  p_attrList    A list of the column names of the column pairs being compared.
--  p_idList      A list of column name, id pairs used to bind to the query.
--                Each is bound to a variable whose name is the index of the pair and
--                a l or r dependending if it is the left value or right (old) value.
-- p_keyValList   A list of the key values. We need this list in order to identify
--                subsets of each cursor that have duplicate keys
--  p_flags       Flags are RETURN_NONE, RETURN_DIFFS, RETURN_ALL.
--  p_deltaEntries  A list of results so far accumulated.  This procedure may add to that
--        list.
--  p_deltaSummary A structure in which to record the number of rows, each for
--                sames and different.
  PROCEDURE  GET_DELTAS_IGNORE_DUPLICATES( p_firstQueryText VARCHAR2,
                        p_secondQueryText VARCHAR2,
                        p_collectionType VARCHAR2,
                        p_keyList VARCHAR2_TABLE,
                        p_attrList MGMT_DELTA_VALUES,
                        p_idList MGMT_DELTA_VALUES,
                        p_keyValList MGMT_NAME_VALUES,
                        p_flags SMALLINT,
                        p_deltaRecorder in out MGMT_DELTA_RECORDER,
                        p_deltaSummary in out MGMT_DELTA_SUMMARY )
  IS
    fCurs INTEGER;     -- Cursor Id for first query
    sCurs INTEGER;     -- Cursor Id for second query
    fNumrows INTEGER;  -- Numrows used to get return value from execute call.
    sNumrows INTEGER;  -- Numrows used to get return value from execute call.
    col_cnt INTEGER;   -- The column count from describe_columns
    frec_tab dbms_sql.desc_tab;  -- The table that has describe_column information
    srec_tab dbms_sql.desc_tab;  -- The table that has describe_column information
    fCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    fNumVal NUMBER;           -- Used to hold left side number column data.
    fTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    sCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    sNumVal NUMBER;           -- Used to hold left side number column data.
    sTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    fDone BOOLEAN;             -- Used to indicate that cursor is exhausted.
    sDone BOOLEAN;
    fKeyCols MGMT_NAME_VALUES; -- To hold key for each entry.
    sKeyCols MGMT_NAME_VALUES; -- To hold key for each entry.
    deltaEntry MGMT_DELTA_ENTRY_OBJ;
  BEGIN
    -- Get first and second query Cursor.
    fCurs := GET_CURSOR_EXTRA_BINDS( p_firstQueryText,
                         p_idList,
                         p_keyValList,
                         FALSE,
                         frec_tab );
    sCurs := GET_CURSOR_EXTRA_BINDS( p_firstQueryText,
                         p_idList,
                         p_keyValList,
                         TRUE,
                         srec_tab );

    -- LOOP until both left and right cursors are exhausted.
    fKeyCols := GET_KEY_COLS( fCurs, p_keyList, frec_tab );
    sKeyCols := GET_KEY_COLS( sCurs, p_keyList, srec_tab );
    LOOP
      fDone := fKeyCols IS NULL;
      sDone := sKeyCols IS NULL;

      deltaEntry := NULL;

      IF fDone AND sDone THEN EXIT; END IF;

      -- comVal = 0 then same.  <0 then f < s, >0 then r > s.  If fDone or sDone
      -- then we don't care but will make it zero so left and right test don't
      -- get fooled.

      -- If left is exhausted and not right, or left > right then
      IF fDone THEN
        --  Add a right only and advance the right cursor.
        -- FIrst get the keyCols
        deltaEntry :=
          GET_ONE_SIDE_DELTA( p_collectionType,
                            sKeyCols,
                            p_attrList,
                            sCurs,
                            srec_tab,
                            RIGHT_ONLY_C,
                            p_deltaSummary,
                            p_flags );
        -- get next row.
        sKeyCols := GET_KEY_COLS( sCurs, p_keyList, srec_tab );
      -- Else if right is exhausted and not left, or right > left then
      ELSE IF sDone THEN
        --  Add a left only and advance the left cursor.
        -- FIrst get the keyCols
        deltaEntry :=
          GET_ONE_SIDE_DELTA( p_collectionType,
                            fKeyCols,
                            p_attrList,
                            fCurs,
                            frec_tab,
                            LEFT_ONLY_C,
                            p_deltaSummary,
                            p_flags );
        -- get next row.
        fKeyCols := GET_KEY_COLS( fCurs, p_keyList, frec_tab );
      -- Else if left = right, then compare the left and right diff columns.
      ELSE
        deltaEntry :=
          GET_BOTH_SIDE_DELTA( p_collectionType,
                             fKeyCols,
                             p_attrList,
                             fCurs,
                             sCurs,
                             frec_tab,
                             srec_tab,
                             p_deltaSummary,
                             p_flags );
        -- get next rowx.
        fKeyCols := GET_KEY_COLS( fCurs, p_keyList, frec_tab );
        sKeyCols := GET_KEY_COLS( sCurs, p_keyList, srec_tab );
      END IF; END IF;
      IF deltaEntry IS NOT NULL
      THEN
        p_deltaRecorder.RECORD_DELTA( deltaEntry );
      END IF;
    END LOOP;
    -- Close both cursors.
    dbms_sql.close_cursor(fCurs);
    dbms_sql.close_cursor(sCurs);
  EXCEPTION
    WHEN OTHERS THEN
      dbms_sql.close_cursor(fCurs);
      dbms_sql.close_cursor(sCurs);
      RAISE;
  END GET_DELTAS_IGNORE_DUPLICATES;



------------------------------------------------------------------------------------
-- This procedure takes first and second (left and right, new and old) queries
-- and various pieces of metadata to perform the query
-- in and to add any results to the p_deltaEntries parameter.
--  p_firstQueryText   A query that returns a list of key columns, followed by a list of
--        column pairs. Each key column corresponds to a column in the key list and each
--        column pair corresponds to a column in the attrList.  They must be in the same
--        order.
--  p_secondQueryText   A query that returns a list of key columns, followed by a list of
--        column pairs. Each key column corresponds to a column in the key list and each
--        column pair corresponds to a column in the attrList.  They must be in the same
--        order.
--  p_keyList     A list of the column names of the key matching columns.
--  p_attrList    A list of the column names of the column pairs being compared.
--  p_idList      A list of column name, id pairs used to bind to the query.
--                Each is bound to a variable whose name is the index of the pair and
--                a l or r dependending if it is the left value or right (old) value.
--  p_flags       Flags are RETURN_NONE, RETURN_DIFFS, RETURN_ALL.
--  p_deltaEntries  A list of results so far accumulated.  This procedure may add to that
--        list.
--  p_deltaSummary A structure in which to record the number of rows, each for
--                sames and different.
  PROCEDURE  GET_DELTAS( p_firstQueryText VARCHAR2,
                        p_secondQueryText VARCHAR2,
                        p_collectionType VARCHAR2,
                        p_keyList VARCHAR2_TABLE,
                        p_attrList MGMT_DELTA_VALUES,
                        p_idList MGMT_DELTA_VALUES,
                        p_flags SMALLINT,
                        p_deltaRecorder in out MGMT_DELTA_RECORDER,
                        p_deltaSummary in out MGMT_DELTA_SUMMARY )
  IS
    fCurs INTEGER;     -- Cursor Id for first query
    sCurs INTEGER;     -- Cursor Id for second query
    fNumrows INTEGER;  -- Numrows used to get return value from execute call.
    sNumrows INTEGER;  -- Numrows used to get return value from execute call.
    col_cnt INTEGER;   -- The column count from describe_columns
    frec_tab dbms_sql.desc_tab;  -- The table that has describe_column information
    srec_tab dbms_sql.desc_tab;  -- The table that has describe_column information
    fCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    fNumVal NUMBER;           -- Used to hold left side number column data.
    fTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    sCharVal VARCHAR2(4000);  -- Used to hold left side character column data.
    sNumVal NUMBER;           -- Used to hold left side number column data.
    sTimeVal TIMESTAMP;       -- Used to hold left side timestamp column data.
    fDone BOOLEAN;             -- Used to indicate that cursor is exhausted.
    sDone BOOLEAN;
    fKeyCols MGMT_NAME_VALUES; -- To hold key for each entry.
    sKeyCols MGMT_NAME_VALUES; -- To hold key for each entry.
    comVal NUMBER;
    deltaEntry MGMT_DELTA_ENTRY_OBJ;
    tempDeltaEntry MGMT_DELTA_ENTRY_OBJ;
    tempFKeyCols MGMT_NAME_VALUES;
    tempSKeyCols MGMT_NAME_VALUES;
    tempFDone BOOLEAN;
    tempSDone BOOLEAN;
    newFirstQueryText VARCHAR2(4000);
    newSecondQueryText VARCHAR2(4000);
  BEGIN
    -- Get first and second query Cursor.
    fCurs := GET_CURSOR( p_firstQueryText,
                         p_idList,
                         FALSE,
                         frec_tab );
    sCurs := GET_CURSOR( p_firstQueryText,
                         p_idList,
                         TRUE,
                         srec_tab );

    -- LOOP until both left and right cursors are exhausted.
    fKeyCols := GET_KEY_COLS( fCurs, p_keyList, frec_tab );
    sKeyCols := GET_KEY_COLS( sCurs, p_keyList, srec_tab );
    LOOP
      fDone := fKeyCols IS NULL;
      sDone := sKeyCols IS NULL;

      deltaEntry := NULL;

      IF fDone AND sDone THEN EXIT; END IF;

      -- comVal = 0 then same.  <0 then f < s, >0 then r > s.  If fDone or sDone
      -- then we don't care but will make it zero so left and right test don't
      -- get fooled.
      comVal := 0;
      IF NOT (fDone OR sDone) THEN
        comVal := COMPARE_KEYS( fKeyCols, sKeyCols );
      END IF;

      -- If left is exhausted and not right, or left > right then
      IF fDone OR comVal > 0 THEN
        --  Add a right only and advance the right cursor.
        -- FIrst get the keyCols
        deltaEntry :=
          GET_ONE_SIDE_DELTA( p_collectionType,
                            sKeyCols,
                            p_attrList,
                            sCurs,
                            srec_tab,
                            RIGHT_ONLY_C,
                            p_deltaSummary,
                            p_flags );
        -- get next row.
        sKeyCols := GET_KEY_COLS( sCurs, p_keyList, srec_tab );
      -- Else if right is exhausted and not left, or right > left then
      ELSE IF sDone OR comVal < 0 THEN
        --  Add a left only and advance the left cursor.
        -- FIrst get the keyCols
        deltaEntry :=
          GET_ONE_SIDE_DELTA( p_collectionType,
                            fKeyCols,
                            p_attrList,
                            fCurs,
                            frec_tab,
                            LEFT_ONLY_C,
                            p_deltaSummary,
                            p_flags );
        -- get next row.
        fKeyCols := GET_KEY_COLS( fCurs, p_keyList, frec_tab );
      -- Else if left = right, then compare the left and right diff columns.
      ELSE
        tempDeltaEntry :=
          GET_BOTH_SIDE_DELTA( p_collectionType,
                             fKeyCols,
                             p_attrList,
                             fCurs,
                             sCurs,
                             frec_tab,
                             srec_tab,
                             p_deltaSummary,
                             p_flags );
        -- get next rowx.
        tempFKeyCols := GET_KEY_COLS( fCurs, p_keyList, frec_tab );
        tempSKeyCols := GET_KEY_COLS( sCurs, p_keyList, srec_tab );

        --if the temporary delta entry is a diff, we don't necessarily want to 
        --record it. What we need to do is to check if the next set of keys is 
        --identical. The keys from the next row in each cursor need to be equal
        --to each other and to the keys from this row.



        tempFDone := tempFKeyCols IS NULL;
        tempSDone := tempSKeyCols IS NULL;
        IF (NOT (tempFDone OR tempSDone))
           AND (COMPARE_KEYS(tempFKeyCols, tempSKeyCols) = 0)
           AND (COMPARE_KEYS(tempFKeyCols, fKeyCols) = 0) THEN
        
          --if we are here, it means that the next row has the same set of
          --keys as the current row. So we need to get the deltas for the 
          --sub-list.
          newFirstQueryText := GET_SUBLIST_QUERY(p_firstQueryText,
                                                          p_attrList,
                                                          tempFKeyCols);
          newSecondQueryText := GET_SUBLIST_QUERY(p_secondQueryText,
                                                          p_attrList,
                                                          tempFKeyCols);
          --in theory, this should be all we need to do
          GET_DELTAS_IGNORE_DUPLICATES( newFirstQueryText,
                      newSecondQueryText,
                      p_collectionType,
                      p_keyList,
                      p_attrList,
                      p_idList,
                      tempFKeyCols,
                      p_flags,
                      p_deltaRecorder,
                      p_deltaSummary);
          --now we just need ot advance the real cursors until we get past this
          --set of keys. We have to do both INDEPENDENTLY since there may be
          --actual insertions or deletions
          fKeyCols := GET_KEY_COLS(fCurs, p_keyList, frec_tab);
          sKeyCols := GET_KEY_COLS(sCurs, p_keyList, srec_tab);
          fDone := fKeyCols IS NULL;
          sDone := sKeyCols IS NULL;
          WHILE(NOT fDone) AND (COMPARE_KEYS(tempFKeyCols, fKeyCols) = 0)
          LOOP
            fKeyCols := GET_KEY_COLS(fCurs, p_keyList, frec_tab);
            fDone := fKeyCols IS NULL;
          END LOOP;
          
          WHILE (NOT sDOne) AND (COMPARE_KEYS(tempSKeyCols, sKeyCols) = 0)
          LOOP
            sKeyCols := GET_KEY_COLS(sCurs, p_keyList, srec_tab);
            sDone := sKeyCols IS NULL;
          END LOOP;

        ELSE
          fKeyCols := tempFKeyCols;
          sKeyCols := tempSKeyCols;
          deltaEntry := tempDeltaEntry;
        END IF;

        

      END IF; END IF;
      IF deltaEntry IS NOT NULL
      THEN
        p_deltaRecorder.RECORD_DELTA( deltaEntry );
      END IF;
    END LOOP;
    -- Close both cursors.
    dbms_sql.close_cursor(fCurs);
    dbms_sql.close_cursor(sCurs);
  EXCEPTION
    WHEN OTHERS THEN
      dbms_sql.close_cursor(fCurs);
      dbms_sql.close_cursor(sCurs);
      RAISE;
  END GET_DELTAS;


--------------------------------------------------------------------------------
--  The following methods are used to create a sql query for doing comparisons.
-- The following methods are used to build the string that will be substituted
-- into the query templates

  -- This returns the select list for
  -- the comparison which includes the matching cols and the diff cols.
  FUNCTION GET_TC1_STRING( p_keyList VARCHAR2_TABLE, p_attrList MGMT_DELTA_VALUES )
      RETURN VARCHAR2
  IS
    selectList VARCHAR2(1000) := '';
    comma VARCHAR2(10) := '';
  BEGIN
    IF p_keyList IS NOT NULL AND p_keyList.COUNT > 0
    THEN
      FOR i IN p_keyList.FIRST..p_keyList.LAST
      LOOP
        selectList := selectList || comma || p_keyList(i);
        comma := ', ';
      END LOOP;
    END IF;

    IF p_attrList IS NOT NULL AND p_attrList.COUNT > 0
    THEN
      FOR i IN p_attrList.FIRST..p_attrList.LAST
      LOOP
        selectList := selectList || comma || p_attrList(i).NAME ;
        comma := ', ';
      END LOOP;
    END IF;

    RETURN selectList;
  END GET_TC1_STRING;
  
   -- This function compares each column in the idlist with its bind var.
  FUNCTION GET_TC3_STRING( p_idList MGMT_DELTA_VALUES )
      RETURN VARCHAR2
  IS
    idPred VARCHAR2(1000) := '';
    andtxt VARCHAR2(10) := ' ';
  BEGIN
    FOR i IN p_idList.FIRST..p_idList.LAST
    LOOP
      -- Only append the id if the status field is not null
      IF p_idList(i).STATUS IS NULL THEN
        idPred := idPred || andtxt || p_idList(i).NAME || ' = :b' || i;
        andtxt := ' and ';
      END IF;
    END LOOP;

    RETURN idPred;
  END GET_TC3_STRING;

  -- This function is like TC1 except that it only returns the select list for
  -- the first (not the second attrs as in TC1).  This is used for the existence
  -- query creation.
  FUNCTION GET_TC4_STRING( p_keyList VARCHAR2_TABLE )
      RETURN VARCHAR2
  IS
    selectList VARCHAR2(1000) := '';
    -- We want the sorting to be character sort to be consistent with the
    -- way comparisons are done when walking the two result sets, so we
    -- explicitly to a to_char...
    comma VARCHAR2(10) := 'to_char(';
  BEGIN
    IF p_keyList IS NOT NULL AND p_keyList.COUNT > 0
    THEN
      selectList := ' order by ';
      FOR i IN p_keyList.FIRST..p_keyList.LAST
      LOOP
        selectList := selectList || comma || p_keyList(i) || ')';
        comma := ', to_char(';
      END LOOP;
    END IF;

    RETURN selectList;
  END GET_TC4_STRING;

  -- GET_QUERY
  --  Createa a query given the base query or table name.  See EACH_QUERY_TEMPLATE
  -- for the format of the query.  It essentially returns the key and diff columns
  -- id'd by the id columns, order by the key columns. (Character sort order).
  FUNCTION GET_QUERY( p_queryText VARCHAR2,
                      p_keyList VARCHAR2_TABLE,
                      p_attrList MGMT_DELTA_VALUES,
                      p_idList MGMT_DELTA_VALUES )
                  RETURN VARCHAR2
  IS
    -- init the query using the exists query template;
    query VARCHAR2(4000) := EACH_QUERY_TEMPLATE;
    tempstr VARCHAR2(1000);
  BEGIN
    -- Now start substituting the appropriate values
    -- First add the select list.
    tempstr := GET_TC1_STRING( p_keyList, p_attrList );
    query := REPLACE( query, TC1, tempstr );

    -- Now plug in the query, table, or view.
    tempstr := GET_TC2_STRING(p_queryText);
    query := REPLACE( query, TC2, tempstr);

    -- Not add the id column match against the binds.
    tempstr := GET_TC3_STRING(  p_idList );
    query := REPLACE( query, TC3, tempstr );

    -- And finally, the order-by match column list.
    tempstr := GET_TC4_STRING(  p_keyList);
    query := REPLACE( query, TC4, tempstr );

    RETURN query;
  END GET_QUERY;

-------------------------------------------------------------------------------------
-- DO_DIFF
--
--  Execute all the queries represented by the members of the MGMT_DELTA_QUERIES
-- collectio passed into this function.
  PROCEDURE DO_DIFF( p_deltaQueries MGMT_DELTA_QUERIES,
                     p_deltaRecorder in out MGMT_DELTA_RECORDER )
  IS
    firstQueryText VARCHAR2(8000);
    secondQueryText VARCHAR2(8000);
    summaryObj MGMT_DELTA_SUMMARY;
  BEGIN
    FOR i IN p_deltaQueries.FIRST..p_deltaQueries.LAST
    LOOP

      -- Set up the queries for each side.
      firstQueryText := GET_QUERY( p_deltaQueries(i).FIRST_QUERY_TEXT,
                                   p_deltaQueries(i).KEY_LIST,
                                   p_deltaQueries(i).ATTRIBUTE_LIST,
                                   p_deltaQueries(i).ID_LIST );
      -- If the second query is null, then it should be the same as the first.
      IF p_deltaQueries(i).SECOND_QUERY_TEXT IS NULL THEN
        secondQueryText := firstQueryText;
      ELSE
        secondQueryText := GET_QUERY( p_deltaQueries(i).SECOND_QUERY_TEXT,
                                      p_deltaQueries(i).KEY_LIST,
                                      p_deltaQueries(i).ATTRIBUTE_LIST,
                                      p_deltaQueries(i).ID_LIST );
      END IF;


      -- Set up a new summary object for this sub-comparison
      summaryObj := MGMT_DELTA_SUMMARY( p_deltaQueries(i).COLLECTION_TYPE,
                                        0,
                                        0,
                                        0,
                                        VARCHAR2_TABLE());
      BEGIN

        -- Do the comparison.
        GET_DELTAS( firstQueryText,
                    secondQueryText,
                    p_deltaQueries(i).COLLECTION_TYPE,
                    p_deltaQueries(i).KEY_LIST,
                    p_deltaQueries(i).ATTRIBUTE_LIST,
                    p_deltaQueries(i).ID_LIST,
                    p_deltaQueries(i).FLAGS,
                    p_deltaRecorder,
                    summaryObj );

      EXCEPTION
        WHEN OTHERS THEN
--          err_num := SQLCODE;
          summaryObj.ERRORS.EXTEND;
          summaryObj.ERRORS(summaryObj.ERRORS.LAST) := SUBSTR(SQLERRM, 1, 256);
          summaryObj.ERRORS.EXTEND;
          summaryObj.ERRORS(summaryObj.ERRORS.LAST) := SUBSTR(firstQueryText, 1, 256);
          IF p_deltaQueries(i).SECOND_QUERY_TEXT IS NOT NULL THEN
            summaryObj.ERRORS(summaryObj.ERRORS.LAST) := SUBSTR(secondQueryText, 1, 256);
          END IF;
      END;
      p_deltaRecorder.RECORD_SUMMARY( summaryObj );
    END LOOP;
  END DO_DIFF;
---------------------------------------------------------------------------------
-- SAVE_COMPARISON_DELTA
--
--  Save the delta information in the repository given the comparison id.  Note
-- thar this does not update the summary information.  
--
-- PARAMETERS
--
--  p_comparisonGuid  The comparison id under which to save this delta.
--  p_collectionType  The logical table name to which this diff refers.
--  p_status          SAME, DIFFERENT, LEFT_ONLY, RIGHT_ONLY
--  p_keyList         The key information
--  p_diffList        The non-key columns with differences.
--
---------------------------------------------------------------------------------

-- SAVE_COMPARISON_RESULTS
--
--  Do a comparison based on the metadata described in the list of logical table
-- information.  This version of do_diff_queries is meant for saved comparisons.
-- The saved comparison's guid is returned.
--
-- PARAMETERS
--
--  p_deltaRecorder   Holds the comparison results.
--  p_comparisonType  The type of the comparison.  For config fw, this is the
--                  snapshot_type.
--  p_compTargetType  The type of the target of the comparison.
--  p_compTargetProperties A list of comparison target properties, which are
--                    name, left_value, right_value triplets.
--  session_id - identifies the session in which this comparison is saved.
--  RETURNS           A guid pointing to the comparison result in the
--                  MGMT_DELTA_SAVED_COMPARISONS table.
--  
  FUNCTION START_SAVE_COMPARISON_RESULTS( p_comparisonType VARCHAR2,
                                          p_compTargetType VARCHAR2,
                                          p_compTargetProperties COMPARISON_PROPERTIES,
	                                        p_session_id RAW DEFAULT SYS_GUID())
                  RETURN RAW
  IS
    l_tempGuid RAW(16);
    l_tempKeyGuid RAW(16);
    l_deltaValues MGMT_DELTA_VALUES;
  BEGIN
    l_tempGuid := sys_guid();
    -- First write a save comparison entry
    INSERT INTO mgmt_delta_saved_comparison( DELTA_COMP_GUID, 
                                             COMPARISON_TYPE,
                                             TARGET_TYPE,
                                             SESSION_ID)
      VALUES ( l_tempGuid,
               p_comparisonType,
               p_compTargetType,
               p_session_id);


    -- Write out the comparison properties
    IF p_compTargetProperties IS NOT NULL AND
       p_compTargetProperties.COUNT > 0
    THEN
      FOR h IN p_compTargetProperties.FIRST..p_compTargetProperties.LAST
      LOOP
        INSERT INTO MGMT_DELTA_COMP_PROPERTIES( DELTA_COMP_GUID,
                                                NAME,
                                                LEFT_VALUE,
                                                RIGHT_VALUE )
          VALUES( l_tempGuid,
                  p_compTargetProperties(h).NAME,
                  p_compTargetProperties(h).LEFT_VALUE,
                  p_compTargetProperties(h).RIGHT_VALUE );
      END LOOP;
    END IF;
    RETURN l_tempGuid;
  END START_SAVE_COMPARISON_RESULTS;
  -------------------------------------------------------------------------------------
-- DO_DIFF_QUERIES
--
--  Do a comparison based on the metadata described in the list of logical table
-- information.  Return two cursors, one which is a list of all of the differences
-- requested in the metadata, the other is a summary of the findings.
--
-- PARAMETERS
--
--  p_deltaQueries    A list of MGMT_DELTA_QUERY objects, each of which describes
--                  a logical table, the details of which are used to compare
--                  two snapshots. 
--  p_deRef           A ref cursor to the MGMT_DELTA_ENTRY_OBJ records.
--  p_dsRef           A ref cursor to the MGMT_DELTA_SUMMARY records.
--  session_id - identifies the session in which this comparison is saved.
--
  PROCEDURE DO_DIFF_QUERIES( p_deltaQueries MGMT_DELTA_QUERIES,
                             p_deRef out DELTA_ENTRY_REF,
                             p_dsRef out DELTA_SUMMARY_REF,
                             p_session_id RAW DEFAULT SYS_GUID())
  IS
    deltaRecorder MGMT_DELTA_ENTRY_RECORDER;
    l_compGuid RAW(16);
  BEGIN
    l_compGuid := START_SAVE_COMPARISON_RESULTS( NULL,
                                                 NULL,
                                                 NULL,
						                                     p_session_id);
    deltaRecorder := MGMT_DELTA_ENTRY_RECORDER( sysdate, l_compGuid );

    DO_DIFF( p_deltaQueries, deltaRecorder );

    RETRIEVE_SAVED_COMPARISON( l_compGuid,
                               p_deRef,
                               p_dsRef );

  END DO_DIFF_QUERIES;

-------------------------------------------------------------------------------------
-- DO_DIFF_QUERIES_AND_SAVE
--
--  Do a comparison based on the metadata described in the list of logical table
-- information.  This version of do_diff_queries is meant for saved comparisons.  
-- The saved comparison's guid is returned.
--
-- PARAMETERS
--
--  p_deltaQueries    A list of MGMT_DELTA_QUERY objects, each of which describes
--                  a logical table, the details of which are used to compare
--                  two snapshots. 
--  p_comparisonType  The type of the comparison.  For config fw, this is the
--                  snapshot_type.
--  p_compTargetType  The type of the target of the comparison. 
--  p_compTargetProperties A list of properties that describe the two targets of
--                  the comparison.
--  session_id - identifies the session in which this comparison is saved.
--  RETURNS           A guid pointing to the comparison result in the 
--                  MGMT_DELTA_SAVED_COMPARISONS table.
--
  FUNCTION DO_DIFF_QUERIES_AND_SAVE( p_deltaQueries MGMT_DELTA_QUERIES,
                                     p_comparisonType VARCHAR2,
                                     p_compTargetType VARCHAR2,
                                     p_compTargetProperties COMPARISON_PROPERTIES,
                                     p_session_id RAW DEFAULT SYS_GUID())
            RETURN RAW
  IS
    deltaRecorder MGMT_DELTA_ENTRY_RECORDER;
    l_compGuid RAW(16);
  BEGIN
    l_compGuid := START_SAVE_COMPARISON_RESULTS( p_comparisonType,
                                                 p_compTargetType,
                                                 p_compTargetProperties,
                                                 p_session_id);
    deltaRecorder := MGMT_DELTA_ENTRY_RECORDER( sysdate, l_compGuid );

    DO_DIFF( p_deltaQueries, deltaRecorder );

    return l_compGuid;

  END DO_DIFF_QUERIES_AND_SAVE;
--------------------------------------------------------------------------------
  FUNCTION READ_DELTAS( p_status VARCHAR2, 
                        p_collection_type VARCHAR2, 
                        p_key_guid RAW)
            RETURN MGMT_DELTA_ENTRY_OBJ
  IS
    key_values MGMT_NAME_VALUES;
    CURSOR c2 IS
      SELECT name, value, type 
      FROM mgmt_delta_comp_key_cols
      WHERE key_guid = p_key_guid;
    key_rec c2%ROWTYPE;
    delta_values MGMT_DELTA_VALUES;
    CURSOR c3 IS
      SELECT name, left_value, right_value, status, type 
      FROM mgmt_delta_comp_delta_details
      WHERE key_guid = p_key_guid
      order by name;
    detail_rec c3%ROWTYPE;
  BEGIN
    -- Get the key values for this delta entry.
    key_values := MGMT_NAME_VALUES();
    FOR key_rec IN c2
    LOOP
      key_values.EXTEND;
      key_values(key_values.LAST) := MGMT_NAME_VALUE( key_rec.name,
                                                      key_rec.value,
                                                      key_rec.type );
    END LOOP;

    -- Now get the delta value details.
    delta_values := MGMT_DELTA_VALUES();
    FOR detail_rec IN C3
    LOOP
      delta_values.EXTEND;
      delta_values(delta_values.LAST) := MGMT_DELTA_VALUE( detail_rec.name,
                                                           detail_rec.left_value,
                                                           detail_rec.right_value,
                                                           detail_rec.status,
                                                           detail_rec.type );
    END LOOP;

    RETURN MGMT_DELTA_ENTRY_OBJ( p_status,
                                 p_collection_type,
                                 key_values,
                                 delta_values );

  END READ_DELTAS;
--------------------------------------------------------------------------------
-- RETRIEVE_SAVED_COMPARISON
--  p_diffGuid      The id of the saved comparison. 
--  p_deRef         A ref cursor to the MGMT_DELTA_ENTRY_OBJ records.
--  p_dsRef         A ref cursor to the MGMT_DELTA_SUMMARY records.
--  p_collectionTypes An optional list of comparison type to restrict the returned
--                  results to.
  PROCEDURE RETRIEVE_SAVED_COMPARISON( p_diffGuid RAW,
                                       p_deRef out DELTA_ENTRY_REF,
                                       p_dsRef out DELTA_SUMMARY_REF,
                                       p_collectionTypes VARCHAR2_TABLE := NULL )
  IS
    l_deltaEntries MGMT_DELTA_ENTRIES;    
    CURSOR c1 IS
      SELECT status, collection_type, key_guid 
      FROM 
        mgmt_delta_comparison_deltas d,
        TABLE( p_collectionTypes ) t
      WHERE d.collection_type = t.column_value
        AND delta_comp_guid = p_diffGuid;
    CURSOR c1All IS
      SELECT status, collection_type, key_guid 
      FROM 
        mgmt_delta_comparison_deltas
      WHERE delta_comp_guid = p_diffGuid;
    delta_rec c1%ROWTYPE; 
    l_deltaEntry MGMT_DELTA_ENTRY_OBJ;
  BEGIN
    l_deltaEntries := MGMT_DELTA_ENTRIES();

    IF p_collectionTypes IS NULL THEN
      FOR delta_rec IN c1All
      LOOP
        l_deltaEntry := READ_DELTAS( delta_rec.status, 
                                     delta_rec.collection_type, 
                                     delta_rec.key_guid );
        -- Now make a deltaEntry and place it in the list.
        l_deltaEntries.EXTEND;
        l_deltaEntries(l_deltaEntries.LAST) := l_deltaEntry;
      END LOOP;
      OPEN p_dsRef FOR SELECT COLLECTION_TYPE, DIFFERENT_COUNT, LEFT_COUNT, RIGHT_COUNT, ERRORS 
                       FROM MGMT_DELTA_COMP_SUMMARIES WHERE DELTA_COMP_GUID = p_diffGuid;
    ELSE
      FOR delta_rec IN c1
      LOOP
        l_deltaEntry := READ_DELTAS( delta_rec.status, 
                                     delta_rec.collection_type, 
                                     delta_rec.key_guid );
        -- Now make a deltaEntry and place it in the list.
        l_deltaEntries.EXTEND;
        l_deltaEntries(l_deltaEntries.LAST) := l_deltaEntry;
      END LOOP;
      OPEN p_dsRef FOR SELECT COLLECTION_TYPE, DIFFERENT_COUNT, LEFT_COUNT, RIGHT_COUNT, ERRORS 
                       FROM MGMT_DELTA_COMP_SUMMARIES s, TABLE(p_collectionTypes) t 
                       WHERE s.COLLECTION_TYPE = t.column_value 
                         AND DELTA_COMP_GUID = p_diffGuid;
    END IF;
    OPEN p_deRef FOR SELECT * FROM TABLE(CAST(l_deltaEntries AS MGMT_DELTA_ENTRIES));
  END RETRIEVE_SAVED_COMPARISON;
  


--------------------------------------------------------------------------------
-- DO_HISTORY_DIFFS
--
--  This procedure does a comparison for the purpose of saving the results in
--  the history tables.  It is essentially the same as do_diff_queries except 
--  where the results go.
--
-- PARAMETERS
--  
--  p_deltaQueries    A list of MGMT_DELTA_QUERY objects, each of which describes
--                  a logical table, the details of which are used to compare
--                  two snapshots, one new current, one the previous current.
--                  The results of the comparison are recorded as history entries
--                  in the various MGMT_DELTA history tables.
--  p_snaptime        This is the date-time of when the new current was actual
--                  captured.  It is used to record the time that the changes
--                  were detected.
--
  PROCEDURE DO_HISTORY_DIFFS( p_deltaQueries MGMT_DELTA_QUERIES,
                              p_snaptime DATE )
  IS
    deltaRecorder MGMT_HISTORY_ENTRY_RECORDER;
  BEGIN
    deltaRecorder := MGMT_HISTORY_ENTRY_RECORDER( p_snaptime );

    DO_DIFF( p_deltaQueries, deltaRecorder );

  END DO_HISTORY_DIFFS;
  
------------------------------------------------------------------------------------
-- PURGE_HISTORY_RECORDS
  -- BEGIN Purge policies callback procedures
  PROCEDURE ECM_HISTORY_PURGE(pcb_params IN OUT MGMT_PURGE_CALLBACK_PARAMS) IS
    TYPE p_rowid_list_type IS
      TABLE OF UROWID
      INDEX BY BINARY_INTEGER;
      l_purge_rowids p_rowid_list_type;
    l_rows_purged NUMBER := 0;
    -- Snapshot delta entries
    CURSOR purge_delta_snaps (c_purge_till_time DATE) IS
      SELECT snap.ROWID
        FROM MGMT_DELTA_SNAP snap
        WHERE snap.NEW_LEFT_TIMESTAMP < c_purge_till_time;

    CURSOR purge_delta_entries (c_purge_till_time DATE) IS
      SELECT ROWID
        FROM MGMT_DELTA_ENTRY e
        WHERE DELTA_TIME < c_purge_till_time
          AND NOT EXISTS ( SELECT * FROM MGMT_DELTA_SNAP s
                           WHERE e.DELTA_GUID = s.DELTA_GUID);
    -- Unused delta ids.
    CURSOR purge_delta_ids (c_purge_till_time DATE) IS
      SELECT ROWID
        FROM MGMT_DELTA_IDS id
        WHERE (id.LAST_UPDATED_TIME < c_purge_till_time) AND
              NOT EXISTS ( SELECT * FROM MGMT_DELTA_ENTRY e
                           WHERE id.ROW_GUID = e.ROW_GUID);

  BEGIN
    BEGIN
      OPEN purge_delta_snaps(pcb_params.purge_upto_time);
      LOOP
        -- Bulk collect row ids
        FETCH purge_delta_snaps BULK COLLECT INTO l_purge_rowids LIMIT 500;

        -- Exit when no more row ids fetched
        EXIT WHEN l_purge_rowids.COUNT <= 0;

        -- BULK delete
        FORALL i IN l_purge_rowids.FIRST..l_purge_rowids.LAST
          DELETE FROM MGMT_DELTA_SNAP WHERE ROWID = l_purge_rowids(i);

        COMMIT;
        l_purge_rowids.DELETE;
      END LOOP;
    EXCEPTION
      WHEN OTHERS THEN NULL;
    END;

    -- If any error occurs before the snap or ids are purged, they will get purged
    -- on a later date.
    -- Close the cursor if open
    IF (purge_delta_snaps%ISOPEN) THEN
      CLOSE purge_delta_snaps;
    END IF;

    BEGIN
      OPEN purge_delta_entries(pcb_params.purge_upto_time);
      LOOP
        -- Bulk collect row ids
        FETCH purge_delta_entries BULK COLLECT INTO l_purge_rowids LIMIT 500;

        -- Exit when no more row ids fetched
        EXIT WHEN l_purge_rowids.COUNT <= 0;
  
        -- BULK delete
        FORALL i IN l_purge_rowids.FIRST..l_purge_rowids.LAST
          DELETE FROM MGMT_DELTA_ENTRY WHERE ROWID = l_purge_rowids(i);

        COMMIT;

        l_rows_purged := l_rows_purged + l_purge_rowids.COUNT;

        l_purge_rowids.DELETE;

      END LOOP;
    EXCEPTION
      WHEN OTHERS THEN NULL;
    END;
    -- Close the cursor if open
    IF (purge_delta_entries%ISOPEN) THEN
      CLOSE purge_delta_entries;
    END IF;

    BEGIN
      OPEN purge_delta_ids(pcb_params.purge_upto_time);
      LOOP
        -- Bulk collect row ids
        FETCH purge_delta_ids BULK COLLECT INTO l_purge_rowids LIMIT 500;

        -- Exit when no more row ids fetched
        EXIT WHEN l_purge_rowids.COUNT <= 0;

        -- BULK delete
        FORALL i IN l_purge_rowids.FIRST..l_purge_rowids.LAST
          DELETE FROM MGMT_DELTA_IDS WHERE ROWID = l_purge_rowids(i);

        COMMIT;

        l_purge_rowids.DELETE;

      END LOOP;
    EXCEPTION
      WHEN OTHERS THEN NULL;
    END;

    -- Close the cursor if open
    IF (purge_delta_ids%ISOPEN) THEN
      CLOSE purge_delta_ids;
    END IF;

    pcb_params.callback_result := 0;
    pcb_params.rows_processed  := l_rows_purged;
    pcb_params.error_code      := 0;
    pcb_params.error_msg       := NULL;

  END ECM_HISTORY_PURGE;

------------------------------------------------------------------------------
--
-- PURPOSE
--    Cleans up all history records older than the number of days passed in.
--    Returns the number of history entries purged.
--
-- PARAMETERS
--
--  p_numberOfDays  The number of days, prior to which the history should be
--                  purged.
--
--  RETURNS         The number of history entries purged.
--
  FUNCTION PURGE_HISTORY( p_numberOfDays NUMBER := 0,
                          p_snapshotType VARCHAR2 := NULL,
                          p_targetType VARCHAR2 := NULL,
                          p_targetName VARCHAR2 := NULL )
                RETURN NUMBER
  IS
    beforeDate DATE;
    rowcount NUMBER  := 0;
    l_num_rows NUMBER := 0;
    l_num_rows1 NUMBER := 0;
    sqlText VARCHAR(256);
    l_row_guids T_ROW_GUID_TABLE;
  BEGIN
    -- Calculate the correct date to remove before.
    beforeDate := SYSDATE - p_numberOfDays;

    -- Delete from the delta_snap table older than the date.
    sqlText := 'DELETE FROM MGMT_DELTA_SNAP WHERE DELTA_TIMESTAMP < :bd';
    IF p_snapshotType IS NOT NULL
    THEN
      sqlText := sqlText || ' AND SNAPSHOT_TYPE = ' || 
        em_check.enquote_literal(replace(p_snapshotType, '''', ''''''));

    END IF;
    
    IF p_targetType IS NOT NULL
    THEN
      sqlText := sqlText || ' AND TARGET_TYPE = ' ||
        em_check.enquote_literal(replace(p_targetType, '''', ''''''));
    END IF;
    
    IF p_targetName IS NOT NULL
    THEN
      sqlText := sqlText || ' AND NEW_LEFT_TARGET_NAME = ' || 
        em_check.enquote_literal(replace(p_targetName, '''', '''''')); 
    END IF;

    sqlText := sqlText ||  ' AND ROWNUM <= ' || MGMT_GLOBAL.MAX_COMMIT;

    -- Delete rows from the table in chunks of size MAX_COMMIT
    LOOP
      EXECUTE IMMEDIATE sqlText USING beforeDate;
      l_num_rows := SQL%ROWCOUNT;
      COMMIT;
      EXIT WHEN l_num_rows < MGMT_GLOBAL.MAX_COMMIT;
    END LOOP;
    
    -- Remove all the delta entries prior to the date only if the filters are all
    -- null in chunks of size MAX_COMMIT.  
    LOOP
      IF p_snapshotType IS NULL 
        AND p_targetType IS NULL 
        AND p_targetName IS NULL 
      THEN
        DELETE FROM MGMT_DELTA_ENTRY 
          WHERE DELTA_TIME < beforeDate AND ROWNUM <= MGMT_GLOBAL.MAX_COMMIT
          RETURNING ROW_GUID BULK COLLECT INTO l_row_guids;

        l_num_rows := SQL%ROWCOUNT;
 
      ELSE
        -- There are filters so don't remove the entries that have NO_DELTA as their
        -- delta guid.  Remove all other whose delta_guid does not have a corresponding
        -- entry in the DELTA_SNAP table.  Those were removed based on the filters.

        DELETE FROM MGMT_DELTA_ENTRY e
          WHERE DELTA_TIME < beforeDate
            AND DELTA_GUID <> NO_DELTA
            AND NOT EXISTS (SELECT * FROM MGMT_DELTA_SNAP s
                            WHERE e.DELTA_GUID = s.DELTA_GUID 
                            AND ROWNUM <= 1)
            AND rownum <= MGMT_GLOBAL.MAX_COMMIT
            RETURNING ROW_GUID BULK COLLECT INTO l_row_guids;

        l_num_rows := SQL%ROWCOUNT;
      END IF;
      
      FORALL i IN l_row_guids.FIRST..l_row_guids.LAST 
        DELETE FROM MGMT_DELTA_IDS id WHERE id.ROW_GUID = l_row_guids(i)
           AND  NOT EXISTS (select * from mgmt_delta_entry e
                            where e.row_guid = id.row_guid and rownum <=1) ;
     
      l_num_rows1 := SQL%ROWCOUNT;
      rowcount := rowcount + l_num_rows;
      rowcount := rowcount + l_num_rows1;
      COMMIT;
      EXIT WHEN l_num_rows < MGMT_GLOBAL.MAX_COMMIT;
    END LOOP;


    RETURN ROWCOUNT;

  END PURGE_HISTORY;
END MGMT_DELTA;
/
show errors;