Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

Oracle

Solutions for High-End


Oracle® DBAs and Developers Professional

Resolving NAME_
RESOLVE Issues
Steven Feuerstein
In this article, Steven Feuerstein explores the weaknesses in one of the DBMS_
UTILITY programs—NAME_RESOLVE—and shows you how he came up with
an alternative.

I
’VE long held strongly mixed feelings about the DBMS_UTILITY package. I
refer to it in my classes as the “remainders bin” of built-in packages: As far
as I can tell, if someone inside Oracle builds a program that’s useful to them
(and maybe one or two others) but doesn’t clearly belong anywhere else, it October 2004
gets tossed into DBMS_UTILITY.
Volume 11, Number 10
The quality of code inside this package seems a bit uneven. Of the 31
programs defined in DBMS_UTILITY in Oracle9i Release 2 (a number I 1 Resolving NAME_
arrived at by executing the following query: SELECT COUNT(*) FROM RESOLVE Issues
ALL_PROCEDURES WHERE OBJECT_NAME = ‘DBMS_UTILITY’), several Steven Feuerstein
have very dubious track records:
9 Using Autonomous
• RECOMPILE_SCHEMA—It’s supposed to recompile all invalid objects, Transactions
but it seems to ignore the dependency tree of those objects, so it often Moru Nuhamovici
invalidates many objects in the course of recompiling the currently
12 Techniques in Oracle Reports
invalid objects.
Error Message Handling
• TABLE_TO_COMMA and COMMA_TO_TABLE—These could be fairly Bulusu Lakshman
useful delimited string parsing utilities... except they only work with
comma-delimited strings and, worse, the elements in the string must 16 October 2004 Downloads
be valid PL/SQL identifiers. Go figure.
• NAME_RESOLVE—Another program with high potential, NAME_
RESOLVE is supposed to take the name of an object—say, “SALES_
PKG.CALC_TOTALS”—and return all of the individual component Indicates accompanying files are available online at
www.pinnaclepublishing.com.
elements of the specified object (schema, object name, sub-object name,
object type, database link). Unfortunately, it only PROCEDURE DBMS_UTILITY.NAME_RESOLVE
Argument Name Type In/Out
seems to work with stored program object names, --------------------- --------- ------
and it doesn’t resolve objects accessible through NAME VARCHAR2 IN
CONTEXT NUMBER IN
synonyms properly in all cases. SCHEMA VARCHAR2 OUT
PART1 VARCHAR2 OUT
So what’s a developer to do? We dutifully inform PART2 VARCHAR2 OUT
DBLINK VARCHAR2 OUT
Oracle of the problems we find... and then build our own PART1_TYPE NUMBER OUT
substitutes. For example, Solomon Yakobson wrote a OBJECT_NUMBER NUMBER OUT
program to recompile all objects that’s been available on
the Quest Pipeline for years; it does things right, using In other words, pass in the object name as a single
CONNECT BY START WITH to make sure that objects are string, as well as a context value, and receive back all the
recompiled in the right order. resolved elements of the name (plus the object number
As for string parsing, well, there must be hundreds, or handle).
maybe thousands of such programs circulating. I wrote Looking at this list, some questions immediately arise:
one in 2002 called str2list, which parses a delimited string • What the heck is that CONTEXT?
(you specify the delimiter) and then deposits the elements • What are the PART1_TYPE values?
into your collection via native dynamic SQL. That was fun • Why isn’t there a PART2 type?
and I’m including it in the Download for this article. • How are PART1 and PART2 filled for various types
And most lately, in my work on Qnxo (active of objects?
mentoring software available at www.qnxo.com), I’ve
come to rely heavily on DBMS_UTILITY.NAME_ To answer these questions, of course, we’ll
RESOLVE—which has prompted me, with help from my journey forth into the world of Oracle documentation.
colleague, Leonid Khukhlovich, to come up with a Table 1 displays what the Supplied PL/SQL Packages
program that enhances and completes NAME_RESOLVE, and Types Reference has to say about NAME_RESOLVE
so that it works for almost all object types and isn’t parameters.
befuddled by synonyms. That helps a little bit. For example, Oracle implies
here (and in other places like Metalink states it more
The Oracle story on explicitly) that NAME_RESOLVE is only to be used
DBMS_UTILITY.NAME_RESOLVE with stored programs. It does not, however, tell us
Before we go exploring in the nooks and crannies of anything useful regarding CONTEXT. What are we
NAME_RESOLVE, let’s first see what Oracle has to say supposed to pass for that parameter? I think the time
about the program. Its parameter list (from a DESCRIBE has come to build some scripts that actually exercise
DBMS_UTILITY command in SQL*Plus) is: NAME_RESOLVE, and unveil its secrets.

Table 1. NAME_RESOLVE parameter definitions.

Parameter Description
name This can be of the form [[a.]b.]c[@d], where a, b, c are SQL identifiers and d is a dblink. No syntax checking is performed on the
dblink. If a dblink is specified, or if the name resolves to something with a dblink, then the object is not resolved, but the schema,
part1, part2, and dblink OUT parameters are filled in. a, b, and c may be delimited identifiers, and may contain NLS characters
(single and multi-byte).

context Must be an integer between 0 and 8.

schema Schema of the object: c. If no schema is specified in name, then the schema is determined by resolving the name.

part1 First part of the name. The type of this name is specified part1_type (synonym, procedure, or package).

part2 If this is non-NULL, then this is a procedure name within the package indicated by part1.

dblink If this is non-NULL, then either a database link was specified as part of name or name was a synonym that resolved to something
with a database link. In this latter case, part1_type indicates a synonym.

Type of part1 5 - synonym


7 - procedure (top level)
8 - function (top level)
9 - package
If a synonym, then it means that name is a synonym that translates to something with a database link. In this case, if further name
translation is desired, then you must call the DBMS_UTILITY.NAME_RESOLVE procedure on this remote node.

2 Oracle Professional October 2004 www.pinnaclepublishing.com


Exercising NAME_RESOLVE PART1_TYPE returned PACKAGE and not the actual
Normally, I’d suggest that we immediately use utPLSQL, program type.
an open source unit testing framework, and its graphical And now let’s try a context value of 2:
interface, Ounit, to start building test scripts to exercise
NAME_RESOLVE (visit www.ounit.com for more SQL> @snc_test1 2
Error resolving "proc" with context 2
information). In this case, however, I have so many basic ORA-04047: object specified is
questions (for example, what’s with those context values?) incompatible with the flag specified

about this built-in that I’m going to throw together a


quick test script that will then allow me to build a real Interesting. So the object (that is, the type of the
regression test script for utPLSQL. object) seems to be related to the context. Yet for contexts
First, I’m going to take advantage of (reuse!) 3-5, I get a different error:
existing code. Several years ago I wrote a program
Error resolving "proc" with context 3
named SNC (for Show Name Components), which ORA-06564: object proc does not exist
essentially provides a “cover” for NAME_RESOLVE.
Its header is: And for contexts 6 and 7, I’m back to the
“incompatible” error.
PROCEDURE snc ( Okay, to summarize what I’ve found so far: If I
NAME_IN IN VARCHAR2
, context_in IN PLS_INTEGER) use a context value of 1, NAME_RESOLVE will resolve
the names of stored program units. Oracle says that
The full program is shown in the “Name resolve NAME_RESOLVE only works with stored program units,
listings” document that’s included in the Download for so I guess I should be happy with that. Yet... I wonder...
this article. It simply calls NAME_RESOLVE (and frees as long as I’m logged into the SCOTT schema and have
me from having to declare all of those variables for the access to my dear old EMP table, let’s give that a try:
OUT parameters) and then displays the results. Using snc,
I can quickly get a basic handle on NAME_RESOLVE SQL> exec snc ('EMP', 1)
Error resolving "EMP" with context 1
behavior with snc_test1.sql (the source code is shown ORA-04047: object specified is
in the “Name resolve listings” document; partial results incompatible with the flag specified

of execution are shown here for a context value of 0): SQL> exec snc ('EMP', 2)
Schema: SCOTT
SQL> @snc_test1 0 2: EMP
Error resolving "proc" with context 0
ORA-20005: ORU-10034: context argument SQL> exec snc ('EMP', 3)
must be 1 or 2 or 3 or 4 or 5 or 6 or 7 Error resolving "EMP" with context 3
ORA-06564: object EMP does not exist

I received the same error for each of the attempts with


Hey, there’s a nice discovery! If I use a context value
a context of 0. That’s curious! I guess the valid range for
of 2, then NAME_RESOLVE seems to work for tables,
CONTEXT isn’t really 0-8, but 1-7!
though the description “Table” doesn’t appear, but
Well, let’s try it for the next context. Ah, much better:
instead a value of 2. This is yet another object type that
SQL> @snc_test1 1
I need to define in the local object_type function of the
Schema: SCOTT SNC package.
Procedure: PROC But what about views, and object types, and...?
Schema: SCOTT I could continue to run lots of tests on all of these
Function: FUNC variations of objects and contexts, but that’s very clumsy
Schema: SCOTT and tedious. And why, I wonder, should I, a user of
Package: PKG NAME_RESOLVE, have to know about, or care about, the
Schema: SCOTT context at all? Shouldn’t NAME_RESOLVE figure out the
Package: PKG right context based on the type? Maybe, but it doesn’t.
Name: PKGPROC
Ah, but perhaps the SNC program could do that!
Schema: SCOTT Listing 1 displays version 2 of SNC. It now calls NAME_
Package: PKG
Name: PKGFUNC RESOLVE inside a loop, and keeps on trying until it
encounters a context that works for the name supplied.
So for a context value of 1, NAME_RESOLVE It’s now a “smarter” program than NAME_RESOLVE!
resolved the name into its individual components. Notice, I’ve also made some other changes:
however, that in the last two executions, even though I • I removed the context parameter, since the whole
passed the name of a packaged procedure or function, the point is to hide that from the user.

www.pinnaclepublishing.com Oracle Professional October 2004 3


• I moved my type names from inside the local ,object_number
);
object_type function into a collection. That’s much l_resolved := TRUE;
cleaner and easier to expand (notice that I’ve added EXCEPTION
WHEN OTHERS
TABLE and VIEW values). The collection is initialized THEN
at the start of the procedure execution. l_context := l_context + 1;
END;
END LOOP;

Listing 1. Version 2 of SNC, the smarter program. -- If the object number is NULL,
-- name resolution failed.
IF object_number IS NULL
CREATE OR REPLACE PROCEDURE snc (NAME_IN IN VARCHAR2) THEN
IS DBMS_OUTPUT.put_line ( 'Name "'
-- Maximum context allowed by NAME_RESOLVE || NAME_IN
c_max_context CONSTANT PLS_INTEGER := 8; || '" does not identify a valid object.'
-- Test context value );
l_context PLS_INTEGER := 0; ELSE
-- DBMS_OUTPUT.put_line ('Context: ' || l_context);
l_resolved BOOLEAN := FALSE; DBMS_OUTPUT.put_line ('Schema: ' || sch);
--
-- Variables to hold components of the name. IF part1 IS NOT NULL
sch VARCHAR2 (100); THEN
part1 VARCHAR2 (100); DBMS_OUTPUT.put_line (
part2 VARCHAR2 (100); name_for_type (part1_type) || ': ' || part1);
dblink VARCHAR2 (100);
part1_type NUMBER; IF part2 IS NOT NULL
object_number NUMBER; THEN
DBMS_OUTPUT.put_line ('Name: ' || part2);
-- Collection to hold object type names END IF;
-- for type numbers ELSE
TYPE names_tt IS TABLE OF DBMS_OUTPUT.put_line (
all_objects.object_type%TYPE name_for_type (part1_type) || ': ' || part2);
INDEX BY BINARY_INTEGER; END IF;
names_t names_tt; IF dblink IS NOT NULL
-- THEN
-- The type numbers I now know about DBMS_OUTPUT.put_line ('Database Link:'
table_type CONSTANT PLS_INTEGER := 2; || dblink);
view_type CONSTANT PLS_INTEGER := 4; END IF;
synonym_type CONSTANT PLS_INTEGER := 5; END IF;
procedure_type CONSTANT PLS_INTEGER := 7; EXCEPTION
function_type CONSTANT PLS_INTEGER := 8; WHEN OTHERS
package_type CONSTANT PLS_INTEGER := 9; THEN
DBMS_OUTPUT.put_line ('SNC Error resolving "'
PROCEDURE initialize || NAME_IN);
IS DBMS_OUTPUT.put_line (
BEGIN DBMS_UTILITY.format_error_stack);
names_t (table_type) := 'Table'; END snc;
names_t (view_type) := 'View'; /
names_t (synonym_type) := 'Synonym';
names_t (procedure_type) := 'Procedure';
names_t (function_type) := 'Function'; When I run it for my expanded set of objects
names_t (package_type) := 'Package';
END initialize; (snc_test2.sql), I see that tables and views are now
handled intelligently:
FUNCTION name_for_type (type_in IN PLS_INTEGER)
RETURN VARCHAR2
IS Context: 2
BEGIN Schema: SCOTT
RETURN names_t (type_in); Table: TAB
EXCEPTION
WHEN NO_DATA_FOUND Context: 2
THEN Schema: SCOTT
RETURN type_in; View: VU
END name_for_type;
BEGIN
initialize; So SNC has gotten more flexible, and I’m tempted to
/* Break down the name into its components */ say that I’m now ready to use it against a wide array of
WHILE (NOT l_resolved AND l_context <= c_max_context) inputs (types of objects and different ways of naming
LOOP
BEGIN objects, and particularly with the use of synonyms). Yet
DBMS_UTILITY.name_resolve (NAME_IN I hesitate, because for all the cool qualities of SNC, it’s
,l_context
,sch still just a demonstration program. My efforts with this
,part1 program, however, aren’t just for demonstration purposes
,part2
,dblink or simply to write an article. I need an enhanced version
,part1_type of NAME_RESOLVE in Qnxo that works under all

4 Oracle Professional October 2004 www.pinnaclepublishing.com


circumstances (or as many as possible). Furthermore, I have, in turn, moved the display logic from
with the way this program is structured, the only way SNC to QNR_SHOW, which now calls QNXO_NAME_
to test it, to verify that it works, is to visually scan the RESOLVE and displays the results.
results and see that they make sense. That won’t suffice
for a large number of complex cases. NAME_RESOLVE and synonyms
I’ll therefore produce a third version of this One of the features of NAME_RESOLVE is that it’s
program, shown in Listing 2, that can serve as a supposed to have “x-ray vision” when it comes to
substitute for NAME_RESOLVE. In fact, it has the same synonyms. In other words, if I set up a synonym for
parameter list as NAME_RESOLVE, except that I’m a procedure and pass the name of the synonym to
leaving out CONTEXT. NAME_RESOLVE, it should return the information for
that procedure. Let’s run some tests to see how NAME_
RESOLVE fares in the area of synonyms.
Listing 2. The QNXO_NAME_RESOLVE procedure.
I’ll set up a number of synonyms, including
CREATE OR REPLACE PROCEDURE qnxo_name_resolve ( synonyms of synonyms—see Listing 3.
NAME IN VARCHAR2
,SCHEMA OUT VARCHAR2
,part1 OUT VARCHAR2 Listing 3. Defining my synonyms.
,part2 OUT VARCHAR2
,dblink OUT VARCHAR2
,part1_type OUT NUMBER CONNECT SCOTT/TIGER
,object_number OUT NUMBER
) AUTHID CURRENT_USER GRANT EXECUTE ON qnr_show TO PUBLIC;
IS CREATE OR REPLACE PUBLIC SYNONYM qnr_show FOR qnr_show;
-- Maximum context allowed by NAME_RESOLVE
c_max_context CONSTANT PLS_INTEGER := 8; CREATE OR REPLACE PACKAGE pkg
-- Test context value IS
l_context PLS_INTEGER := 0; PROCEDURE pkgproc;
--
l_resolved BOOLEAN := FALSE; FUNCTION pkgfunc
BEGIN RETURN DATE;
/* Break down the name into its components */ END pkg;
WHILE (NOT l_resolved AND l_context <= c_max_context) /
LOOP
BEGIN GRANT EXECUTE ON pkg TO PUBLIC;
DBMS_UTILITY.name_resolve (NAME
,l_context CREATE SYNONYM ls_pkg FOR pkg;
,SCHEMA CREATE OR REPLACE PUBLIC SYNONYM ps_pkg FOR pkg;
,part1 CREATE SYNONYM ls_ls_pkg FOR ls_pkg;
,part2 CREATE OR REPLACE PUBLIC SYNONYM ps_ps_pkg FOR ps_pkg;
,dblink CREATE SYNONYM ls_ps_pkg FOR ps_pkg;
,part1_type CREATE OR REPLACE PUBLIC SYNONYM ps_ls_pkg FOR ls_pkg;
,object_number
);
l_resolved := TRUE; I then run my test, using QNR_SHOW, first from
EXCEPTION SCOTT (the owner of the objects):
WHEN OTHERS
THEN
l_context := l_context + 1; BEGIN
END; qnr_show ('pkg');
END LOOP; qnr_show ('ls_pkg');
EXCEPTION qnr_show ('ps_pkg');
WHEN OTHERS qnr_show ('ps_ls_pkg');
THEN qnr_show ('ps_ps_pkg');
DBMS_OUTPUT.put_line ('Qnxo_Name_Resolve cannot qnr_show ('ls_ps_pkg');
resolve "' || NAME || '"' END;
); /
DBMS_OUTPUT.put_line (
DBMS_UTILITY.format_error_stack);
END qnxo_name_resolve; Here are the SCOTT results:
/
pkg ==> Schema: SCOTT Package: PKG
ls_pkg ==> Schema: SCOTT Package: PKG
Notice that it’s actually gotten quite a bit smaller, ps_pkg ==> Schema: SCOTT Package: PKG
because I took out the display code; now it’s “all ps_ls_pkg ==> Schema: SCOTT Package: PKG
Name "ps_ps_pkg" does not identify a valid object.
business.” It calls NAME_RESOLVE until it’s successful Name "ls_ps_pkg" does not identify a valid object.
or runs out of context values. Then it passes back
what it finds. I also define it to be an “invoker rights” And now from DEMO:
program, so that when someone other than its owner
runs this utility, it will run under the authority of that BEGIN
scott.qnr_show ('scott.pkg');
schema, and see its objects, not those of the owner of scott.qnr_show ('ps_pkg');
QNXO_NAME_RESOLVE. scott.qnr_show ('ps_ls_pkg');

www.pinnaclepublishing.com Oracle Professional October 2004 5


scott.qnr_show ('ps_ps_pkg'); || '.'
END; || NAME_IN
/ || '" does not identify a synonym.'
);
DBMS_OUTPUT.put_line ('');
And here are the results: END show_synonym;

scott.pkg ==> Schema: SCOTT Package: PKG When I use this to display the synonyms I created in
ps_pkg ==> Schema: SCOTT Package: PKG
ps_ls_pkg ==> Schema: SCOTT Package: PKG the previous section, I see the following:
Name "ps_ps_pkg" does not identify a valid object.
SQL> BEGIN
So it seems we’ve found a problem with 2 qnr.show_synonym ('SCOTT', 'ls_pkg');
3 qnr.show_synonym ('SCOTT', 'ps_pkg');
NAME_RESOLVE: It doesn’t properly resolve multiple 4 qnr.show_synonym ('PUBLIC', 'ps_pkg');
layers of synonyms in the specific case when one of the 5 qnr.show_synonym ('SCOTT', 'ls_ls_pkg');
6 qnr.show_synonym ('SCOTT', 'ls_ps_pkg');
“inner” synonyms is a PUBLIC synonym. Let’s see if we 7 qnr.show_synonym ('SCOTT', 'ps_ls_pkg');
can understand why this is happening—and then see 8 qnr.show_synonym ('PUBLIC', 'ps_ls_pkg');
9 END;
how we can fix it within QNXO_NAME_RESOLVE. 10 /
Synonym "ls_pkg" is defined as:
Owner: SCOTT
The problem with public synonyms Synonym name: LS_PKG
To figure out what’s going on, I’ll need to query the Table owner: SCOTT
Table name: PKG
contents of ALL_SYNONYMS to show me synonym Database link:
definitions. Rather than run standalone queries, I’ll
"SCOTT.ps_pkg" is not a synonym.
create a PL/SQL procedure to take care of the details.
And now I face a quandary: I’ve so far created two Synonym "ps_pkg" is defined as:
Owner: PUBLIC
standalone procedures, QNXO_NAME_RESOLVE and Synonym name: PS_PKG
QNXO_SHOW. Now I’m going to create another Table owner: SCOTT
Table name: PKG
standalone procedure to show synonyms? I don’t like all Database link:
of these separate procedures clogging up my object list.
Synonym "ls_ls_pkg" is defined as:
So I’ll move them (and create them) all in a single Owner: SCOTT
package named QNR. This package is defined in two Synonym name: LS_LS_PKG
Table owner: SCOTT
files, qnr.pks and qnr.pkb. I show in Listing 4 only the Table name: LS_PKG
show_synonyms procedure that’s new. There’s nothing Database link:
particularly challenging about it: Query the row and then Synonym "ls_ps_pkg" is defined as:
display the results. Owner: SCOTT
Synonym name: LS_PS_PKG
Table owner: SCOTT
Table name: PS_PKG
Listing 4. The implementation of show_synonyms. Database link:

PROCEDURE show_synonym (owner_in IN VARCHAR2 "SCOTT.ps_ls_pkg" is not a synonym.


, NAME_IN IN VARCHAR2)
IS Synonym "ps_ls_pkg" is defined as:
l_synonym all_synonyms%ROWTYPE; Owner: PUBLIC
BEGIN Synonym name: PS_LS_PKG
SELECT * Table owner: SCOTT
INTO l_synonym Table name: LS_PKG
FROM all_synonyms Database link:
WHERE owner = UPPER (owner_in)
AND synonym_name = UPPER (NAME_IN);
Everything looks pretty much like what I would
DBMS_OUTPUT.put_line ('Synonym "' || NAME_IN have expected, except for one thing: When I create a
|| '" is defined as:');
DBMS_OUTPUT.put_line (' Owner: '
synonym on top of a public synonym, ALL_SYNONYMS
|| l_synonym.owner); loses the information about the public nature of that
DBMS_OUTPUT.put_line (' Synonym name: '
|| l_synonym.synonym_name);
“internal” synonym. Take a close look at this definition:
DBMS_OUTPUT.put_line (' Table owner: '
|| l_synonym.table_owner); Synonym "ls_ps_pkg" is defined as:
DBMS_OUTPUT.put_line (' Table name: ' Owner: SCOTT
|| l_synonym.table_name); Synonym name: LS_PS_PKG
DBMS_OUTPUT.put_line (' Database link: ' Table owner: SCOTT
|| l_synonym.db_link); Table name: PS_PKG
DBMS_OUTPUT.put_line ('');
EXCEPTION
WHEN NO_DATA_FOUND The “table owner” (hey, I didn’t name these columns!)
THEN
DBMS_OUTPUT.put_line ( '"'
should be PUBLIC, not SCOTT. So it looks to me like
|| owner_in NAME_RESOLVE can’t do its job because of a quirk in

6 Oracle Professional October 2004 www.pinnaclepublishing.com


ALL_SYNONYMS. synonym (the object to which the synonym points) to the
Fortunately, now that we’ve diagnosed this problem, header synonym information (owner and synonym name)
we can come up with a solution: If DBMS_UTILITY and try again.
.NAME_RESOLVE is unable to resolve the name provided, If I didn’t find a synonym this time but I did find at
then treat it as a synonym and recursively resolve that least one row in the synonyms view for the input value, I
synonym name until we get to the “base” object (with pass back these last bits of “table” information as the base
special PL/SQL logic to handle the aforementioned quirk). object information for the synonym.
Then try NAME_RESOLVE on that object. Now let’s look at the local procedures. To take care
of the parsing of the synonym name, I simply rely on
Recursive resolution of synonym names DBMS_UTILITY.NAME_TOKENIZE, since that’s what it’s
To resolve a synonym into its base object information, I’ll designed to do; this is shown in Listing 6. This procedure
need to do the following: doesn’t do any semantical analysis; it just parses the
1. Parse the incoming name into its separate owner and string as indicated by the documentation for this built-in:
object name elements, since ALL_SYNONYMS has
separate columns for that information. Parse name as “a [. b [. c ]][@ dblink ]”. Strip double
2. Get the information from ALL_SYNONYMS for that quotes, or convert to uppercase if there are no quotes.
owner-name combo. If I don’t find a match in the Ignore comments of all sorts. Do no semantic analysis.
view, try again using PUBLIC as the owner. Leave any missing values as null.
3. Repeat that step until I don’t find a row in the
ALL_SYNONYMS view. I’ll then be down to the If I simply pass “emp” for the name, the string “EMP”
“real” object. Return that information. will be returned in the parameter named “a”. If I pass in
“scott.emp”, then DBMS_UTILITY.NAME_TOKENIZE
I’ve placed this logic in the qnr.synonym_resolve will send back “SCOTT” in the “a” parameter and “EMP”
procedure. The body of that procedure is shown in Listing in the “b” parameter.
5. First, I parse the name, and then I execute a loop that
recursively resolves synonyms. The logic for querying the
Listing 6. Parsing an object name into its component pieces.
synonym information is hidden in the get_synonym_info
local procedure (explained shortly). PROCEDURE parse_name
IS
c all_objects.object_name%TYPE;
Listing 5. The body of qnr.synonym_resolve. dblink all_objects.object_name%TYPE;
nextpos BINARY_INTEGER;
BEGIN
BEGIN DBMS_UTILITY.name_tokenize (
parse_name; NAME => NAME_IN
,a => l_synonym.owner
LOOP ,b => l_synonym.synonym_name
get_synonym_info (l_synonym.owner ,c => c
,l_synonym.synonym_name ,dblink => dblink
,l_synonym ,nextpos => nextpos
,still_a_synonym );
);
IF l_synonym.synonym_name IS NULL
IF still_a_synonym THEN
THEN -- No owner specified, so name is passed back
started_as_synonym := TRUE; -- in "a". Rearrange the items.
-- l_synonym.synonym_name := l_synonym.owner;
-- Now see if we have a recursive synonym l_synonym.owner := USER;
l_synonym.owner := l_synonym.table_owner; END IF;
l_synonym.synonym_name := l_synonym.table_name; END parse_name;
ELSE
-- All done, pass back values if we have any.
IF started_as_synonym Moving on to the substance of this procedure,
THEN consider the code shown in Listing 7. The get_synonym_
schema_out := l_synonym.table_owner;
object_name_out := l_synonym.table_name; info procedure first queries from ALL_SYNONYMS for
END IF; the specified owner and synonym name. If it finds a
EXIT; match, it returns that information. If there’s no match,
END IF; it tries again, using PUBLIC as the synonym owner
END LOOP;
END synonym_resolve; (correcting the problem demonstrated earlier in the
article). If we still find nothing, the data passed in to
Within the body of the loop itself, if I find that I still get_synonym_info represents the base object information
have a synonym, I copy the “table” information of the for the synonym. We’re done.

www.pinnaclepublishing.com Oracle Professional October 2004 7


TEST ('ps_pkg');
Listing 7. Getting the synonym information. TEST ('ps_pkg');
TEST ('ls_ls_pkg');
TEST ('ls_ps_pkg');
PROCEDURE get_synonym_info ( TEST ('ps_ls_pkg');
owner_in IN all_synonyms.owner%TYPE TEST ('ps_ps_pkg');
,NAME_IN IN all_synonyms.synonym_name%TYPE TEST ('scott.ls_pkg');
,syn_out OUT all_synonyms%ROWTYPE TEST ('scott.ps_pkg');
,is_syn_out OUT BOOLEAN TEST ('scott.ps_pkg');
) TEST ('scott.ls_ls_pkg');
IS TEST ('scott.ls_ps_pkg');
l_synonym all_synonyms%ROWTYPE; TEST ('scott.ps_ls_pkg');
BEGIN TEST ('scott.ps_ps_pkg');
SELECT * END;
INTO syn_out /
FROM all_synonyms
WHERE owner = owner_in AND synonym_name = NAME_IN; Synonym "ls_pkg" resolved to "SCOTT.PKG"
Synonym "ps_pkg" resolved to "SCOTT.PKG"
is_syn_out := TRUE; Synonym "ps_pkg" resolved to "SCOTT.PKG"
EXCEPTION Synonym "ls_ls_pkg" resolved to "SCOTT.PKG"
WHEN NO_DATA_FOUND Synonym "ls_ps_pkg" resolved to "SCOTT.PKG"
THEN Synonym "ps_ls_pkg" resolved to "SCOTT.PKG"
-- Now try the same thing with PUBLIC as owner. Synonym "ps_ps_pkg" resolved to "SCOTT.PKG"
BEGIN Synonym "scott.ls_pkg" resolved to "SCOTT.PKG"
SELECT * Synonym "scott.ps_pkg" resolved to "SCOTT.PKG"
INTO syn_out Synonym "scott.ps_pkg" resolved to "SCOTT.PKG"
FROM all_synonyms Synonym "scott.ls_ls_pkg" resolved to "SCOTT.PKG"
WHERE owner = 'PUBLIC' Synonym "scott.ls_ps_pkg" resolved to "SCOTT.PKG"
AND synonym_name = NAME_IN; Synonym "scott.ps_ls_pkg" resolved to "SCOTT.PKG"
Synonym "scott.ps_ps_pkg" resolved to "SCOTT.PKG"
is_syn_out := TRUE;
EXCEPTION
WHEN OTHERS Enhancing qnr.name_resolve to handle
THEN
-- We have gone as far as we can go. all synonyms
syn_out.table_owner := owner_in; I’m almost done. I’ve created a procedure that resolves
syn_out.table_name := NAME_IN;
is_syn_out := FALSE; synonyms recursively down to their correct base object
END; information. Now I need to plug that into qnr.name_
END get_synonym_info;
resolve. To do this, I’ll need to perform some additional
reorganization of my code (a.k.a. “refactoring”). The full
Using qnr.synonym_resolve, I built the test script
reimplementation of the procedure is shown in Listing 9;
shown in Listing 8 and found in qnr_resolve_syn_test.sql
I explain the interesting portions after the listing.
in the accompanying Download. The results were
very pleasing indeed; we’re now able to resolve all of
the synonyms. Listing 9. Final, refactored implementation of qnr.name_resolve.

PROCEDURE name_resolve (
Listing 8. Testing the synonym_resolve procedure. NAME IN VARCHAR2
,SCHEMA OUT VARCHAR2
,part1 OUT VARCHAR2
CREATE SYNONYM ls_pkg FOR pkg; ,part2 OUT VARCHAR2
CREATE PUBLIC SYNONYM ps_pkg FOR pkg; ,dblink OUT VARCHAR2
CREATE SYNONYM ls_ls_pkg FOR ls_pkg; ,part1_type OUT NUMBER
CREATE PUBLIC SYNONYM ps_ps_pkg FOR ps_pkg; ,object_number OUT NUMBER
CREATE SYNONYM ls_ps_pkg FOR ps_pkg; )
CREATE PUBLIC SYNONYM ps_ls_pkg FOR ls_pkg; IS
PROCEDURE resolve_in_loop (NAME_IN IN VARCHAR2)
SET serveroutput on format wrapped IS
-- Maximum context allowed by NAME_RESOLVE
DECLARE c_max_context CONSTANT PLS_INTEGER := 8;
PROCEDURE TEST (NAME IN VARCHAR2) -- Test context value
IS l_context PLS_INTEGER := 0;
SCHEMA all_objects.owner%TYPE; --
object_name all_objects.object_name%TYPE; l_resolved BOOLEAN := FALSE;
BEGIN BEGIN
qnr.synonym_resolve (NAME, SCHEMA, object_name); /* Break down the name into its components */
dbms_output.put_line ( 'Synonym "' WHILE (NOT l_resolved
|| NAME AND l_context <= c_max_context)
|| '" resolved to "' LOOP
|| SCHEMA BEGIN
|| '.' DBMS_UTILITY.name_resolve (NAME_IN
|| object_name ,l_context
|| '"' ,SCHEMA
); ,part1
END; ,part2
BEGIN
TEST ('ls_pkg'); Continues on page 15

8 Oracle Professional October 2004 www.pinnaclepublishing.com


Oracle
Professional

Using Autonomous
Transactions
Moru Nuhamovici
With the introduction of autonomous transactions in debited must be in $200 increments. So, an attempt to
Oracle8i, a whole new series of possibilities became available debit more than $200 would be logged and not allowed.
to the user. Oracle has always used autonomous transactions The myaudit_table will store all attempts at trying to
for internal purposes, but it hasn’t exposed this feature to break the rule of debiting more than $200.
the general public before. In this article, Moru Nuhamovici
explains the benefits of autonomous transactions and shows SQL> create table myaudit_table
2 (
how this feature can be exploited. 3 table_name varchar2(30),
4 user_name varchar2(30) default user,
5 timestamp date default sysdate,

P
RIOR to Oracle8i, there was no way to execute a 6 comments varchar2(4000)
transaction separate from the current transaction— 7 );

therefore, if the parent transaction rolled back, the Table created.


transaction executed with it also rolled back. In contrast,
SQL> create table ClientTransactions
autonomous transactions provide a way to have another 2 (
transaction perform a body of work that can be 3 Clientid number(10),
4 Debit number(10),
committed or rolled back independently of the current 5 Credit number(10),
one. In other words, it’s a fully independent transaction. 6 Overdraft number(10),
7 Balance number(10)
Clearly, one critical rule you must follow when 8* );
using an autonomous transaction is that it has to either
Table created.
commit or rollback prior to returning control back to the
calling process. SQL> -- insert some rows into clienttransactions
Autonomous transactions can be used within SQL> insert into clienttransactions
the following: values (1, 0, 1000, 100, 1000);
• An anonymous block 1 row created.
• A procedure, function, or package
SQL> insert into clienttransactions
• A database trigger values (2, 0, 2000, 200, 2000);

1 row created.
Autonomous transactions are typically used for
auditing, often defined inside triggers to catch a user SQL> insert into clienttransactions
values (3, 0, 3000, 300, 3000);
trying to modify the data in some manner. If the
transaction that was trying to modify the data didn’t 1 row created.
complete and failed, the independent autonomous SQL> select * from clienttransactions;
transaction would still succeed and catch the attempt at
CLIENTID DEBIT CREDIT OVERDRAFT BALANCE
modifying the data. A common reason why the original ---------- ---------- ---------- ---------- ----------
transaction would fail would be properly enforced 1 0 1000 100 1000
2 0 2000 200 2000
security that prevents the user from performing 3 0 3000 300 3000
unauthorized actions.
Let’s take a look at a couple of examples that Next, I create a function that returns the maximum
illustrate this usage. amount being debited. Creating the function to isolate
this rule makes it easier to change this later on.
Performing I/O on an Update statement
Suppose I have a clienttransactions table that records the create or replace function max_debit
return number as
amount credited, the amount debited, and the overall max_debit number(10);
balance. I have a rule requiring that the amount being begin

www.pinnaclepublishing.com Oracle Professional October 2004 9


max_debit := 200; attempted or managed to see certain confidential data
return max_debit;
end; and when.
/ I can track the user who has attempted to see data—
and also control whether I’ll allow it or not. In this next
I then create a database trigger that’s responsible for example, when a user tries to see the balance currently
logging changes to the clienttransactions table. Note the available for any clientid, I’ll insert a record into
use of the “pragma autonomous_transaction” clause, myaudit_table showing who and what was seen. I’ll also
which indicates to Oracle that the SQL statements in this create two users for this example: honestsam, who can
trigger are run as an autonomous transaction. The main query and see the value of the balance, and sneekypete,
transaction may fail, but the transaction within this who cannot. I’ll modify the table myaudit_table slightly
trigger will still proceed. in order to hold more information about the primary key
and the column data actually viewed.
create or replace trigger ClientTransactions_Audit
before update on ClientTransactions
for each row create table myaudit_table
declare (
pragma autonomous_transaction; table_name varchar2(30),
cnt number; column_name varchar2(30),
begin pk_value number(10),
if (:new.debit > max_debit()) then column_data number(10),
insert into myaudit_table (table_name, comments) user_name varchar2(30) default user,
values ('ClientTransactions','Attempt to deduct ' timestamp date default sysdate
|| to_char(:new.debit) ); )
commit; /
raise_application_error( -20001,
'You cannot deduct more than ' create or replace function LogSelects(
|| to_char(max_debit()) ); Table_Name in Varchar2,
end if; Column_Name in Varchar2,
end; Pk_value in number,
/ Column_data in number)
return number is
pragma autonomous_transaction;
Once the trigger is created, I can test it by issuing begin
the following transactions: insert into myaudit_table values
( Table_name, Column_name, Pk_Value
, Column_data, user, sysdate
SQL> update clienttransactions set debit = 500 , 'you where caught' );
, credit = credit - 500, balance = balance - 500 commit;
where clientid = 1; if upper(user) = 'SNEEKYPETE' then
raise_application_error( -20001,
ERROR at line 1: 'You are not allowed to view this column');
ORA-20001: You cannot deduct more than 200 else
ORA-06512: at "TEST.CLIENTTRANSACTIONS_AUDIT", line 9 return Column_data;
ORA-04088: error during execution of trigger end if;
'TEST.CLIENTTRANSACTIONS_AUDIT' end;
/

At this point, the error returned tells me that I can’t create or replace view v_clienttransactions
deduct more than $200. What’s not known to a user who as
select Clientid, Debit, Credit, Overdraft
tries to perform this transaction is that this attempt has , LogSelects('ClientTransactions', 'Balance'
been logged. Let’s take a peek at the myaudit_table and , ClientId, Balance) Balance from clienttransactions;
/
see what’s been caught:
This is the key technique here: I’m using a view to
SQL> select table_name, timestamp, comments
from myaudit_table; wrap the clienttransactions table; the users aren’t
querying the table directly. By doing this, I’m able to
TABLE_NAME TIMESTAMP COMMENTS include the function LogSelects and alias it with the
---------------------- --------- --------------------- name Balance. This will be transparent to the user.
ClientTransactions 27-JUN-04 Attempt to deduct 500
Now I’ll create my two users and test the theory out.
You can see that the trigger caught the attempt SQL> create user honestsam identified by honestsam;
to deduct more than $200, as well as preventing it
User created.
from happening.
SQL> create user sneekypete identified by sneekypete;
Performing I/O on a Select statement User created.
The ability to catch attempts to modify data is great, but
SQL> grant create session to honestsam, sneekypete;
what about catching attempts to read data? In some
situations it would be nice to be able to track who has Grant succeeded.

10 Oracle Professional October 2004 www.pinnaclepublishing.com


SQL> grant select on v_clienttransactions to honestsam; SQL> select table_name, column,name
, column_value, user_name
Grant succeeded. from myaudit_table;

SQL> grant select on v_clienttransactions to sneekypete; TABLE_NAME COLUMN_NAME COLUMN_DATA USER_NAME


------------------- ------------------------ ---------
Grant succeeded. ClientTransactions Balance 1000 HONESTSAM
ClientTransactions Balance 1000 SNEEKYPETE
SQL> connect honestsam / honestsam ClientTransactions Balance 1000 HONESTSAM
Connected. ClientTransactions Balance 2000 HONESTSAM
ClientTransactions Balance 3000 HONESTSAM
SQL> select balance from test.v_clienttransactions
where clientid = 1;
As you can see, the LogSelects was invoked even
BALANCE though the user never actually “saw” (that is, queried)
----------
1000 the individual record values. This is one of the quirks, and
it’s a reason to apply this technique carefully.
SQL> connect sneekypete/sneekypete;
Connected.
Summary
SQL> select balance from test.v_clienttransactions
where clientid = 1; Autonomous transactions give the user new possibilities
* that were previously available only to the system
ERROR at line 1:
ORA-20001: You are not allow to view this column internally. They’re mostly applicable to tracking the
ORA-06512: at "TEST.LOGSELECTS", line 12 operations—both the ones that failed and the successful
ones. As with any power tool, it’s wise to make sure you
SQL> connect test/test; understand some side effects, so that you can apply the
Connected.
functionality and interpret the results properly. ▲
SQL> select table_name, column,name
, column_value, user_name
from myaudit_table;
Moru Nuhamovici is a software development manager at Trapeze
Software Group, Inc. He has more than 15 years of software development
TABLE_NAME COLUMN_NAME COLUMN_DATA USER_NAME experience and has been working with Oracle products for the past seven
------------------- ------------------------ ---------
ClientTransactions Balance 1000 HONESTSAM years. He’s currently involved in managing the ongoing development in
ClientTransactions Balance 1000 SNEEKYPETE Trapeze’s Para transit division. Moru@trapezesoftware.com.

You can see that the tests worked as expected.

Caveats
If you intend to use this feature frequently, be sure to
increase the limit on transactions within your database
initialization file, init.ora. When this feature is used
extensively, it can make troubleshooting a bit more
difficult, as sometimes you can be guessing about which
transactions are the ones currently active.
D:
Sometimes the autonomous transaction will report
that users saw something they really didn’t. Suppose GE
A
I wanted to sum the balance of all clients for a report: PA
TER
R OFT
QU REALS
SQL> connect honestsam/honestsam;
Connected. A
SQL> select sum(balance) from test.v_clienttransactions;

SUM(BALANCE)
------------
6000

SQL> connect test/test;


Connected.

Know a clever shortcut? Have an idea


for an article for Oracle Professional?
Visit www.pinnaclepublishing.com and click
on “Write For Us” to submit your ideas.

www.pinnaclepublishing.com Oracle Professional October 2004 11


Oracle
Professional

Techniques in Oracle Reports


Error Message Handling
Bulusu Lakshman
In this article, Bulusu Lakshman explores how to apply best customized code when possible. I discuss them in more
practices in Oracle Reports development with reference to detail in the following sections.
error message handling. The techniques described here
will help the Reports developer implement a standard Errors raised out of failure of Report object properties
programming interface for trapping Reports errors as well These REP errors are caused by failure of Report object
as make the Reports application foolproof. Whether you’re properties that are evaluated at runtime. Common
working with version 9i, 10g, or an earlier version of Oracle instances of such errors include wrong anchoring
Reports, you’ll find this article useful, as Bulusu discusses (implicit or explicit) and improper placement of fields
normal error message handling, how to use SRW.PROGRAM_ within repeating frames. A typical example is the
ABORT and RETURN (FALSE), and how to build a PL/SQL following error:
interface for error tracking.
REP-1213: Field <field_name> references <column_name>
at a frequency below its group.

A
LL errors in Oracle Reports belong to a standard
type of REP and are characterized by Error Type This is caused by the placement of a field inside a
(indicates whether the error is a warning message repeating frame that’s not in sync with the group of the
or an actual error message), Error Code (an integer associated source column in the data model.
indicating the error number), and Error Text (the text of Normally, Oracle Reports halts the execution of the
the error message). report and raises its own error.
There’s a set of procedures, functions, and exceptions
to handle errors in Reports. Let’s first look at a brief Errors raised out of exceptions from trigger/PL/SQL code
overview of the primary ones before delving into PL/SQL code can be incorporated into a report by using
examples of their use. any one of the following:
• PL/SQL query in the data model
Procedures and functions • Formula column in the data model
The SRW package contains a procedure called MESSAGE • Validation code of a report parameter
that allows you to define a customized error message, • Group filter in the data model
which can be application-specific. • Format trigger of a frame, repeating frame, field, or
SQLCODE and SQLERRM are functions that return boilerplate text
the code and text for standard PL/SQL exceptions. • Report triggers such as BEFORE PARAMETER
FORM, AFTER PARAMETER FORM, BEFORE
Exceptions REPORT, BETWEEN PAGES, and AFTER REPORT
PROGRAM_ABORT will prevent further Reports
processing as the result of an error. This is also part of the Errors raised out of exceptions from trigger/
SRW package and has to be raised explicitly with the PL/SQL code can be handled in the exception handler
RAISE command. using the SQLCODE and SQLERRM functions. These are
I’ll classify the errors raised in Reports into the SQL and/or PL/SQL exceptions that are caused by the
following broad categories: failure of SQL DML statements or PL/SQL statements.
• Errors raised out of failure of Report object You should always code an exception handling section
properties. with the WHEN OTHERS clause, wherever SQL and/or
• Errors raised out of exceptions from trigger/ PL/SQL is involved. A typical exception handling section
PL/SQL code (that is, failure of DML statements can be as follows:
or PL/SQL errors).
EXCEPTION WHEN OTHERS THEN
SRW.MESSAGE(100, TO_CHAR(SQLCODE)||' '||SQLERRM);
These errors can and should be handled by writing RAISE SRW.PROGRAM_ABORT;

12 Oracle Professional October 2004 www.pinnaclepublishing.com


SRW.PROGRAM_ABORT and RETURN (FALSE) meaningful string containing the name of the
Use of SRW.PROGRAM_ABORT is required whenever a Report trigger or PL/SQL program unit name can
situation occurs that demands stopping further execution be passed as part of the error message. Also,
of the report. SRW.PROGRAM_ABORT is an exception concatenate SQLERRM to this message when
and has to be raised explicitly as follows: logging in the WHEN OTHERS exception handler.
3. In each PL/SQL query, code a WHEN OTHERS
RAISE SRW.PROGRAM_ABORT; handler and raise SRW.PROGRAM_ABORT in
this handler. The error flag defined in Step 1 should
RETURN (FALSE), on the other hand, behaves be set to -1. Return a NULL REF CURSOR variable
differently. It prevents the wrong action from taking value for the corresponding return value. This is
place, but resumes execution of the report thereafter. shown here:
The only exception to this behavior is when it’s used in
a BEFORE or AFTER PARAMETER FORM Report trigger function <name> return <REF_CURSOR_type> is
or when it’s used as part of the validation code of a v_rc <REF_CURSOR_type>;
begin
Report parameter. .........
A typical scenario where you’d use SRW.PROGRAM_ .........
RETURN (<REF_CURSOR_var>);
ABORT is in a WHEN OTHERS clause, as shown in the EXCEPTION WHEN OTHERS THEN
previous section. On the other hand, RETURN (FALSE) :error_flag := -1;
log_error(...);
can be used to suppress printing of a particular row based raise srw.program_abort;
on a runtime condition. This can be done in the Format RETURN (v_rc);
end;
trigger of the corresponding repeating frame. When a
row meets the specified condition, it’s excluded from
Note how a NULL cursor variable of type
appearing in the Report output, but the Report execution
REF_CURSOR_type is returned on failure.
resumes after that. Here’s an example to show this:
4. In each Formula column, code a WHEN OTHERS
handler and raise SRW.PROGRAM_ABORT in
IF (<condition> THEN
RETURN (FALSE); this handler. The error flag defined in Step 1
ELSE should be set to -1. Return a NULL value for the
RETURN (TRUE);
END IF; corresponding return value if it’s numeric or
character and FALSE if it’s boolean. As mentioned
The ELSE part is necessary; otherwise, no rows in Step 2, log the error in an error-specific table.
after the violating row are printed, even though Report This is shown here:
execution resumes.
function <formula_column_name> Formula
return Varchar2 is
PL/SQL interface for tracking Reports errors begin
As I mentioned earlier, PL/SQL code can be specified .........
.........
in a report at various places—for instance, as part of a return (<normal_value>);
PL/SQL query, in a Group filter or a Formula column, EXCEPTION WHEN OTHERS THEN
:error_flag := -1;
or as part of validation of a Report parameter, Format log_error(...);
trigger, or Report trigger. Let’s take a look at a simple raise srw.program_abort;
RETURN (NULL);
but elegant PL/SQL interface to keep track of Reports end;
errors that are raised from exceptions or abnormal
conditions in PL/SQL code. This technique also works 5. In each Format trigger, code a WHEN OTHERS
for cases where the execution of one part of code is handler and raise SRW.PROGRAM_ABORT in
dependent on the successful completion of another part this handler. The error flag defined in Step 1
of code. should not be set to -1. Return FALSE for the
Here are the steps involved in building the required corresponding return value. As mentioned in Step
PL/SQL interface: 2, log the error in an error-specific table. This is
1. Define a user parameter that corresponds to an shown here:
error flag. Its value can be set to indicate an error
situation, such as setting it to -1 (assuming 0 function <name>FormatTrigger return boolean is
begin
indicates success). .........
2. It’s good programming practice to log any errors .........
RETURN (TRUE);
in an error-specific table so as to help determine EXCEPTION WHEN OTHERS THEN
what the actual error is. When logging the error, a log_error(...);

www.pinnaclepublishing.com Oracle Professional October 2004 13


raise srw.program_abort; function AfterReport return boolean is
RETURN (FALSE); begin
end; IF (:error_flag = -1) THEN
return (false);
END IF;
Note that the error flag isn’t set to -1, because <perform_an_action>;
a Format trigger can’t accept a numeric value IF <not_a_success> THEN
log_error (...);
assignment; it can only return a TRUE or raise srw.program_abort;
FALSE. Also, a function call can’t be used as an RETURN (FALSE);
END IF;
assignment target. -- This is necessary, otherwise, the trigger
6. In each Report trigger that’s defined, use RAISE -- never succeeds.
return (TRUE);
SRW.PROGRAM_ABORT in case there’s an EXCEPTION
occurrence of an abnormal condition that’s WHEN SRW.PROGRAM_ABORT THEN
RETURN FALSE;
application-specific. Also, code an exception handler WHEN OTHERS THEN
to handle the possible SRW.PROGRAM_ABORT raise srw.program_abort;
RETURN FALSE;
exception. In addition, code a WHEN OTHERS end;
handler and raise SRW.PROGRAM_ABORT once
again in this handler. The error flag defined in Step Note that there’s no need to set the error flag in
1 should be set to -1 in each of these handlers. As this trigger.
mentioned in Step 2, log the error in an error-specific
table. This is shown here: Handling input parameters
To handle input parameters, write an AFTER
function BeforeReport return boolean is
begin
PARAMETER FORM trigger, which validates each of
......... the input parameters. If any of the validations fail,
.........
set the error flag to -1, display a meaningful message
IF (<application-specific-condition> is FALSE) THEN for the failed validation using the SRW.MESSAGE
-- This is the error flag defined as a user procedure, raise SRW.PROGRAM_ABORT, and log
-- parameter in the report.
:error_flag := -1; the error in your error table. Your exception handlers
log_error (...); code should be similar to that used for Formula
raise srw.program_abort;
RETURN (FALSE); columns, but return (FALSE) in this case. Here’s how
END IF; the code looks:
-- This is necessary, otherwise, the trigger
-- never succeeds.
return (TRUE); function AfterPForm return boolean is
EXCEPTION indicator NUMBER := 0;
WHEN SRW.PROGRAM_ABORT THEN msg_string VARCHAR2(4000);
:error_flag := -1; begin
RETURN FALSE;
WHEN OTHERS THEN IF <param1_condition_failed> THEN
:error_flag := -1; msg_string := 'AfterPForm:'
raise srw.program_abort; ||'Invalid value for <param1>';
RETURN FALSE; indicator := -2;
end; ELSIF <param2_condition_failed> THEN
msg_string := 'AfterPForm:'
||'Invalid value for <param2>';
The following points are worth noting: indicator := -2;
• SRW.PROGRAM_ABORT is raised twice—once for ......
.........
the IF statement and once for the WHEN OTHERS END IF;
exception handler. IF indicator = -2 THEN
:error_flag := -1;
• The error flag is set to -1 in each of the exception srw.message(100, msg_string);
handlers. log_error(...);
raise srw.program_abort;
return (FALSE);
Why is it necessary to set the error flag? ELSIF indicator = 0 THEN
return (TRUE);
The setting of the error flag is needed for propagating the END IF;
error status to another part of code in the Report that’s EXCEPTION
WHEN SRW.PROGRAM_ABORT THEN
executed after the code where it’s set to -1. For example, :error_flag := -1;
an action taken in an AFTER REPORT trigger shouldn’t RETURN FALSE;
WHEN OTHERS THEN
happen when the execution of code in Formula columns :error_flag := -1;
isn’t successful. However, the AFTER REPORT trigger log_error(...);
raise srw.program_abort;
fires even on failure of Formula columns. A sample of this RETURN FALSE;
code is shown here: end;

14 Oracle Professional October 2004 www.pinnaclepublishing.com


Summary (both from SAMS Publishing). Oracle and Java Development has been
This article describes some techniques for dealing with translated into Japanese and Polish. Bulusu holds an Oracle Masters
error messages in Oracle Reports. Beginning with a credential from Oracle Corporation, is an OCP-Certified Application
discussion of normal error-messaging capabilities, I Developer, and is a double honors graduate in computer science &
engineering and mathematics. He has more than 12 years of experience
described a PL/SQL interface for complex error handling
in application development using Oracle and its various tools,
in Reports. Remember that the techniques discussed here
including Oracle and Java, in both the client/server and Web
will work in Oracle Reports 10g, 9i, or earlier. ▲
environments. He’s presented papers at numerous national and
international conferences and has contributed to lead technical
Bulusu Lakshman is the author of PL/SQL Programming FAQ (from journals in the U.S. and U.K. Bulusu is employed by Compunnel
NetImpress), Oracle9i PL/SQL: A Developer’s Guide (from Apress), and Software Group Inc., a leading technical consulting firm in New Jersey.
Oracle and Java Development and Oracle Developer Forms Techniques balakshman@hotmail.com.

NAME_RESOLVE... THEN
resolve_in_loop (l_schema || '.' || l_name);
END IF;
Continued from page 8 END try_as_synonym;
BEGIN
resolve_in_loop (NAME);
,dblink
,part1_type IF object_number IS NULL
,object_number THEN
); try_as_synonym (NAME);
l_resolved := TRUE; END IF;
EXCEPTION EXCEPTION
WHEN OTHERS WHEN OTHERS
THEN THEN
l_context := l_context + 1; try_as_synonym (NAME);
END; END name_resolve;
END LOOP;
END resolve_in_loop;
First, I’ll move my looping call to NAME_RESOLVE
PROCEDURE try_as_synonym (NAME_IN IN VARCHAR2)
IS
into its own local procedure, resolve_in_loop. Then,
l_schema all_objects.owner%TYPE; I’ll call another local procedure that tries to resolve a
l_name all_objects.object_name%TYPE;
BEGIN
synonym, in the cases when NAME_RESOLVE has failed.
-- Try to resolve as synonym. The main body of my qnr.name_resolve program is now
synonym_resolve (NAME_IN, l_schema, l_name);
short and sweet! My try_as_synonym procedure resolves
IF l_name IS NOT NULL the synonym and then tries to resolve that base object

Don’t miss another issue! Subscribe now and save!


Subscribe to Oracle Professional today and receive a special one-year introductory rate:
Just $179* for 12 issues (that’s $20 off the regular rate)

NAME ❑ Check enclosed (payable to Pinnacle Publishing)


❑ Purchase order (in U.S. and Canada only); mail or fax copy
COMPANY
❑ Bill me later
❑ Credit card: __ VISA __MasterCard __American Express
ADDRESS
CARD NUMBER EXP. DATE

CITY STATE/PROVINCE ZIP/POSTAL CODE


SIGNATURE (REQUIRED FOR CARD ORDERS)

COUNTRY IF OTHER THAN U.S.


Detach and return to:
Pinnacle Publishing ▲ 316 N. Michigan Ave. ▲ Chicago, IL 60601
E-MAIL Or fax to 312-960-4106

* Outside the U.S. add $30. Orders payable in


INS4
PHONE (IN CASE WE HAVE A QUESTION ABOUT YOUR ORDER) U.S. funds drawn on a U.S. or Canadian bank.

Pinnacle, A Division of Lawrence Ragan Communications, Inc. ▲ 800-493-4867 x.4209 or 312-960-4100 ▲ Fax 312-960-4106

www.pinnaclepublishing.com Oracle Professional October 2004 15


information, using my resolve_in_loop program. inside ALL_SYNONYMS than with DBMS_UTILITY
Using this new version and a much expanded test .NAME_RESOLVE itself.
script (see qnr_setup.sql and qnr_test.sql), I find that all With all of the facts clearly laid out, I then solved the
of my object references are resolved correctly, including problem by building my own version of DBMS_UTILITY
recursive public synonyms, as you can see from this .NAME_RESOLVE on top of that built-in. I produced
partial output: my solution in a series of steps, making it more robust
each time, and refactoring to keep the code clean and easy
ps_ps_emp ==> owner: U1 Table: EMP to understand.
ps_ps_dept ==> owner: U1 Table: DEPT
ps_ps_pkg ==> owner: U1 Package: PKG Finally, I have a new utility that extends my toolbox
ps_ps_proc ==> owner: U1 Procedure: PROC of reusable code. I’m going to apply this program to
ps_ps_func ==> owner: U1 Function: FUNC
ps_ps_objt ==> owner: U1 13: OBJT Qnxo so that I can close out a few bugs, and then place
this in the Qnxo PL/SQL repository so that everyone
Identify, analyze, solve, extend who downloads and uses Qnxo can take advantage of
Whew. That was a long and somewhat convoluted qnr.name_resolve as well. ▲
journey. Let’s recap. I started by identifying a problem:
DBMS_UTILITY.NAME_RESOLVE was very useful, but 410FEUER.ZIP at www.pinnaclepublishing.com
it didn’t handle all of the scenarios I needed to support
Steven Feuerstein is considered one of the world’s leading experts
in Qnxo. I wanted to come up with a solution to this
on the Oracle PL/SQL language, having written nine books on PL/SQL,
problem, but first I took some time to analyze the
including Oracle PL/SQL Programming and Oracle PL/SQL Best Practices
situation: How does DBMS_UTILITY.NAME_RESOLVE
(all from O’Reilly Media). Steven has been developing software since
work? Where exactly does it fail? What is the cause of 1980 and serves as a Senior Technology Advisor to Quest Software.
the problem? Interestingly, I found that DBMS_UTILITY His current projects include Qnxo (www.qnxo.com), a new active
.NAME_RESOLVE did more than was advertised mentoring software product, and the Refuser Solidarity Network
(resolves names of tables as well as stored program units), (www.refusersolidarity.net), which supports the Israeli military refuser
plus a point of failure had more to do with data issues movement. steven@stevenfeuerstein.com.

October 2004 Downloads


• 410FEUER.ZIP—Source code to accompany Steven Feuerstein’s article, “Resolving NAME_RESOLVE Issues.”

For access to current and archive content and source code, log in at www.pinnaclepublishing.com.

Editor: Garry Chan (gchan@procaseconsulting.com) Oracle Professional (ISSN 1525-1756)


Contributing Editor: Bryan Boulton is published monthly (12 times per year) by:
CEO & Publisher: Mark Ragan
Pinnacle Publishing
Group Publisher: Michael King A Division of Lawrence Ragan Communications, Inc.
Executive Editor: Farion Grove 316 N. Michigan Ave., Suite 300
Chicago, IL 60601
Questions?
POSTMASTER: Send address changes to Lawrence Ragan Communications, Inc., 316
Customer Service: N. Michigan Ave., Suite 300, Chicago, IL 60601.

Phone: 800-493-4867 x.4209 or 312-960-4100 Copyright © 2004 by Lawrence Ragan Communications, Inc. All rights reserved. No part
Fax: 312-960-4106 of this periodical may be used or reproduced in any fashion whatsoever (except in the
Email: PinPub@Ragan.com case of brief quotations embodied in critical articles and reviews) without the prior
written consent of Lawrence Ragan Communications, Inc. Printed in the United States
of America.
Advertising: RogerS@Ragan.com
Oracle, Oracle 8i, Oracle 9i, PL/SQL, and SQL*Plus are trademarks or registered trademarks of
Editorial: FarionG@Ragan.com Oracle Corporation. Other brand and product names are trademarks or registered trademarks
of their respective holders. Oracle Professional is an independent publication not affiliated
Pinnacle Web Site: www.pinnaclepublishing.com with Oracle Corporation. Oracle Corporation is not responsible in any way for the editorial
policy or other contents of the publication.

Subscription rates This publication is intended as a general guide. It covers a highly technical and complex
subject and should not be used for making decisions concerning specific products or
applications. This publication is sold as is, without warranty of any kind, either express or
United States: One year (12 issues): $199; two years (24 issues): $348 implied, respecting the contents of this publication, including but not limited to implied
Other:* One year: $229; two years: $408 warranties for the publication, performance, quality, merchantability, or fitness for any particular
purpose. Lawrence Ragan Communications, Inc., shall not be liable to the purchaser or any
Single issue rate: other person or entity with respect to any liability, loss, or damage caused or alleged to be
caused directly or indirectly by this publication. Articles published in Oracle Professional
$27.50 ($32.50 outside United States)* reflect the views of their authors; they may or may not reflect the view of Lawrence Ragan
Communications, Inc. Inclusion of advertising inserts does not constitute an endorsement by
* Funds must be in U.S. currency. Lawrence Ragan Communications, Inc., or Oracle Professional.

16 Oracle Professional October 2004 www.pinnaclepublishing.com

You might also like