Larry Steinle

August 23, 2013

Programmatically Validating Oracle Packages

Filed under: Oracle — Larry Steinle @ 10:06 pm
Tags: , ,

Whenever an Oracle view, function, procedure or package header is modified the object is invalidated and any objects that call the invalidated object become invalid. To avoid errors these objects need to be compiled. The purpose of this article is to demonstrate how to identify the invalid objects compiling them in the correct order so that all objects can be validated in one pass while providing the ability to track which objects fail to validate.

The following tree demonstrates how one Oracle object calls another Oracle object creating relationship dependencies between the objects.

World.func
–> Travel.proc
–> –> MeetPeople.proc
OtherFunk.func

If MeetPeople.proc becomes invalidated then so does Travel.proc and World.func. In order to compile these objects they must be compiled from the child leaf node up to the root node.

What’s Invalid?

We can query SYS.DBA_Objects (or SYS.All_Objects depending on your permissions) to identify which objects are no longer valid:

SELECT Object_Id, Owner, Object_Type, Object_Name, Status
  FROM SYS.DBA_Objects
 WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM')
   AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')
   AND UPPER(DBA_Objects.Status) <> 'VALID';

While this returns the list of objects that need to be recompiled we still don’t know what order to compile. If we attempt to compile World.func before the child objects have been compiled it will fail. We have to compile in reverse call order.

Object Dependencies

Any object that calls another object to perform a given task is said to be dependent upon the other object. Oracle tracks these relationships in the SYS.Public_Dependency table. Between the tables SYS.Public_Dependency and SYS.DBA_Objects we are able to reconstruct the correct call order to compile the objects.

The following query demonstrates how to get the complete order call. This query also manages recursive calls where object a calls object b which calls object a creating an unending relationship loop.

SELECT Owner,
       Object_Type,
       Object_Name,
       1 AS Call_Level
  FROM SYS.DBA_Objects
 WHERE UPPER(DBA_Objects.Status) <> 'VALID'
 UNION
SELECT Call_Chain.Dependent_Owner,
       Call_Chain.Dependent_Type,
       Call_Chain.Dependent_Name,
       Call_Chain.Call_Level
  FROM (  --Construct call chain
          SELECT Relationships.Dependent_Id,
                 Relationships.Dependent_Owner,
                 Relationships.Dependent_Name,
                 Relationships.Dependent_Type,
                 Relationships.Dependent_Status,
                 MAX(LEVEL) AS Call_Level
            FROM (  --Get object parent-child relationships
                    SELECT DBA_Objects.Object_Id,
                           DBA_Objects.Owner,
                           DBA_Objects.Object_Type,
                           DBA_Objects.Object_Name,
                           DBA_Objects.Status,
                           Dependency.Object_Id AS Dependent_Id,
                           Dependency.Owner AS Dependent_Owner,
                           Dependency.Object_Type AS Dependent_Type,
                           Dependency.Object_Name AS Dependent_Name,
                           Dependency.Status AS Dependent_Status
                      FROM (SELECT Object_Id, Owner, Object_Type, Object_Name, Status FROM SYS.DBA_Objects WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM') AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')) DBA_Objects
                      JOIN SYS.Public_Dependency
                        ON Public_Dependency.Object_Id = DBA_Objects.Object_Id AND DBA_Objects.Status <> 'VALID'
                      JOIN (SELECT Object_Id, Owner, Object_Type, Object_Name, Status FROM SYS.DBA_Objects WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM') AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')) Dependency_Key
                        ON Dependency_Key.Object_Id = Public_Dependency.Referenced_Object_Id
                      JOIN (SELECT Object_Id, Owner, Object_Type, Object_Name, Status FROM SYS.DBA_Objects WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM') AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')) Dependency
                        ON UPPER(Dependency.Object_Name) = UPPER(Dependency_Key.Object_Name)
                       AND Dependency_Key.Object_Id <> DBA_Objects.Object_Id
                 ) Relationships
          CONNECT BY PRIOR Relationships.Object_Id = Relationships.Dependent_Id
                 AND Relationships.Dependent_Id > PRIOR Relationships.Dependent_Id
          GROUP BY Relationships.Dependent_Id,
                 Relationships.Dependent_Owner,
                 Relationships.Dependent_Name,
                 Relationships.Dependent_Type,
                 Relationships.Dependent_Status
       ) Call_Chain
 WHERE UPPER(Call_Chain.Dependent_Status) <> 'VALID'
 ORDER BY Call_Level DESC, Object_Name ASC;

Compiling the Invalid Objects

At this point we can iterate thru each record calling the appropriate SQL command to compile the object:

BEGIN
  FOR Invalid_Object_Rec IN (
    SELECT Owner,
           Object_Type,
           Object_Name,
           1 AS Call_Level
      FROM SYS.DBA_Objects
     WHERE UPPER(DBA_Objects.Status) <> 'VALID'
     UNION
    SELECT Call_Chain.Dependent_Owner,
           Call_Chain.Dependent_Type,
           Call_Chain.Dependent_Name,
           Call_Chain.Call_Level
      FROM (  --Construct call chain
              SELECT Relationships.Dependent_Id,
                     Relationships.Dependent_Owner,
                     Relationships.Dependent_Name,
                     Relationships.Dependent_Type,
                     Relationships.Dependent_Status,
                     MAX(LEVEL) AS Call_Level
                FROM (  --Get object parent-child relationships
                        SELECT DBA_Objects.Object_Id,
                               DBA_Objects.Owner,
                               DBA_Objects.Object_Type,
                               DBA_Objects.Object_Name,
                               DBA_Objects.Status,
                               Dependency.Object_Id AS Dependent_Id,
                               Dependency.Owner AS Dependent_Owner,
                               Dependency.Object_Type AS Dependent_Type,
                               Dependency.Object_Name AS Dependent_Name,
                               Dependency.Status AS Dependent_Status
                          FROM (
                                 SELECT Object_Id,
                                        Owner,
                                        Object_Type,
                                        Object_Name,
                                        Status
                                   FROM SYS.DBA_Objects
                                  WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM')
                                    AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')
                               ) DBA_Objects
                          JOIN SYS.Public_Dependency
                            ON Public_Dependency.Object_Id = DBA_Objects.Object_Id
                           AND DBA_Objects.Status <> 'VALID'
                          JOIN (
                                 SELECT Object_Id,
                                        Owner,
                                        Object_Type,
                                        Object_Name,
                                        Status
                                   FROM SYS.DBA_Objects
                                  WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM')
                                    AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')
                               ) Dependency_Key
                            ON Dependency_Key.Object_Id = Public_Dependency.Referenced_Object_Id
                          JOIN (
                                 SELECT Object_Id,
                                        Owner,
                                        Object_Type,
                                        Object_Name,
                                        Status
                                   FROM SYS.DBA_Objects
                                  WHERE DBA_Objects.Owner NOT IN ('SYS', 'SYSTEM')
                                    AND UPPER(DBA_Objects.Object_Type) IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'TYPE', 'TYPE BODY', 'VIEW')
                               ) Dependency
                            ON UPPER(Dependency.Object_Name) = UPPER(Dependency_Key.Object_Name)
                           AND Dependency_Key.Object_Id <> DBA_Objects.Object_Id
                     ) Relationships
            CONNECT BY PRIOR Relationships.Object_Id = Relationships.Dependent_Id
                   AND Relationships.Dependent_Id > PRIOR Relationships.Dependent_Id
              GROUP BY Relationships.Dependent_Id,
                       Relationships.Dependent_Owner,
                       Relationships.Dependent_Name,
                       Relationships.Dependent_Type,
                       Relationships.Dependent_Status
           ) Call_Chain
       WHERE UPPER(Call_Chain.Dependent_Status) <> 'VALID'
    ORDER BY Call_Level DESC, Object_Name ASC
  ) LOOP
    BEGIN
      IF UPPER(Invalid_Object_Rec.Object_Type) IN ('TYPE BODY', 'PACKAGE BODY') THEN
        EXECUTE IMMEDIATE 'ALTER ' || Invalid_Object_Rec.Object_Type || ' "' || Invalid_Object_Rec.Owner || '"."' || Invalid_Object_Rec.Name || '" COMPILE BODY';
      ELSE
        EXECUTE IMMEDIATE 'ALTER ' || Invalid_Object_Rec.Object_Type || ' "' || Invalid_Object_Rec.Owner || '"."' || Invalid_Object_Rec.Name || '" COMPILE';
      END IF;
    EXCEPTION WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE("Failed to compile " || Invalid_Object_Rec.Owner || '.' || Invalid_Object_Rec.Object_Name || ' (' || Invalid_Object_Rec.Object_Type || ')';
    END;
  END LOOP;
END;

Summary

In this article we saw how to build an Oracle Compiler using Oracle SQL code. This could just as easily have been done from a Java or VS.Net application. Regardless, with a single pass we have a validated Oracle database.

Advertisement

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: