Tải bản đầy đủ
PLVobj: A Packaged Interface to ALL_OBJECTS

PLVobj: A Packaged Interface to ALL_OBJECTS

Tải bản đầy đủ

[Appendix A] Appendix: PL/SQL Exercises
Accept a string from the user that specifies the program unit he wants the package to work with.
Convert it to the owner−name−type information I need to use when working with the data dictionary
views.

Fetch rows from one or more data dictionary views based on the program unit specified.
I would like to be able to say that as I began writing my first source−related utility I instantly recognized the
need to create a package like PLVobj. The truth is that my first read of the situation was that it was very easy
to define a cursor against USER_OBJECTS and get what I needed for my package. So I just started hacking
away. I built the first version of my program and got it working. And then I started on my next utility.
Suddenly I was confronted with having to write the same (or very similar) kind of code again. I was troubled
by the redundancy. Still, it was pretty simple stuff, so I went ahead with the duplication of code. I got that
second utility to work as well. Then I sent the packages to one of my devoted beta testers. He installed them in
a networked environment under a common user and told his developers to try them out.
Neither utility worked. At all. It didn't take too long to figure out why. In my own, intimate development and
testing environment, everything existed in the same Oracle account. In the beta environment the utilities were
installed in a single account and then shared by all. My naive reliance on the USER_OBJECTS data
dictionary view doomed the utilities. I needed instead to use the ALL_OBJECTS view. This meant that I also
needed to provide a schema or owner to the cursor. Suddenly I had to perform less−than−trivial enhancements
to two different programs.
At this point, I came to my senses. I needed to consolidate all of this logic, all code relating to the objects data
dictionary view, into a single location −− a package. I could not afford, in terms of productivity and code
quality, to have code redundancy. As you begin to use new data structures or develop a new technique the first
time, it is sometimes difficult to justify cleaving off the code to its own repository or package. When you get
to needing it the second time, however, there should be no excuses. Avoid with fanatical determination any
redundancies in your application code.
And so PLVobj was born. Of course, the version I share with you is very different from the first, second,
third, and fourth versions of the package. Believe me, it has changed a lot over a four−month period. I seem to
come across new complexities every week. (For example, a module name is not always in upper case; you can
create program units whose names have lowercase letters if you enclose the name in double quotes.)
The PLVobj package offers functionality in several areas:

Set and view the "current object" maintained inside the package.

Access a cursor into the ALL_OBJECTS view, providing a full range of cursor−based functionality
through PL/SQL procedures and functions.

Trace the actions of the package.
The elements available in PLVobj are described in the following sections. Before diving into the programs,
however, let's review the ALL_OBJECTS view.

10.3 PLVprsps: Parsing
PL/SQL Strings

11.2 ALL_OBJECTS View

11. PLVobj: A Packaged Interface to ALL_OBJECTS

327

[Appendix A] Appendix: PL/SQL Exercises

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

11. PLVobj: A Packaged Interface to ALL_OBJECTS

328

Chapter 11
PLVobj: A Packaged
Interface to
ALL_OBJECTS

11.2 ALL_OBJECTS View
The main objective of PLVobj is to provide a programmatic interface to the ALL_OBJECTS view. This data
dictionary view has the following columns:
Name
−−−−−−−−−−−−−−−−
OWNER
OBJECT_NAME
OBJECT_ID
OBJECT_TYPE
CREATED
LAST_DDL_TIME
TIMESTAMP
STATUS

Null?
−−−−−−−−
NOT NULL
NOT NULL
NOT NULL

Type
−−−−
VARCHAR2(30)
VARCHAR2(30)
NUMBER
VARCHAR2(12)
NOT NULL DATE
NOT NULL DATE
VARCHAR2(75)
VARCHAR2(7)

It contains a row for every stored code object to which you have execute access (the USER_OBJECTS view
contains a row for each stored code object you created). The OWNER is the schema that owns the program
unit.
object_name
The name of the object.
object_type
The type of the object, which is one of the following: package, package body, procedure, function, or
synonym.
object_id
An internal pointer used to quickly obtain related information about the object in other data dictionary
views.
last_ddl_time
Stores the date and timestamp when this object was last compiled into the database.
Status column
Either VALID or INVALID. This column is set by the Oracle Server as it maintains dependencies and
performs compiles.
Without a package like PLVobj, every time you wanted to read from this view, you would need to write a
SELECT statement in SQL*Plus or, in PL/SQL, define a cursor, and then do the "cursor thing." You would
need to understand the complexities of the ALL_OBJECTS view (for example, all names are uppercased
unless you created the object with double quotes around the name). You would also need to know how to
parse a program name into its full set of components: owner, name, and type.
If several developers in your organization need to do the same thing, you soon have a situation where the
same kind of query and similar code is "hard−coded" across your application or utilities. Lots of hours are
329

[Appendix A] Appendix: PL/SQL Exercises
wasted and the resources required for maintenance of the application multiply.
A better solution is to write the cursor once −− in a package, of course −− and then let all developers
reference that cursor. They each do their own opens, fetches, and closes, but the SQL (just about the most
volatile part of one's application) is shared. An even better solution, however, is to hide the cursor in the body
of the package and build a complete programmatic interface to the cursor. With this approach, you build
procedures and functions that perform the cursor operations; a user of the package never has to call native
PL/SQL cursor operations. This is the approach I have taken with PLVobj and described in the following
sections.

11.2.1 Cursor Into ALL_OBJECTS
At the heart of PLVobj (in the package body) is a cursor against the ALL_OBJECTS view. The cursor is
defined as follows:
CURSOR obj_cur
IS
SELECT owner, object_name, object_type, status
FROM ALL_OBJECTS
WHERE object_name LIKE v_currname
AND object_type LIKE v_currtype
AND owner LIKE v_currschema
ORDER BY owner,
DECODE (object_type,
'PACKAGE', 1,
'PACKAGE BODY', 2,
'PROCEDURE', 3,
'FUNCTION', 4,
'TRIGGER', 5,
6),
object_name;

Notice that the cursor contains references to three package variables: v_currschema, v_currname, and
v_currtype. These three variables make up the current object of PLVobj and are discussed in more detail
later in this chapter.
Notice that I use the LIKE operator so that you can retrieve multiple objects from a single schema, or even
multiple schemas. Furthermore, since my PL/SQL development is PL/SQL and package−centric, I use a
DECODE statement to bring those up first in the ordering system.
Again, since this cursor is defined in the package body, users of PLVobj cannot directly OPEN, fetch from,
or CLOSE the cursor. All of these actions must be taken through procedures which are described in the next
section.

11.1 Why PLVobj?

11.3 Setting the Current
Object

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

11.2.1 Cursor Into ALL_OBJECTS

330

Chapter 11
PLVobj: A Packaged
Interface to
ALL_OBJECTS

11.3 Setting the Current Object
PLVobj provides a number of different programs to set and change the current object of PLVobj. The
current object of PLVobj is defined by three private package variables:
v_currschema
The owner of the object(s).
v_currname
The name of the object(s). The name can be wildcarded, by including % in the name specified.
v_currtype
The type of the object(s). The type can be wildcarded as well, again by including % in the type
specified.
Since the above elements are private variables, a user of PLVobj will never see or reference these variables
directly. Instead, I provide the following set of procedures and functions to maintain these variables (I call this
layer of code which surrounds variables "get and set" routines): setcurr, set_name, set_type, and
set_schema.
The setcurr program calls the other set programs. Its header is:
PROCEDURE setcurr
(name_in IN VARCHAR2, type_in IN VARCHAR2 := NULL);

The first argument is the module name, the second, optional argument is the module type. While the first
argument is called a "name" argument, you can actually in this single argument supply the name, type, and
schema. PLVobj makes it as easy as possible to supply this information. All of the following formats for the
name_in argument are acceptable:
name
An unqualified identifier. In this case, PLVobj determines the type of the object from the data
dictionary. This type will be unambiguous if name refers to a procedure or function. If a package, then
the type could be either PACKAGE or PACKAGE BODY. PLVobj defaults to PACKAGE.
schema.name
The name of the object is qualified by the schema name. You will need to specify the schema name if
you want to convert the case of a program owned by another user.
type:name
Both the type and the name are specified, separated by a colon. Valid type strings for each type of
program unit are shown below.
type:schema.name
331

[Appendix A] Appendix: PL/SQL Exercises
In this case, the user has specified all three elements of a program unit: the type of the program, the
owner, and the name.
In fact, the second argument is optional because PLVobj allows you to concatenate the type onto the name
argument.
The following table shows different ways of specifying programs and the resulting PLVobj current object
values. In this table, pkg is the name of a package, func the name of a function, and proc the name of a
procedure. The setcurr program is executed from the PLV account.
Call to setcurr

Schema Program Type

Program Name

PLVobj.setcurr ('proc');

PLV

PROCEDURE

proc

PLVobj.setcurr ('func');

PLV

FUNCTION

func

PLVobj.setcurr ('pkg');

PLV

PACKAGE

pkg

PLVobj.setcurr ('b:pkg');

PLV

PACKAGE BODY

pkg

PLVobj.setcurr ('body:pkg');

PLV

PACKAGE BODY

pkg

PLVobj.setcurr ('s:SCOTT.empmaint');
SCOTT

PACKAGE

empmaint

PLVobj.setcurr ('%:plv%');

ALL TYPES

Like PLV%

PLV

PLVobj.setcurr ('s:scott.% ');

All present
SCOTT PACKAGE
The above table assumes, by the way, that the PLV account has execute authority on SCOTT's empmaint
package.

Notice that when I specify a function or procedure, I do not have to provide the type at all. There can only be
one object of a given name in a schema, and the object_type is therefore unambiguously set to
PROCEDURE. On the other hand, when I am working with packages, the situation is more ambiguous. A
package can have up to two objects: the specification and the body. So if you provide a package name but do
not supply a type, PLVobj will set the current type to PACKAGE. If you want to set the current object to a
package body, you must supply a valid object type or object type abbreviation.
Here are the valid options for object type:
Program Type

Valid Entries for Type in Call to setcurr

Package Specification S PS SPEC or SPECIFICATION
Package Body

B PB BODY or PACKAGE BODY

Procedure

P PROC or PROCEDURE

Function
F FUNC or FUNCTION
So I can set the current object to the PACKAGE BODY of the testcase package with any of the following
calls to setcurr:
PLVobj.setcurr
PLVobj.setcurr
PLVobj.setcurr
PLVobj.setcurr

('b:testcase');
('pb:testcase');
('body:testcase');
('package body:testcase');

NOTE: The setcurr program relies on DBMS_UTILITY.NAME_RESOLVE to uncover
all the components of a non−wildcarded object entry. This builtin only returns non−NULL
values for PL/SQL stored code elements. Consequently, you cannot use setcurr to set the
current object to non−PL/SQL elements such as tables and indexes. Instead, you will need to
call the individual set programs explored in the next section.
332

[Appendix A] Appendix: PL/SQL Exercises
You can use the PLVobj.setcurr program to convert your entry and set the current object accordingly. In
some cases, however, you may want simply to change the current object type. Or you may want to take
advantage of the parsing and conversion algorithms of PLVobj without actually changing the current object.
To provide this flexibility, PLVobj offers a number of additional programs which are explained below.

11.3.1 Setting Individual Elements of Current Object
You can change the current schema, name, or type independently with the set programs. You can also retrieve
the current object values with corresponding functions. The "get and set" programs are shown below:
PROCEDURE set_schema (schema_in IN VARCHAR2 := USER);
FUNCTION currschema RETURN VARCHAR2;
PROCEDURE set_type (type_in IN VARCHAR2);
FUNCTION currtype RETURN VARCHAR2;
PROCEDURE set_name (name_in IN VARCHAR2);
FUNCTION currname RETURN VARCHAR2;

I use these programs in the PLVvu.err procedure, which displays compile errors for the specified program.
The main body of err is shown below:
PLVobj.setcurr (name_in);
IF PLVobj.currtype = PLVobj.c_package AND
INSTR (name_in, ':') = 0
THEN
/* Show errors for package spec, then body. */
show_errors;
PLVobj.set_type (PLVobj.c_package_body);
show_errors (TRUE);
ELSE
show_errors (TRUE);
END IF;

Translation: I call the setcurr procedure to set the current object. Then I call currtype to see if the
program type is PACKAGE. If it is, I need to check to see if the developer has requested to see errors for just
the package specification or both specification and body. If both were requested (there is no colon in the
name, therefore no type specified), then I show errors for the specification, explicitly set the type to
PACKAGE BODY −− overriding the existing value −− and then show errors for the body.

11.3.2 Converting the Program Name
All the logic to convert a string into the separate components of schema, name, and type are handled by the
convobj program, whose header is shown below:
PROCEDURE convobj
(name_inout IN OUT VARCHAR2,
type_inout IN OUT VARCHAR2,
schema_inout IN OUT VARCHAR2);

All three arguments of convobj are IN OUT parameters, so that you can provide a value and also have a
value sent back for each of the components of an object.
The logic inside convobj is complex. If the name or type passed in to convobj contains a wildcard, those
wildcarded strings are returned (in their separate components) by the procedure. If, on the other hand, no
wildcards are present, convobj relies on the DBMS_UTILITY.NAME_RESOLVE builtin procedure to
automatically resolve the program name into its individual components.
NAME_RESOLVE resolves the specified object name into the owner or schema, first name, second name (if
11.3.1 Setting Individual Elements of Current Object

333

[Appendix A] Appendix: PL/SQL Exercises
in a package), program type, and database link if any. The program type is one of the following numbers:
5
(synonym)
7
(procedure)
8
(function)
9
(package)
NAME_RESOLVE is so useful because it automatically determines the appropriate schema of the named
element you pass in. Notice that NAME_RESOLVE does not distinguish between a package body and its
specification (they both have the same name, so that would be something of a challenge). The
nameres.sql script in the plvision\use subdirectory provides an easy way to call and see the results from
DBMS_UTILITY.NAME_RESOLVE.
You can call convobj when you want to convert a name to its different components without changing the
current object in the PLVobj package. I do this, for example, in the setcase.sql script, which provides an
easy−to−use frontend to PLVcase for case conversion of your code. I call PLVobj.convobj even before I
call any PLVcase programs because I want to display the program you have just requested for conversion.
This code is shown below:
PLVobj.convobj (modname, modtype, modschema);
modstring := modtype || ' ' || modname;
p.l ('=========================');
p.l ('PL/Vision Case Conversion');
p.l ('=========================');
p.l ('Converting ' || modstring || '..');

The PLVobj package also offers a convert_type procedure, which encapsulates the logic by which it
converts any number of different abbreviations and strings to a valid object type for the ALL_OBJECTS
view. The header for convert_type is as follows:
PROCEDURE convert_type (type_inout IN OUT VARCHAR2);

You pass it a string and it changes that screen to a valid type.

11.3.3 Displaying the Current Object
PLVobj provides the showcurr procedure to display the current object. Its header is:
PROCEDURE showcurr (show_header_in IN BOOLEAN := TRUE);

You can display the current object with a header (the default) or without, in which case you will simply see
the schema, name, and type (note that this full name is constructed with a call to the fullname function,
also available for your use). Some examples follow:
SQL> exec PLVobj.set_name ('PLVctlg');
SQL> exec PLVobj.set_type ('table);
SQL> exec PLVobj.showcurr; −− Displays a header
Schema.Name.Type
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
PLV.PLVCTLG.TABLE

11.3.3 Displaying the Current Object

334

[Appendix A] Appendix: PL/SQL Exercises
SQL> exec PLVobj.setcurr ('PLVio');
SQL> exec PLVobj.showcurr (FALSE); −− Suppresses header
PLV.PLVIO.PACKAGE

The showobj1.sql SQL*Plus script uses showcurr to display all the objects specified by your input.
This script is described below in the section called "Accessing ALL_OBJECTS."

11.3.4 Saving and Restoring the Current Object
PLVobj provides programs to both save the current object and restore it from the last save. The headers for
these programs are shown below:
PROCEDURE savecurr;
PROCEDURE restcurr;

You will want to use these programs if you are using PLVobj (in particular, the current object of PLVobj)
more than once in a given program execution. Suppose, for example, that you have nested loops. In the outer
loop, you call PLVobj.setcurr to scan through a set of program units. Inside the inner loop, you need to
use PLVobj.setcurr to change the focus of activity to another object. When you are done with the inner
loop execution, however, you will want to set the current object back to the outer loop values.
PL/Vision runs into this scenario because of the extensive work manipulating PL/SQL code objects.

11.2 ALL_OBJECTS View

11.4 Accessing
ALL_OBJECTS

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

11.3.4 Saving and Restoring the Current Object

335

Chapter 11
PLVobj: A Packaged
Interface to
ALL_OBJECTS

11.4 Accessing ALL_OBJECTS
Once you have set the current object in PLVobj (with either a call to setcurr or calls to the individual set
programs), you can open, fetch from, and close the PLVobj cursor.

11.4.1 Opening and Closing the PLVobj Cursor
To open the cursor, you call the open_objects procedure, defined as follows:
PROCEDURE open_objects;

This procedure first checks to see if the cursor is already open and, if not, takes that action. The
implementation of open_objects is shown below:
PROCEDURE open_objects IS
BEGIN
IF obj_cur%ISOPEN
THEN
NULL;
ELSE
OPEN obj_cur;
END IF;
END;

When you are done fetching from the cursor, you may close it with the following procedure:
PROCEDURE close_objects;

whose implementation makes sure that the cursor is actually open before attempting to close the cursor:
PROCEDURE close_objects IS
BEGIN
IF obj_cur%ISOPEN
THEN
CLOSE obj_cur;
END IF;
END;

11.4.2 Fetching from the PLVobj Cursor
Once the cursor is open, you will usually want to fetch rows from the result set. You do this with the
fetch_object procedure, which is overloaded as follows:
PROCEDURE fetch_object;
PROCEDURE fetch_object (name_out OUT VARCHAR2, type_out OUT VARCHAR2);

If you call fetch_objects without providing any OUT arguments, the name and type will be passed
directly into the current object variables, v_currname and v_currtype.
336

[Appendix A] Appendix: PL/SQL Exercises
If, on the other hand, you provide two return values in the call to fetch_object, the current object will
remain unchanged and you will be able to do what you want with the fetched values. The call to
fetch_object without arguments is, therefore, equivalent to:
PLVobj.fetch_object (v_name, v_type);
PLVobj.setcurr (v_name, v_type);

11.4.3 Checking for Last Record
To determine when you have fetched all of the records from the cursor, use the more_objects function,
whose header is:
FUNCTION more_objects RETURN BOOLEAN;

This function returns TRUE when the obj_cur is open and when obj_cur%FOUND returns TRUE. In all
other cases, the function returns FALSE (including when the PLVobj cursor is not even open).

11.4.4 Showing Objects with PLVobj
To see how all of these different cursor−oriented programs can be utilized, consider the following script
(stored in showobj1.sql).
DECLARE
first_one BOOLEAN := TRUE;
BEGIN
PLVobj.setcurr ('&1');
PLVobj.open_objects;
LOOP
PLVobj.fetch_object;
EXIT WHEN NOT PLVobj.more_objects;
PLVobj.showcurr (first_one);
first_one := FALSE;
END LOOP;
PLVobj.close_objects;
END;
/

It sets the current object to the value passed in at the SQL*Plus command line. It then opens and fetches from
the PLVobj cursor, exiting when more_objects returns FALSE. Finally, it closes the PLVobj cursor. This
cursor close action is truly required. The PLVobj cursor is not declared in the scope of the anonymous block;
instead, it is defined in the package body. After you open it, it will remain open for the duration of your
session, unless you close it explicitly.
In the following example of a call to showobj1.sql, I ask to see all the package specifications in my
account whose names start with "PLVC". I see that I have four packages.
SQL> start showobj1 s:PLVc%
Schema.Name.Type
PLV.PLVCASE.PACKAGE
PLV.PLVCAT.PACKAGE
PLV.PLVCHR.PACKAGE
PLV.PLVCMT.PACKAGE

If you are not working in SQL*Plus, you can easily convert the showobj1.sql script into a procedure as
follows:
CREATE OR REPLACE PROCEDURE showobj (obj_in IN VARCHAR2)
IS
first_one BOOLEAN := TRUE;

11.4.3 Checking for Last Record

337