Using the DBMS_METADATA_DIFF API to Compare Object Metadata

This section provides an example that uses the retrieval, comparison, and submit interfaces of DBMS_METADATA and DBMS_METADATA_DIFF to fetch metadata for two tables, compare the metadata, and generate ALTER statements which make one table like the other. For simplicity, function variants are used throughout the example.

Example 21-10 Comparing Object Metadata

  1. Create two tables, TAB1 and TAB2:

    SQL> CREATE TABLE TAB1
      2     (    "EMPNO" NUMBER(4,0),
      3          "ENAME" VARCHAR2(10),
      4          "JOB" VARCHAR2(9),
      5          "DEPTNO" NUMBER(2,0)
      6     ) ;
     
    Table created.
     
    SQL> CREATE TABLE TAB2
      2     (    "EMPNO" NUMBER(4,0) PRIMARY KEY ENABLE,
      3          "ENAME" VARCHAR2(20),
      4          "MGR" NUMBER(4,0),
      5          "DEPTNO" NUMBER(2,0)
      6     ) ;
     
    Table created.
     
    

    Note the differences between TAB1 and TAB2:

    • The table names are different

    • TAB2 has a primary key constraint; TAB1 does not

    • The length of the ENAME column is different in each table

    • TAB1 has a JOB column; TAB2 does not

    • TAB2 has a MGR column; TAB1 does not

  2. Create a function to return the table metadata in SXML format. The following are some key points to keep in mind about SXML when you are using the DBMS_METADATA_DIFF API:

    • SXML is an XML representation of object metadata.

    • The SXML returned is not the same as the XML returned by DBMS_METADATA.GET_XML, which is complex and opaque and contains binary values, instance-specific values, and so on.

    • SXML looks like a direct translation of SQL creation DDL into XML. The tag names and structure correspond to names in the Oracle Database SQL Language Reference.

    • SXML is designed to support editing and comparison.

    To keep this example simple, a transform parameter is used to suppress physical properties:

    SQL> CREATE OR REPLACE FUNCTION get_table_sxml(name IN VARCHAR2) RETURN CLOB IS
      2   open_handle NUMBER;
      3   transform_handle NUMBER;
      4   doc CLOB;
      5  BEGIN
      6   open_handle := DBMS_METADATA.OPEN('TABLE');
      7   DBMS_METADATA.SET_FILTER(open_handle,'NAME',name);
      8   --
      9   -- Use the 'SXML' transform to convert XML to SXML
     10   --
     11   transform_handle := DBMS_METADATA.ADD_TRANSFORM(open_handle,'SXML');
     12   --
     13   -- Use this transform parameter to suppress physical properties
     14   --
     15   DBMS_METADATA.SET_TRANSFORM_PARAM(transform_handle,'PHYSICAL_PROPERTIES',
     16                                     FALSE);
     17   doc := DBMS_METADATA.FETCH_CLOB(open_handle);
     18   DBMS_METADATA.CLOSE(open_handle);
     19   RETURN doc;
     20  END;
     21  /
     
    Function created.
     
    
  3. Use the get_table_sxml function to fetch the table SXML for the two tables:

    SQL> SELECT get_table_sxml('TAB1') FROM dual;
     
      <TABLE xmlns="http://xmlns.oracle.com/ku" version="1.0">
       <SCHEMA>SCOTT</SCHEMA>
       <NAME>TAB1</NAME>
       <RELATIONAL_TABLE>
          <COL_LIST>
             <COL_LIST_ITEM>
                <NAME>EMPNO</NAME>
                <DATATYPE>NUMBER</DATATYPE>
                <PRECISION>4</PRECISION>
                <SCALE>0</SCALE>
             </COL_LIST_ITEM>
             <COL_LIST_ITEM>
                <NAME>ENAME</NAME>
                <DATATYPE>VARCHAR2</DATATYPE>
                <LENGTH>10</LENGTH>
             </COL_LIST_ITEM>
             <COL_LIST_ITEM>
                <NAME>JOB</NAME>
                <DATATYPE>VARCHAR2</DATATYPE>
                <LENGTH>9</LENGTH>
             </COL_LIST_ITEM>
             <COL_LIST_ITEM>
                <NAME>DEPTNO</NAME>
                <DATATYPE>NUMBER</DATATYPE>
                <PRECISION>2</PRECISION>
                <SCALE>0</SCALE>
             </COL_LIST_ITEM>
          </COL_LIST>
       </RELATIONAL_TABLE>
    </TABLE> 
      
    1 row selected.
     
    SQL> SELECT get_table_sxml('TAB2') FROM dual;
     
      <TABLE xmlns="http://xmlns.oracle.com/ku" version="1.0">
       <SCHEMA>SCOTT</SCHEMA>
       <NAME>TAB2</NAME>
       <RELATIONAL_TABLE>
          <COL_LIST>
             <COL_LIST_ITEM>
                <NAME>EMPNO</NAME>
                <DATATYPE>NUMBER</DATATYPE>
                <PRECISION>4</PRECISION>
                <SCALE>0</SCALE>
             </COL_LIST_ITEM>
             <COL_LIST_ITEM>
                <NAME>ENAME</NAME>
                <DATATYPE>VARCHAR2</DATATYPE>
                <LENGTH>20</LENGTH>
             </COL_LIST_ITEM>
             <COL_LIST_ITEM>
                <NAME>MGR</NAME>
                <DATATYPE>NUMBER</DATATYPE>
                <PRECISION>4</PRECISION>
                <SCALE>0</SCALE>
             </COL_LIST_ITEM>
             <COL_LIST_ITEM>
                <NAME>DEPTNO</NAME>
                <DATATYPE>NUMBER</DATATYPE>
                <PRECISION>2</PRECISION>
                <SCALE>0</SCALE>
             </COL_LIST_ITEM>
          </COL_LIST>
          <PRIMARY_KEY_CONSTRAINT_LIST>
             <PRIMARY_KEY_CONSTRAINT_LIST_ITEM>
                <COL_LIST>
                   <COL_LIST_ITEM>
                      <NAME>EMPNO</NAME>
                   </COL_LIST_ITEM>
                </COL_LIST>
             </PRIMARY_KEY_CONSTRAINT_LIST_ITEM>
          </PRIMARY_KEY_CONSTRAINT_LIST>
       </RELATIONAL_TABLE>
    </TABLE> 
     
    1 row selected.
     
    
  4. Compare the results using the DBMS_METADATA browsing APIs:

    SQL> SELECT dbms_metadata.get_sxml('TABLE','TAB1') FROM dual;
    SQL> SELECT dbms_metadata.get_sxml('TABLE','TAB2') FROM dual;
    
  5. Create a function using the DBMS_METADATA_DIFF API to compare the metadata for the two tables. In this function, the get_table_sxml function that was just defined in step 2 is used.

    SQL> CREATE OR REPLACE FUNCTION compare_table_sxml(name1 IN VARCHAR2,
      2                                          name2 IN VARCHAR2) RETURN CLOB IS
      3   doc1 CLOB;
      4   doc2 CLOB;
      5   diffdoc CLOB;
      6   openc_handle NUMBER;
      7  BEGIN
      8   --
      9   -- Fetch the SXML for the two tables
     10   --
     11   doc1 := get_table_sxml(name1);
     12   doc2 := get_table_sxml(name2);
     13   --
     14   -- Specify the object type in the OPENC call
     15   --
     16   openc_handle := DBMS_METADATA_DIFF.OPENC('TABLE');
     17   --
     18   -- Add each document
     19   --
     20   DBMS_METADATA_DIFF.ADD_DOCUMENT(openc_handle,doc1);
     21   DBMS_METADATA_DIFF.ADD_DOCUMENT(openc_handle,doc2);
     22   --
     23   -- Fetch the SXML difference document
     24   --
     25   diffdoc := DBMS_METADATA_DIFF.FETCH_CLOB(openc_handle);
     26   DBMS_METADATA_DIFF.CLOSE(openc_handle);
     27   RETURN diffdoc;
     28  END;
     29  /
     
    Function created.
    
  6. Use the function to fetch the SXML difference document for the two tables:

    SQL> SELECT compare_table_sxml('TAB1','TAB2') FROM dual;
    
    <TABLE xmlns="http://xmlns.oracle.com/ku" version="1.0">
      <SCHEMA>SCOTT</SCHEMA>
      <NAME value1="TAB1">TAB2</NAME>
      <RELATIONAL_TABLE>
        <COL_LIST>
          <COL_LIST_ITEM>
            <NAME>EMPNO</NAME>
            <DATATYPE>NUMBER</DATATYPE>
            <PRECISION>4</PRECISION>
            <SCALE>0</SCALE>
          </COL_LIST_ITEM>
          <COL_LIST_ITEM>
            <NAME>ENAME</NAME>
            <DATATYPE>VARCHAR2</DATATYPE>
            <LENGTH value1="10">20</LENGTH>
          </COL_LIST_ITEM>
          <COL_LIST_ITEM src="1">
            <NAME>JOB</NAME>
            <DATATYPE>VARCHAR2</DATATYPE>
            <LENGTH>9</LENGTH>
          </COL_LIST_ITEM>
          <COL_LIST_ITEM>
            <NAME>DEPTNO</NAME>
            <DATATYPE>NUMBER</DATATYPE>
            <PRECISION>2</PRECISION>
            <SCALE>0</SCALE>
          </COL_LIST_ITEM>
          <COL_LIST_ITEM src="2">
            <NAME>MGR</NAME>
            <DATATYPE>NUMBER</DATATYPE>
            <PRECISION>4</PRECISION>
            <SCALE>0</SCALE>
          </COL_LIST_ITEM>
        </COL_LIST>
        <PRIMARY_KEY_CONSTRAINT_LIST src="2">
          <PRIMARY_KEY_CONSTRAINT_LIST_ITEM>
            <COL_LIST>
              <COL_LIST_ITEM>
                <NAME>EMPNO</NAME>
              </COL_LIST_ITEM>
            </COL_LIST>
          </PRIMARY_KEY_CONSTRAINT_LIST_ITEM>
        </PRIMARY_KEY_CONSTRAINT_LIST>
      </RELATIONAL_TABLE>
    </TABLE>
     
    1 row selected.
     
    

    The SXML difference document shows the union of the two SXML documents, with the XML attributes value1 and src identifying the differences. When an element exists in only one document it is marked with src. Thus, <COL_LIST_ITEM src="1"> means that this element is in the first document (TAB1) but not in the second. When an element is present in both documents but with different values, the element's value is the value in the second document and the value1 gives its value in the first. For example, <LENGTH value1="10">20</LENGTH> means that the length is 10 in TAB1 (the first document) and 20 in TAB2.

  7. Compare the result using the DBMS_METADATA_DIFF browsing APIs:

    SQL> SELECT dbms_metadata_diff.compare_sxml('TABLE','TAB1','TAB2') FROM dual;
    
  8. Create a function using the DBMS_METADATA.CONVERT API to generate an ALTERXML document. This is an XML document containing ALTER statements to make one object like another. You can also use parse items to get information about the individual ALTER statements. (This example uses the functions defined thus far.)

    SQL> CREATE OR REPLACE FUNCTION get_table_alterxml(name1 IN VARCHAR2,
      2                                           name2 IN VARCHAR2) RETURN CLOB IS
      3   diffdoc CLOB;
      4   openw_handle NUMBER;
      5   transform_handle NUMBER;
      6   alterxml CLOB;
      7  BEGIN
      8   --
      9   -- Use the function just defined to get the difference document
     10   --
     11   diffdoc := compare_table_sxml(name1,name2);
     12   --
     13   -- Specify the object type in the OPENW call
     14   --
     15   openw_handle := DBMS_METADATA.OPENW('TABLE');
     16   --
     17   -- Use the ALTERXML transform to generate the ALTER_XML document
     18   --
     19   transform_handle := DBMS_METADATA.ADD_TRANSFORM(openw_handle,'ALTERXML');
     20   --
     21   -- Request parse items
     22   --
     23   DBMS_METADATA.SET_PARSE_ITEM(openw_handle,'CLAUSE_TYPE');
     24   DBMS_METADATA.SET_PARSE_ITEM(openw_handle,'NAME');
     25   DBMS_METADATA.SET_PARSE_ITEM(openw_handle,'COLUMN_ATTRIBUTE');
     26   --
     27   -- Create a temporary LOB
     28   --
     29   DBMS_LOB.CREATETEMPORARY(alterxml, TRUE );
     30   --
     31   -- Call CONVERT to do the transform
     32   --
     33   DBMS_METADATA.CONVERT(openw_handle,diffdoc,alterxml);
     34   --
     35   -- Close context and return the result
     36   --
     37   DBMS_METADATA.CLOSE(openw_handle);
     38   RETURN alterxml;
     39  END;
     40  /
     
    Function created.
    
  9. Use the function to fetch the ALTER_XML document:

    SQL> SELECT get_table_alterxml('TAB1','TAB2') FROM dual;
     
    <ALTER_XML xmlns="http://xmlns.oracle.com/ku" version="1.0">
       <OBJECT_TYPE>TABLE</OBJECT_TYPE>
       <OBJECT1>
          <SCHEMA>SCOTT</SCHEMA>
          <NAME>TAB1</NAME>
       </OBJECT1>
       <OBJECT2>
          <SCHEMA>SCOTT</SCHEMA>
          <NAME>TAB2</NAME>
       </OBJECT2>
       <ALTER_LIST>
          <ALTER_LIST_ITEM>
             <PARSE_LIST>
                <PARSE_LIST_ITEM>
                   <ITEM>NAME</ITEM>
                   <VALUE>MGR</VALUE>
                </PARSE_LIST_ITEM>
                <PARSE_LIST_ITEM>
                   <ITEM>CLAUSE_TYPE</ITEM>
                   <VALUE>ADD_COLUMN</VALUE>
                </PARSE_LIST_ITEM>
             </PARSE_LIST>
             <SQL_LIST>
                <SQL_LIST_ITEM>
                   <TEXT>ALTER TABLE "SCOTT"."TAB1" ADD ("MGR" NUMBER(4,0))</TEXT>
                </SQL_LIST_ITEM>
             </SQL_LIST>
          </ALTER_LIST_ITEM>
          <ALTER_LIST_ITEM>
             <PARSE_LIST>
                <PARSE_LIST_ITEM>
                   <ITEM>NAME</ITEM>
                   <VALUE>JOB</VALUE>
                </PARSE_LIST_ITEM>
                <PARSE_LIST_ITEM>
                   <ITEM>CLAUSE_TYPE</ITEM>
                   <VALUE>DROP_COLUMN</VALUE>
                </PARSE_LIST_ITEM>
             </PARSE_LIST>
             <SQL_LIST>
                <SQL_LIST_ITEM>
                   <TEXT>ALTER TABLE "SCOTT"."TAB1" DROP ("JOB")</TEXT>
                </SQL_LIST_ITEM>
             </SQL_LIST>
          </ALTER_LIST_ITEM>
          <ALTER_LIST_ITEM>
             <PARSE_LIST>
                <PARSE_LIST_ITEM>
                   <ITEM>NAME</ITEM>
                   <VALUE>ENAME</VALUE>
                </PARSE_LIST_ITEM>
                <PARSE_LIST_ITEM>
                   <ITEM>CLAUSE_TYPE</ITEM>
                   <VALUE>MODIFY_COLUMN</VALUE>
                </PARSE_LIST_ITEM>
                <PARSE_LIST_ITEM>
                   <ITEM>COLUMN_ATTRIBUTE</ITEM>
                   <VALUE> SIZE_INCREASE</VALUE>
                </PARSE_LIST_ITEM>
             </PARSE_LIST>
             <SQL_LIST>
                <SQL_LIST_ITEM>
                   <TEXT>ALTER TABLE "SCOTT"."TAB1" MODIFY 
                        ("ENAME" VARCHAR2(20))
                   </TEXT>
                </SQL_LIST_ITEM>
             </SQL_LIST>
          </ALTER_LIST_ITEM>
          <ALTER_LIST_ITEM>
             <PARSE_LIST>
                <PARSE_LIST_ITEM>
                   <ITEM>CLAUSE_TYPE</ITEM>
                   <VALUE>ADD_CONSTRAINT</VALUE>
                </PARSE_LIST_ITEM>
             </PARSE_LIST>
             <SQL_LIST>
                <SQL_LIST_ITEM>
                   <TEXT>ALTER TABLE "SCOTT"."TAB1" ADD  PRIMARY KEY
                         ("EMPNO") ENABLE
                   </TEXT>
                </SQL_LIST_ITEM>
             </SQL_LIST>
          </ALTER_LIST_ITEM>
          <ALTER_LIST_ITEM>
             <PARSE_LIST>
                <PARSE_LIST_ITEM>
                   <ITEM>NAME</ITEM>
                   <VALUE>TAB1</VALUE>
                </PARSE_LIST_ITEM>
                <PARSE_LIST_ITEM>
                   <ITEM>CLAUSE_TYPE</ITEM>
                   <VALUE>RENAME_TABLE</VALUE>
                </PARSE_LIST_ITEM>
             </PARSE_LIST>
             <SQL_LIST>
                <SQL_LIST_ITEM>
                   <TEXT>ALTER TABLE "SCOTT"."TAB1" RENAME TO "TAB2"</TEXT>
                </SQL_LIST_ITEM>
             </SQL_LIST>
          </ALTER_LIST_ITEM>
       </ALTER_LIST>
    </ALTER_XML>
     
     
    1 row selected.
     
    
  10. Compare the result using the DBMS_METADATA_DIFF browsing API:

    SQL> SELECT dbms_metadata_diff.compare_alter_xml('TABLE','TAB1','TAB2') FROM dual;
    
  11. The ALTER_XML document contains an ALTER_LIST of each of the alters. Each ALTER_LIST_ITEM has a PARSE_LIST containing the parse items as name-value pairs and a SQL_LIST containing the SQL for the particular alter. You can parse this document and decide which of the SQL statements to execute, using the information in the PARSE_LIST. (Note, for example, that in this case one of the alters is a DROP_COLUMN, and you might choose not to execute that.)

  12. Create one last function that uses the DBMS_METADATA.CONVERT API and the ALTER DDL transform to convert the ALTER_XML document into SQL DDL:

    SQL> CREATE OR REPLACE FUNCTION get_table_alterddl(name1 IN VARCHAR2,
      2                                           name2 IN VARCHAR2) RETURN CLOB IS
      3   alterxml CLOB;
      4   openw_handle NUMBER;
      5   transform_handle NUMBER;
      6   alterddl CLOB;
      7  BEGIN
      8   --
      9   -- Use the function just defined to get the ALTER_XML document
     10   --
     11   alterxml := get_table_alterxml(name1,name2);
     12   --
     13   -- Specify the object type in the OPENW call
     14   --
     15   openw_handle := DBMS_METADATA.OPENW('TABLE');
     16   --
     17   -- Use ALTERDDL transform to convert the ALTER_XML document to SQL DDL
     18   -- 
     19   transform_handle := DBMS_METADATA.ADD_TRANSFORM(openw_handle,'ALTERDDL');
     20   --
     21   -- Use the SQLTERMINATOR transform parameter to append a terminator
     22   -- to each SQL statement
     23   --
     24   DBMS_METADATA.SET_TRANSFORM_PARAM(transform_handle,'SQLTERMINATOR',true);
     25   --
     26   -- Create a temporary lob
     27   --
     28   DBMS_LOB.CREATETEMPORARY(alterddl, TRUE );
     29   --
     30   -- Call CONVERT to do the transform
     31   --
     32   DBMS_METADATA.CONVERT(openw_handle,alterxml,alterddl);
     33   --
     34   -- Close context and return the result
     35   --
     36   DBMS_METADATA.CLOSE(openw_handle);
     37   RETURN alterddl;
     38  END;
     39  /
     
    Function created.
     
    
  13. Use the function to fetch the SQL ALTER statements:

    SQL> SELECT get_table_alterddl('TAB1','TAB2') FROM dual;
    ALTER TABLE "SCOTT"."TAB1" ADD ("MGR" NUMBER(4,0))
    /
      ALTER TABLE "SCOTT"."TAB1" DROP ("JOB")
    /
      ALTER TABLE "SCOTT"."TAB1" MODIFY ("ENAME" VARCHAR2(20))
    /
      ALTER TABLE "SCOTT"."TAB1" ADD  PRIMARY KEY ("EMPNO") ENABLE
    /
      ALTER TABLE "SCOTT"."TAB1" RENAME TO "TAB2"
    /
      
    1 row selected.
     
    
  14. Compare the results using the DBMS_METADATA_DIFF browsing API:

    SQL> SELECT dbms_metadata_diff.compare_alter('TABLE','TAB1','TAB2') FROM dual;
    ALTER TABLE "SCOTT"."TAB1" ADD ("MGR" NUMBER(4,0))
      ALTER TABLE "SCOTT"."TAB1" DROP ("JOB")
      ALTER TABLE "SCOTT"."TAB1" MODIFY ("ENAME" VARCHAR2(20))
      ALTER TABLE "SCOTT"."TAB1" ADD  PRIMARY KEY ("EMPNO") USING INDEX 
      PCTFREE 10 INITRANS 2 STORAGE ( INITIAL 16384 NEXT 16384 MINEXTENTS 1
      MAXEXTENTS 505 PCTINCREASE 50 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL
      DEFAULT)  ENABLE ALTER TABLE "SCOTT"."TAB1" RENAME TO "TAB2"
     
    1 row selected.