Drop Types Recursively
As covered in my new Oracle Database 12c PL/SQL Programming book (publisher’s satisfied), you can evolve object types. That means you can change a base object type and the change cascades through dependents. Somebody asked how to remove an object type chain without appending the FORCE
clause.
It’s quite easy if you understand writing a recursive function in PL/SQL, as done here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | -- Create a recursive function. CREATE OR REPLACE FUNCTION drop_dependents (pv_base_object_type VARCHAR2) RETURN NUMBER IS /* Declare a return data type. */ lv_retval NUMBER := 0; /* Declare item type. */ lv_type_name VARCHAR2(30); lv_object_name VARCHAR2(30); /* The first part of the cursor finds the dependent type names of complex object types, and the second part of the cursor finds the dependent collection types. Effectively the set operator finds two distinct branches because you may use any base type as an element of a complex object or of a collection. */ CURSOR base_type (cv_base_type VARCHAR2) IS SELECT uta.type_name , NULL AS object_name FROM user_type_attrs uta INNER JOIN user_types ut ON uta.attr_type_name = ut.type_name WHERE ut.type_name = cv_base_type UNION ALL SELECT uct.type_name , NULL AS object_name FROM user_types ut INNER JOIN user_coll_types uct ON ut.type_name = uct.elem_type_name WHERE uct.elem_type_name = cv_base_type UNION ALL SELECT CASE WHEN package_name IS NULL THEN uo.object_type ELSE 'PACKAGE' END AS type_name , CASE WHEN package_name IS NULL THEN ua.object_name ELSE ua.package_name END AS object_name FROM user_arguments ua LEFT JOIN user_objects uo ON ua.package_name = uo.object_name OR ua.object_name = uo.object_name WHERE type_name = cv_base_type; BEGIN /* Open a parameterized cursor. */ OPEN base_type(pv_base_object_type); /* Loop through return records. */ LOOP /* Fetch records. */ FETCH base_type INTO lv_type_name , lv_object_name; /* Drop type without dependents, or drop leaf node dependent. */ IF base_type%NOTFOUND THEN /* Drop functions when they include an object type or object type dependent as a formal parameter type or return type. Drop procedures when they include an object type or object type dependent. Drop procedures when any function or procedure uses an object type or object type dependent. */ IF lv_type_name IN ('FUNCTION','PACKAGE','PROCEDURE') THEN /* Drop the base type when no dependents are found. */ EXECUTE IMMEDIATE 'DROP '||lv_type_name||' '||lv_object_name; ELSE /* Drop the base type when no dependents are found. */ EXECUTE IMMEDIATE 'DROP TYPE '||pv_base_object_type; END IF; /* Set exit state to one or true. */ lv_retval := 1; /* Exit the loop. */ EXIT; ELSE /* A type must exclude function, package, and procedure; and the object name must be null before you recurse to another level. */ IF lv_type_name NOT IN ('FUNCTION','PACKAGE','PROCEDURE') AND lv_object_name IS NOT NULL THEN /* Drop base type when no dependents are found. */ lv_retval := drop_dependents(lv_type_name); END IF; END IF; END LOOP; /* Close open cursor. */ CLOSE base_type; /* Return 0 for false. */ RETURN lv_retval; END; / |
Somebody asked me to provide a test case of a hierarchy of object types to support the drop_dependents
function. So, here’s the test case code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | -- Create object type. CREATE OR REPLACE TYPE item_object IS OBJECT ( item_name VARCHAR2(30) , item_subname VARCHAR2(30)); / -- Create object type. CREATE OR REPLACE TYPE identified_object IS OBJECT ( identified_id NUMBER , identified_object item_object); / -- Create object collection. CREATE OR REPLACE TYPE item_table IS TABLE OF item_object; / -- Create object collection. CREATE OR REPLACE TYPE item_varray IS VARRAY(5) OF item_object; / -- Create object type. CREATE OR REPLACE TYPE item_async_table IS OBJECT ( item_name VARCHAR2(30) , item_collection item_table); / -- Create object type. CREATE OR REPLACE TYPE item_async_varray IS OBJECT ( item_name VARCHAR2(30) , item_collection item_varray); / -- Create object collection. CREATE OR REPLACE TYPE item_list IS TABLE OF item_async_table; / -- Create object collection. CREATE OR REPLACE TYPE item_array IS VARRAY(10) OF item_async_varray; / -- Create package specification. CREATE OR REPLACE PACKAGE item_package AS /* A published function of the package. */ FUNCTION initialize_object ( id NUMBER , name NUMBER ) RETURN ITEM_OBJECT; END item_package; / -- Create a schema function. CREATE OR REPLACE FUNCTION get_item_object ( pv_id NUMBER , pv_name NUMBER ) RETURN ITEM_OBJECT IS /* Declare a local variable. */ lv_item_object ITEM_OBJECT; BEGIN /* Initialize the object type. */ lv_item_object := item_object(pv_id, pv_name); /* Return the dat type. */ RETURN lv_item_object; END; / |
If you call the function with the base type, it’ll drop the most dependent object type first, and the base object type last. The rest are dropped in their order of dependency. You can call a drop_dependents
function with a base type, like ITEM_OBJECT
, by using the following syntax:
1 2 3 4 5 6 7 | SET SERVEROUTPUT ON SIZE UNLIMITED BEGIN IF drop_dependents('ITEM_OBJECT') = 1 THEN dbms_output.put_line('Objects dropped.'); END IF; END; / |
Hope this helps those looking to drop a chain of object types in an Oracle database.