Quantcast
Channel: Obsessed with Oracle PL/SQL
Viewing all 312 articles
Browse latest View live

One exception handler for all packaged subprograms?

$
0
0

This question was submitted as a comment in one of my videos today:
Do we have to include an exception section for each individual subprogram or can we have a single handler for all subprograms?
The quick answer is: if you want an exception raised in a procedure or function defined in a package, you need to add an exception to that subprogram.

I can certainly see why this question would come up. A package body can have its own exception handler. Here's an example:

CREATE OR REPLACE PACKAGE pkg
AUTHID DEFINER
IS
PROCEDURE proc;
END;
/

CREATE OR REPLACE PACKAGE BODY pkg
IS
PROCEDURE proc
IS
BEGIN
RAISE NO_DATA_FOUND;
END;
BEGIN
NULL;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line ('Was proc executed?');
END;
/

And it kinda, sorta looks like if I execute the following block, I will see "Was proc execute?" on my screen.

BEGIN
pkg.proc;
END;
/

But I would be wrong. Instead, I will see:

ORA-01403: no data found
ORA-06512: at "QDB_PROD.PKG", line 6

But, but, but....what's going on here? I explicitly handle NO_DATA_FOUND right there at the bottom of the package.

What's going on is that the exception handler only looks like it is a handler for the entire package. In actually, it can only possibly handle exceptions raised by the executable code between the BEGIN and EXCEPTION keywords underneath the proc procedure.

This is called the initialization section of the package.  It is designed - you guessed it - initialize the state of the package (set values, perform QA checks, etc.). It runs once per session to initialize the package.

HOWEVER: in stateless environments like websites, this code may well execute each time a user references a package element (runs a subprogram, gets the value of a variable). So these days, it would be rare to find an initialization section in a package, and probably something to generally avoid.

The bottom line when it comes to exception handling for subprograms in a package is simple: you must include an exception section in each of those subprograms. The code that is executed in each of those subprograms could be shared. And should be. You should use a generic error logging API like the open source Logger, so that everyone handles, logs and re-raises exceptions in the same way.



Viewing conditionally compiled code: what will be run?

$
0
0
In the previous (first) post in my series on conditional compilation, I covered use cases and presented some simple examples.

In this post, I show you how you can confirm what code is actually going to be executed after compilation. Without conditional compilation, this is of course a silly exercise. The code that is executed is the same as the code you see in your editor.

But with conditional compilation, the code that is compiled and therefore runs could depend on any of the following:
  • The version of the database in which it is compiled
  • The values of user-defined conditional compilation flags
  • The values of pre-defined (system) conditional compilation flags, like ##plsq1_optimize_level
It can be a little bit nerve-wracking for a developer to not be entirely sure what is going to execute, so we provide the DBMS_PREPROCESSOR package, with its two subprograms:
  • print_post_processed_source - display the post-processed code on your screen
  • get_post_processed_source - return the post-processed code as an array.
If you are wondering why the name of the package contains "preprocessor" but its only subprograms contain "post_processed"....well, what's life without a mystery or two? :-)

The "print" procedure has three overloadings:

1. Prints post-processed source text of a stored PL/SQL unit:

DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE (
   object_type    IN VARCHAR2,
   schema_name    IN VARCHAR2,
   object_name    IN VARCHAR2);

2. Prints post-processed source text of a compilation unit:

DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE (
   source IN VARCHAR2);
 
3. Prints post-processed source text of an INDEX-BY table containing the source text of the compilation unit:

DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE (
   source IN source_lines_t);

Let's try it out. My optimization level is set to the default: 2. I execute these statements:
CREATE OR REPLACE PROCEDURE post_processed
IS
BEGIN
$IF $$plsql_optimize_level = 1
$THEN
-- Slow and problematic
NULL;
$ELSE
-- Fast and modern and easy
NULL;
$END
END post_processed;
/

BEGIN
DBMS_PREPROCESSOR.print_post_processed_source (
'PROCEDURE',
SYS_CONTEXT ('userenv', 'current_schema'),
'POST_PROCESSED');
END;
/
and I see this output:
PROCEDURE post_processed
IS
BEGIN





-- Fast and modern and easy
NULL;

END post_processed;
I then set the optimization level to 1 for this procedure as I recompile it:
ALTER PROCEDURE post_processed COMPILE plsql_optimize_level=1
/
The output then changes as follows:
BEGIN
DBMS_PREPROCESSOR.print_post_processed_source (
'PROCEDURE',
SYS_CONTEXT ('userenv', 'current_schema'),
'POST_PROCESSED');
END;
/

PROCEDURE post_processed
IS
BEGIN


-- Slow and problematic
NULL;




END post_processed;
Take a close and careful look at these two sets of output. Notice how carefully the white space (vertical - lines, and horizontal - spaces) is preserved. This is critical.

This is the code that will be executed. So PL/SQL needs to make sure that if an exception is raised and error stack or back trace is displayed/logged, the line and column numbers in those messages reflect what you see in your original source code, namely:
CREATE OR REPLACE PROCEDURE post_processed
IS
BEGIN
$IF $$plsql_optimize_level = 1
$THEN
-- Slow and problematic
NULL;
$ELSE
-- Fast and modern and easy
NULL;
$END
END post_processed;
I hope you can see that is, in fact, the case.

I could show you examples of calling the other overloadings of the print procedure, but I think you get the idea. Here's essentially the same behavior as the print procedure, but using the get function instead - and encapsulate into my very own procedure, adding line numbers:
CREATE PROCEDURE show_code_for (tp IN VARCHAR2, nm IN VARCHAR2)
IS
l_postproc_code DBMS_PREPROCESSOR.source_lines_t;
l_row PLS_INTEGER;
BEGIN
l_postproc_code :=
DBMS_PREPROCESSOR.get_post_processed_source (
tp,
SYS_CONTEXT ('userenv', 'current_schema'),
nm);
l_row := l_postproc_code.FIRST;

WHILE (l_row IS NOT NULL)
LOOP
DBMS_OUTPUT.put_line (
LPAD (l_row, 3)
|| ' - '
|| RTRIM (l_postproc_code (l_row), CHR (10)));
l_row := l_postproc_code.NEXT (l_row);
END LOOP;
END;
/
and I put it to use:
BEGIN
show_code_for ('PROCEDURE', 'POST_PROCESSED');
END;
/

1 - PROCEDURE post_processed
2 - IS
3 - BEGIN
4 -
5 -
6 - -- Slow and problematic
7 - NULL;
8 -
9 -
10 -
11 -
12 - END post_processed;
I hope you find this helpful.

Resources

Comprehensive white paper: a great starting place - and required reading - for anyone planning on using conditional compilation in production code

Conditional compilation scripts on LiveSQL

Tim Hall (Oracle-BASE) coverage of conditional compilation

Conditional compilation documentation

My Oracle Magazine article on this topic

Conditional Compilation Series

1. An introduction to conditional compilation
2. Viewing conditionally compiled code: what will be run?

Nine Years at the Oracle Dev Gym

$
0
0
Waaaaay back in 2010, on April 8 to be specific, I started a website called the PL/SQL Challenge. It featured a daily PL/SQL quiz (yes, that's right - a new quiz every weekday!) and gave Oracle Database developers a way to both deepen and demonstrate their expertise. Players were ranked and competed for top honors in our annual championships.

Not quite as waaaaay back, in 2014, I rejoined Oracle Corporation after 22 years away (from the company, not from the technology). The PL/SQL Challenge came with me, and a year later we rebranded it as the Oracle Dev Gym.

Today, we offer quizzes on SQL, PL/SQL, database design, logic, Java and Application Express. We've added workouts and classes.

Yesterday we celebrated the ninth anniversary of the Dev Gym / PL/SQL Challenge. And my oh my but Oracle Database developers have been busy!


Here are some stats from those nine years:
  • Almost 35,000 developers and DBAs have taken quizzes on the site, a total of 1.27M answers submitted.
  • They spent a total of over 118 YEARS of their cumulative time taking quizzes, workouts and classes.
  • Their average grade on quizzes is 75% (we have a lot of tough quizzes!).
  • Roughly 3,500 developers have been active on the site in the last three months.
  • We've had almost 35,000 sign-ups for our Dev Gym classes (the most popular being Chris Saxon's Databases for Developers classes).
  • Individual workouts have also been very popular, with just under 33,000 taken.
I'd also like to give a shout-out and big thanks to the following major contributors to the success of the Dev Gym:

  • Kim Berg Hansen, SQL quizmaster, author of hundreds of amazing SQL quizzes
  • Chris Saxon, Database Design quizmaster, creator of Databases for Developers class series
  • Rafael del Nero, Java quizmaster (new to 2018!), and Java Champion
  • Eli Feuerstein, our lead APEX developer
  • Elic (Vitaliy Lyanchevskiy), our ace database quiz reviewer, who has had an enormous positive impact on our quiz quality
  • Livo Curzola, our logic quiz reviewer, for his long and careful career checking over Eli's quizzes

The Dev Gym has become an essential part of the learning experience for thousands of developers. They've discovered new features of the database, learned from other developers, and even written their own quizzes for everyone to play.

If you haven't yet tried the Dev Gym, come check it out! Take a quiz, sign up for a class (they are on-demand, take them at your own pace) or take a workout. 



Writing code to support multiple versions of Oracle Database

$
0
0

3rd in a series on conditional compilation. See end of post for links to all posts in the series.

Do you write code that must run on more than one version of Oracle Database? This is almost always the case for suppliers of "off the shelf" applications. And when confronted with this reality, most developers choose between these two options:

Use only those features available in all versions ("lowest common denominator" or LCD programming).
or
Maintain separate copies of the code for each supported version, so you can take advantage of new features in later versions of the database ("sure to create a mess" or SCAM programming).

And let's face it, both have some serious drawbacks.

The LCD approach ensures that your code will compile on all supported versions. But you will sacrifice the ability to take advantage of new features in the later versions. That can be a high price to pay.

The SCAM approach, well, "sure to create a mess" says it all. What's the chance that you will be able to keep 2, 3, or 4 copies of the same code up to date with all bug fixes, enhancements, comments, etc., along with the special-purpose code written to leverage features in that specific version?

Fortunately, there is a third and better way: use conditional compilation so that you can write and maintain your code in a single file / program unit, but still take maximum advantage of each version's cool new features.

Actually, you use conditional compilation and the DBMS_DB_VERSION package. Let's check them out!

Here's the entire code for the DBMS_DB_VERSION package for Oracle Database 12c:
PACKAGE DBMS_DB_VERSION IS
VERSION CONSTANT PLS_INTEGER := 12; -- RDBMS version number
RELEASE CONSTANT PLS_INTEGER := 2; -- RDBMS release number
ver_le_9_1 CONSTANT BOOLEAN := FALSE;
ver_le_9_2 CONSTANT BOOLEAN := FALSE;
ver_le_9 CONSTANT BOOLEAN := FALSE;
ver_le_10_1 CONSTANT BOOLEAN := FALSE;
ver_le_10_2 CONSTANT BOOLEAN := FALSE;
ver_le_10 CONSTANT BOOLEAN := FALSE;
ver_le_11_1 CONSTANT BOOLEAN := FALSE;
ver_le_11_2 CONSTANT BOOLEAN := FALSE;
ver_le_11 CONSTANT BOOLEAN := FALSE;
ver_le_12_1 CONSTANT BOOLEAN := FALSE;
ver_le_12_2 CONSTANT BOOLEAN := TRUE;
ver_le_12 CONSTANT BOOLEAN := TRUE;
END DBMS_DB_VERSION;
The package contains two "absolute" constants: the version and release numbers. it then contains a set of "relative" constants, basically telling you, true or false, if the current version is less than or equal to the version specified by the constant name.

If I was a betting man, I'd bet a whole lot of money than you could figure out what this package looks like in Oracle Database 18c and 9c. If not, run this query:
  SELECT LPAD (line, 2, '0') || ' - ' || text
FROM all_source
WHERE owner = 'SYS' AND name = 'DBMS_DB_VERSION'
ORDER BY line
One Program for Multiple Versions
The DBMS_DB_VERSION package makes it really easy to ensure that each installation of your code takes full advantage of the latest and greatest features.

Consider the package body below (full code available on LiveSQL). Starting with Oracle Database 10g Release 2, you can use INDICES OF with FORALL to handle spare bind arrays elegantly. Prior to that, you would have to "densify" the collection and get rid of any gaps.

A simple application of the $IF statement along with a reference to the appropriate DBMS_DB_VERSION constant, and job done.
CREATE OR REPLACE PACKAGE BODY pkg
IS
PROCEDURE insert_rows ( rows_in IN ibt )
IS
BEGIN
$IF DBMS_DB_VERSION.VER_LE_10_1
$THEN
/* Remove gaps in the collection */
DECLARE
l_dense t;
l_index PLS_INTEGER := rows_in.FIRST;
BEGIN
WHILE (l_index IS NOT NULL)
LOOP
l_dense (l_dense.COUNT + 1) := rows_in (l_index);
l_index := rows_in.NEXT (l_index);
END LOOP;

FORALL indx IN l_dense.FIRST .. l_dense.LAST
INSERT INTO t VALUES l_dense (indx);
END;
$ELSE
/* Use the very cool INDICES OF feature to skip over gaps. */
FORALL indx IN INDICES OF rows_in
INSERT INTO t VALUES rows_in (indx);
$END
END insert_rows;
END;
Not Just for Oracle Versions
You can use this same technique to manage deployments of different versions of your code (different for different versions of your application or different for different customers). You can create your own variation on DBMS_DB_VERSION, or use your own flags, and use it in exactly the same way in your code base.

You will just need to make that your version package is always available, or your flags are always set so that you end up with the right code for the right customer.

In addition, you can only use constants with Boolean or integer values in your $IF conditions., and those conditions must be static expressions, meaning their values do not vary when initialized in the package.

Perhaps an example would help here. :-)

Suppose you wanted to use conditional compilation to automatically enable or disable features in the application depending on the customer.

You might create code like this:
CREATE OR REPLACE FUNCTION eligible_for_use (type_in       IN VARCHAR2,
customer_in IN VARCHAR2)
RETURN BOOLEAN
AUTHID DEFINER
IS
BEGIN
RETURN type_in = 'TURBO' AND customer_in = 'ACME Inc';
END;
/

CREATE OR REPLACE PACKAGE include_features
AUTHID DEFINER
IS
include_turbo CONSTANT BOOLEAN := eligible_for_use ('TURBO', USER);
END;
/

CREATE OR REPLACE PROCEDURE go_turbo
AUTHID DEFINER
IS
BEGIN
$IF include_features.include_turbo
$THEN
DBMS_OUTPUT.put_line ('all systems go');
$ELSE
NULL;
$END
END;
/
So far, so good. But when I try to apply it, I see this error:
CREATE OR REPLACE PROCEDURE go_turbo
AUTHID DEFINER
IS
BEGIN
$IF include_features.include_turbo
$THEN
DBMS_OUTPUT.put_line ('all systems go');
$ELSE
NULL;
$END
END;
/

PLS-00174: a static boolean expression must be used
The value of the include_turbo constant is not set until the package is initialized, and that's too late when it comes to conditional compilation.

So if you want to take this approach, you will need to generate (or hand-code, but that seems like a stretch) the (in this case) include_features package so that each customer receives its own version of the package, as in:
CREATE OR REPLACE PACKAGE include_features
AUTHID DEFINER
IS
/* Generated 2019-04-06 13:44:50 for ACME Inc - Customer ID 147509 */
include_turbo CONSTANT BOOLEAN := TRUE;
END;
/
and then when go_turbo and other program units are compiled at the customer site, the correct features will be made available.
Tips for Doing It Right
Suppose I want the following procedure to compile and run on versions 12.2, 18.x and 19.x. Will it work as desired?
CREATE OR REPLACE PROCEDURE lots_of_versions
AUTHID DEFINER
IS
BEGIN
$IF dbms_db_versions.ver_le_19 AND NOT dbms_db_versions.ver_le_18
$THEN
DBMS_OUTPUT.put_line ('Having fun on 19c!');
$ELSIF dbms_db_versions.ver_le_18 AND NOT dbms_db_versions.ver_le_12
$THEN
DBMS_OUTPUT.put_line ('Good to go on 18.x');
$ELSIF NOT dbms_db_versions.ver_le_12_1
$THEN
DBMS_OUTPUT.put_line ('Good to go on 12.2');
$ELSE
raise_application_error (-20000, 'Not supported');
$END
END;
/
No - when I try to compile this on 12.2, 18.1 and 18.2, it will fail because those 19c-related constants are not defined in the earlier versions of the DBMS_DB_VERSION package.

This approach will work much better:
CREATE OR REPLACE PROCEDURE lots_of_versions
AUTHID DEFINER
IS
BEGIN
$IF dbms_db_versions.ver_le_12_2
$THEN
raise_application_error (-20000, '12.1 is not supported');
$ELSIF dbms_db_versions.ver_le_18_1
$THEN
DBMS_OUTPUT.put_line ('Good to go on 12.2');
$ELSIF dbms_db_versions.ver_le_19_1
$THEN
DBMS_OUTPUT.put_line ('Good to go on 18.x');
$ELSE
DBMS_OUTPUT.put_line ('Having fun on 19c!');
$END
END;
/
In other words, go from lowest version to highest version in any $IF statements to ensure that the referenced constant will always be defined (or never reached).
Conditional Compilation Series
1. An introduction to conditional compilation
2. Viewing conditionally compiled code: what will be run?
3. Writing code to support multiple versions of Oracle Database

Setting and using your own conditional compilation flags

$
0
0
This post is the fourth in my series on conditional compilation. You will find links to the entire series at the bottom.

In this post, I explore how to set and use conditional compilation flags (also known as inquiry directives and referred to below as ccflags) used in $IF statements, and control which code will be included or excluded when compilation occurs.

In theory, you don't need ccflags at all. You could just create a package with static constants, like DBMS_DB_VERSION, and then reference those constants in $IF statements. That makes sense when many different compilation units (packages, procedures, triggers, functions, object types) need to be consistently controlled by the same settings. With the package approach, when you change a value for the constant, the dependent program units will be invalidated, and upon recompilation, will be compiled with the new values.

If, on the other hand, you want to add conditional compilation logic to a single unit, or a handful, then you might find a package dedicated to this purpose to be a bit too much. For this situation, you might consider using an inquiry directive instead. You know you're looking at a ccflag or inquiry directive, when you see an identifier prefixed by "$$".

An inquiry directive gets its value from the compilation environment, in three different ways:
  1. from a PL/SQL compilation parameter
  2. from a predefined ccflag
  3. from a user-defined ccflag

Compilation Parameters

PL/SQL offers the following compilation parameters as inquiry directives:

$$PLSCOPE_SETTINGS - the current settings for PL/Scope for a program unit

$$PLSQL_CCFLAGS - the current settings for user-defined ccflags for a program unit

$$PLSQL_CODE_TYPE - the type of code, NATIVE or INTERPRETED

$$PLSQL_OPTIMIZE_LEVEL - the optimization level used to compile the program unit

$$PLSQL_WARNINGS - compile-time warnings setting  for the program unit

$$NLS_LENGTH_SEMANTICS - NLS length semantics for the program unit

Here's a procedure you can use to display the current values for these parameters in your session (available for download in this LiveSQL script):
BEGIN
DBMS_OUTPUT.PUT_LINE('$$PLSCOPE_SETTINGS = ' || $$PLSCOPE_SETTINGS);
DBMS_OUTPUT.PUT_LINE('$$PLSQL_CCFLAGS = ' || $$PLSQL_CCFLAGS);
DBMS_OUTPUT.PUT_LINE('$$PLSQL_CODE_TYPE = ' || $$PLSQL_CODE_TYPE);
DBMS_OUTPUT.PUT_LINE('$$PLSQL_OPTIMIZE_LEVEL = ' || $$PLSQL_OPTIMIZE_LEVEL);
DBMS_OUTPUT.PUT_LINE('$$PLSQL_WARNINGS = ' || $$PLSQL_WARNINGS);
DBMS_OUTPUT.PUT_LINE('$$NLS_LENGTH_SEMANTICS = ' || $$NLS_LENGTH_SEMANTICS);
END;
This same information is stored persistently in the database for every program unit and is available through the USER/ALL_PLSQL_OBJECT_SETTINGS view, as in:
SELECT *
FROM all_plsql_object_settings
WHERE name = 'PROGRAM_NAME'

Predefined CCFlags

As of Oracle Database 19c, the ccflags automatically defined for any program unit are:

$$PLSQL_LINE - A PLS_INTEGER literal whose value is the number of the source line on which the directive appears in the current PL/SQL unit.

$$PLSQL_UNIT - A VARCHAR2 literal that contains the name of the current PL/SQL unit. If the current PL/SQL unit is an anonymous block, then $$PLSQL_UNIT contains a NULL value.

$$PLSQL_UNIT_OWNER - A VARCHAR2 literal that contains the name of the owner of the current PL/SQL unit. If the current PL/SQL unit is an anonymous block, then $$PLSQL_UNIT_OWNER contains a NULL value.

$$PLSQL_UNIT_TYPE - A VARCHAR2 literal that contains the type of the current PL/SQL unit—ANONYMOUS BLOCK, FUNCTION, PACKAGE, PACKAGE BODY, PROCEDURE, TRIGGER, TYPE, or TYPE BODY. Inside an anonymous block or non-DML trigger, $$PLSQL_UNIT_TYPE has the value ANONYMOUS BLOCK.

You might be disappointed, however, in how you can use these flags. Selective directives ($IF) must contain only static expressions, which means they are resolved at compilation time, which means in the world of Oracle Database, that you can only work with integer and Boolean values.

So this code will compile:
PROCEDURE test_cc
IS
BEGIN
$IF $$PLSQL_UNIT IS NULL $THEN
-- Include this line
$END
NULL;
END;
but this gives me a headache:
PROCEDURE test_cc
IS
BEGIN
$IF $$PLSQL_UNIT = 'TEST_CC' $THEN
-- Include this line
$END
NULL;
END;

PLS-00174: a static boolean expression must be used
Consequently, these predefined flags are mostly used for tracing and logging purposes, as in:
CREATE OR REPLACE PROCEDURE using_predefined_ccflags
AUTHID DEFINER
IS
myvar INTEGER := 100;
BEGIN
DBMS_OUTPUT.put_line (
'On line ' || $$plsql_line || ' value of myar is ' || myvar);
RAISE PROGRAM_ERROR;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Failure in program unit ' ||
$$plsql_unit_owner || '.' ||
$$plsql_unit_type || '.' ||
$$plsql_unit);
END;
/

BEGIN
using_predefined_ccflags;
END;
/

Procedure created.
Statement processed.
On line 7 value of myar is 100
Failure in program unit SCOTT.PROCEDURE.USING_PREDEFINED_CCFLAGS

User-defined CCFLags

User-defined ccflags are defined by setting the PLSQL_CCFlags parameter before your program unit is compiled. You can set ccflags in this way at the session or program unit level. Here are some examples:
ALTER SESSION SET plsql_ccflags = 'Flag1:10, Flag2:true'
/

ALTER PROCEDURE myproc
COMPILE SET PLSQL_CCFlags = 'Flag1:10, Flag2:true' REUSE SETTINGS
/
Since these are user-defined, you can use them for, well, just about anything. You can turn on and off tracing, or the amount of tracing. You can include or exclude chunks of code depending on features your users have paid for. You can expose otherwise private packaged subprograms in the package specification so you can test them directly, and then make sure they are hidden in production.

Here's an example of that use case (also in LiveSQL):
ALTER SESSION SET PLSQL_CCFLAGS = 'show_private_joke_programs:TRUE'
/

CREATE OR REPLACE PACKAGE sense_of_humor
IS
PROCEDURE calc_how_funny (
joke_in IN VARCHAR2
, funny_rating_out OUT NUMBER
, appropriate_age_out OUT NUMBER
);
$IF $$show_private_joke_programs $THEN
FUNCTION humor_level ( joke_in IN VARCHAR2 )
RETURN NUMBER;

FUNCTION maturity_level ( joke_in IN VARCHAR2 )
RETURN NUMBER;
$END
END;
/

CREATE OR REPLACE PACKAGE BODY sense_of_humor
IS
FUNCTION humor_level ( joke_in IN VARCHAR2 )
RETURN NUMBER
IS
BEGIN
-- Some really interesting code here...
RETURN NULL;
END humor_level;

FUNCTION maturity_level ( joke_in IN VARCHAR2 )
RETURN NUMBER
IS
BEGIN
-- Some really interesting code here...
RETURN NULL;
END maturity_level;

PROCEDURE calc_how_funny (
joke_in IN VARCHAR2
, funny_rating_out OUT NUMBER
, appropriate_age_out OUT NUMBER
)
IS
BEGIN
funny_rating_out := humor_level ( joke_in );
appropriate_age_out := maturity_level ( joke_in );
END calc_how_funny;
END;
/

Tips for Using CCFlags

Don't Assume Anything
By which I mean: don't assume that user-defined ccflags have been set before compilation for use in production.

If a ccflag has not been defined with an ALTER command, it is evaluated to NULL.

So the behavior of your code in production should be, whenever possible, determined by the value of all of your ccflags set to NULL. That way, if for any reason the code is compiled without the proper ALTER statement run, you will not be dealing with a big mess.

Keep CCFlag Settings with Code
One way to avoid the problem described above is to keep all the ccflag settings needed for a particular program unit in the file for that unit itself. Then, whenever you need to compile that program unit, the ccflags are set first, and then the compilation occurs.

Check for Undefined CCFlags

Use the compile-time warnings feature of PL/SQL to reveal any ccflags that have not been set at the time of compilation. Here's an example:
ALTER SESSION SET plsql_warnings = 'ENABLE:ALL'
/

CREATE OR REPLACE PACKAGE sense_of_humor AUTHID DEFINER
IS
PROCEDURE calc_how_funny (
joke_in IN VARCHAR2
, funny_rating_out OUT NUMBER
, appropriate_age_out OUT NUMBER
);
$IF $$show_private_joke_programs $THEN
FUNCTION humor_level ( joke_in IN VARCHAR2 )
RETURN NUMBER;
$END
END;
/

PLW-06003: unknown inquiry directive '$$SHOW_PRIVATE_JOKE_PROGRAMS'

Conditional Compilation Series

1. An introduction to conditional compilation
2. Viewing conditionally compiled code: what will be run?
3. Writing code to support multiple versions of Oracle Database
4. Setting and using your own conditional compilation flags

Does the PL/SQL compiler remove code that is used?

$
0
0
Yes. No. Sort of.

 It's (not all that) complicated.

This question hit my Twitter feed yesterday:
When you enable all warnings, have you ever seen a "PLW-06006-- uncalled procedure removed" (lots of them), when they surely are called?
Now that, I must admit, has to be a little bit concerning. You write code, you know it is going to, or should be, executed, and yet the PL/SQL compiler tells you it's been removed?

OK, OK, calm down. Everything is just fine.

Here's the explanation:
  • The optimizer performed an inlining optimization, so all the code for that procedure (or function) was moved to where it is invoked.
  • The "original" nested or private subprogram that you wrote (and, don't worry, is still and always will be in the source code of your program unit) is, truth be told, never going to be called. 
  • So then the compiler removed it (did not include it in the compiled code - which is not PL/SQL code any longer).
Let's take a look at some code, and what happens when we run it (you can see this for yourselves via my LiveSQL script):
ALTER SESSION SET plsql_optimize_level = 3
/

Statement processed

ALTER SESSION SET plsql_warnings='enable:all'
/

Statement processed

CREATE OR REPLACE PROCEDURE show_inlining
AUTHID DEFINER
IS
FUNCTION f1 (p NUMBER)
RETURN PLS_INTEGER
IS
BEGIN
RETURN p * 10;
END;

FUNCTION f2 (p BOOLEAN)
RETURN PLS_INTEGER
IS
BEGIN
RETURN CASE WHEN p THEN 10 ELSE 100 END;
END;

FUNCTION f3 (p PLS_INTEGER)
RETURN PLS_INTEGER
IS
BEGIN
RETURN p * 10;
END;
BEGIN
DBMS_OUTPUT.put_line ('f1 called: ' || f1 (1));

PRAGMA INLINE (f2, 'YES');
DBMS_OUTPUT.put_line ('f2 called: ' || TO_CHAR (f2 (TRUE) + f2 (FALSE)));

PRAGMA INLINE (f3, 'NO');
DBMS_OUTPUT.put_line ('f3 called: ' || f3 (55));
END;
/

Warning: PROCEDURE SHOW_INLINING
Warning: PROCEDURE SHOW_INLINING
Line/Col: 4/4 PLW-06027: procedure "F1" is removed after inlining
Line/Col: 10/4 PLW-06027: procedure "F2" is removed after inlining
Line/Col: 22/4 PLW-06005: inlining of call of procedure 'F1' was done
Line/Col: 25/4 PLW-06005: inlining of call of procedure 'F2' was done
Line/Col: 25/4 PLW-06004: inlining of call of procedure 'F2' requested
Line/Col: 25/4 PLW-06005: inlining of call of procedure 'F2' was done
Line/Col: 25/52 PLW-06004: inlining of call of procedure 'F2' requested
Line/Col: 28/4 PLW-06008: call of procedure 'F3' will not be inlined

BEGIN
show_inlining;
END;
/

f1 called: 10
f2 called: 110
f3 called: 550
Please note: all the functions (f1 - f3) were executed!

As you can see, the warnings feedback ("PLW" stands for PL/SQL Warning) tells the story: procedures are removed after inlining.

Though I suppose we could be a little more explicit - and reassuring - and say:
PLW-06027: procedure "F1" is removed after inlining....
but just from the compiled code, not the source code of your program unit!

Use RETURNING Clause to Avoid Unnecessary SQL Statements

$
0
0
The RETURNING clause allows you to retrieve values of columns (and expressions based on columns) that were modified by an insert, delete or update. Without RETURNING you would have to run a SELECT statement after the DML statement is completed, in order to obtain the values of the changed columns. So RETURNING helps avoid another roundtrip to the database, another context switch in a PL/SQL block.

The RETURNING clause can return multiple rows of data, in which case you will use the RETURNING BULK COLLECT INTO form.

You can also call aggregate functions in the RETURNING clause to obtain sums, counts and so on of columns in multiple rows changed by the DML statement.

Finally, you can also use RETURNING with EXECUTE IMMEDIATE (for dynamically constructed and executed SQL statements).

Run this LiveSQL script to see all of the statements shown below "in action."

First, I will create a table to use in my scripts:
CREATE TABLE parts ( 
part_number INTEGER
, part_name VARCHAR2 (100))
/

BEGIN
INSERT INTO parts VALUES (1, 'Mouse');
INSERT INTO parts VALUES (100, 'Keyboard');
INSERT INTO parts VALUES (500, 'Monitor');
COMMIT;
END;
/

Which rows did I update? (the wrong way)

The code below issues the update and then in a separate SQL statement retrieves the part number of the row that was just modified - but only by reproducing the logic ("partname = UPPER (partname)") in the WHERE clause.

This means that I have introduced repetition in my code, and also inefficiency (an extra context switch). This is logically equivalent to using the RETURNING clause, but definitely inferior to RETURNING.

And keep in mind that if you use a SELECT after your DML statement to determine if the correct changes were made, you need to be very careful about how you specify the WHERE clause of your query to be sure that you identify the same rows that were (possibly) changed.
DECLARE 
l_num PLS_INTEGER;
BEGIN
UPDATE parts
SET part_name = UPPER (part_name)
WHERE part_name LIKE 'K%';

SELECT part_number
INTO l_num
FROM parts
WHERE part_name = UPPER (part_name);

DBMS_OUTPUT.put_line (l_num);
END;

/
Note: John Keymer @keymer_john pointed out the following on Twitter: I'd argue that these two approaches are not "logically equivalent". Using returning is read consistent, whereas if you do an update and then a select as shown above, that query could potentially return freshly committed rows from other sessions that you *didn't* touch in the update.

Which rows did I update? (the right way)

Don't do an unnecessary SELECT simply to see/verify the impact of a non-query DML statement! Just add RETURNING to the statement and get information back from that single context switch between PL/SQL and SQL. Note that this RETURNING INTO only works because the WHERE clause identifies a single row for changing. If more than one row is or may be changed, you will need to also use BULK COLLECT (see later example).
DECLARE 
l_num PLS_INTEGER;
BEGIN
UPDATE parts
SET part_name = UPPER (part_name)
WHERE part_name LIKE 'K%'
RETURNING part_number
INTO l_num;

DBMS_OUTPUT.put_line (l_num);
END;

Use RETURNING with BULK COLLECT INTO when changing multiple rows

If your non-query DML statement changes (or might change) more than one row, you will want to add BULK COLLECT to your RETURNING INTO clause and populate an array with information from each changed row.
DECLARE 
l_part_numbers DBMS_SQL.number_table;
BEGIN
UPDATE parts
SET part_name = part_name || '1'
RETURNING part_number
BULK COLLECT INTO l_part_numbers;

FOR indx IN 1 .. l_part_numbers.COUNT
LOOP
DBMS_OUTPUT.put_line (l_part_numbers (indx));
END LOOP;
END;

Return an entire row? 

Not with ROW keyword. You can "UPDATE table_name SET ROW =" to perform a record-level update, but you cannot use the ROW keyword in that same way in a RETURNING clause.
DECLARE 
l_part parts%ROWTYPE;
BEGIN
UPDATE parts
SET part_number = -1 * part_number, part_name = UPPER (part_name)
WHERE part_number = 1
RETURNING ROW
INTO l_part;

DBMS_OUTPUT.put_line (l_part.part_name);
END;

Populate record in RETURNING with list of columns

Sorry, but you must list each column, with compatible number and type to the fields of the "receiving" record.
DECLARE 
l_part parts%ROWTYPE;
BEGIN
UPDATE parts
SET part_number = -1 * part_number, part_name = UPPER (part_name)
WHERE part_number = 1
RETURNING part_number, part_name
INTO l_part;

DBMS_OUTPUT.put_line (l_part.part_name);
END;
OK, let's create another table for some other examples.
CREATE TABLE employees ( 
employee_id INTEGER
, last_name VARCHAR2 (100)
, salary NUMBER)
/

BEGIN
INSERT INTO employees VALUES (100, 'Gutseriev', 1000);
INSERT INTO employees VALUES (200, 'Ellison', 2000);
INSERT INTO employees VALUES (400, 'Gates', 3000);
INSERT INTO employees VALUES (500, 'Buffet', 4000);
INSERT INTO employees VALUES (600, 'Slim', 5000);
INSERT INTO employees VALUES (700, 'Arnault', 6000);
COMMIT;
END;
/

Need aggregate information about impact of DML?

Sure, you could execute ANOTHER SQL statement to retrieve that information, using group functions. As in:
DECLARE 
l_total INTEGER;
BEGIN
UPDATE employees
SET salary = salary * 2
WHERE INSTR (last_name, 'e') > 0;

SELECT SUM (salary)
INTO l_total
FROM employees
WHERE INSTR (last_name, 'e') > 0;

DBMS_OUTPUT.put_line (l_total);
END;
Or you could perform a computation in PL/SQL. Use RETURNING to get back all the modified salaries. Then iterate through them, summing up the total along the way. Hmmm. That's a lot of code to write to do a SUM operation.
DECLARE 
l_salaries DBMS_SQL.number_table;
l_total INTEGER := 0;
BEGIN
UPDATE employees
SET salary = salary * 2
WHERE INSTR (last_name, 'e') > 0
RETURNING salary
BULK COLLECT INTO l_salaries;

FOR indx IN 1 .. l_salaries.COUNT
LOOP
l_total := l_total + l_salaries (indx);
END LOOP;

DBMS_OUTPUT.put_line (l_total);
END;
What you should do instead is call the aggregate function right inside the RETURNING clause!

Yes! You can call SUM, COUNT, etc. directly in the RETURNING clause and thereby perform analytics before you return the data back to your PL/SQL block. Very cool.
DECLARE    l_total   INTEGER; 
BEGIN
UPDATE employees
SET salary = salary * 2
WHERE INSTR (last_name, 'e') > 0
RETURNING SUM (salary)
INTO l_total;

DBMS_OUTPUT.put_line (l_total);
END;

Use RETURNING with EXECUTE IMMEDIATE

You can also take advantage of the RETURNING clause when executing a dynamic SQL statement!
DECLARE  
l_part_number parts.part_number%TYPE;
BEGIN
EXECUTE IMMEDIATE
q'[UPDATE parts
SET part_name = part_name || '1'
WHERE part_number = 100
RETURNING part_number INTO :one_pn]'
RETURNING INTO l_part_number;

DBMS_OUTPUT.put_line (l_part_number);
END;

RETURNING Multiple Rows in EXECUTE IMMEDIATE 

In this variation you see how to use RETURNING with a dynamic SQL statement that modifies more than one row.
DECLARE  
l_part_numbers DBMS_SQL.number_table;
BEGIN
EXECUTE IMMEDIATE
q'[UPDATE parts
SET part_name = part_name || '1'
RETURNING part_number INTO :pn_list]'
RETURNING BULK COLLECT INTO l_part_numbers;

FOR indx IN 1 .. l_part_numbers.COUNT
LOOP
DBMS_OUTPUT.put_line (l_part_numbers (indx));
END LOOP;
END;

Resources

The RETURNING INTO Clause (doc)

DML Returning INTO Clause (Oracle-Base)

RETURNING INTO by @dboriented

Oracle Dev Gym workout on RETURNING

How to make sure your code FAILS to compile

$
0
0
Huh, what?

Make sure my code fails to compile?

Why would I want to do that.

Well, suppose that you had a compute-intensive procedure that ran every hour and benefited greatly from full PL/SQL compiler optimization (level set to 3, to take advantage of subprogram inlining and everything else it does).

Next, suppose that somehow as the procedure (newly enhanced, fully tested) was being deployed to production, the optimization level was mistakenly set to 0 or 1. This would cause severe performance problems.

So in that case, wouldn't it be nice if you could build a "circuit breaker" into that procedure so that the compiler says "No go" even if the code itself compiles just fine?

I think it would be nice - and you can accomplish precisely that with the error directive of the conditional compilation feature of PL/SQL.

First, here's the code that demonstrates precisely the scenario outlined above.
CREATE OR REPLACE PROCEDURE compute_intensive
AUTHID DEFINER
IS
BEGIN
$IF $$plsql_optimize_level < 3
$THEN
$ERROR 'compute_intensive must be compiled with maximum optimization!' $END
$END

/* All the intensive code here! */
NULL;
END compute_intensive;
I check the system-defined inquiry directive, $$plsql_optimize_level, to see what the current optimization level is. If less than 3, the PL/SQL compiler encounters the $error directive.

At this point, the compiler rejects the procedure with this error:
PLS-00179: $ERROR: compute_intensive must be compiled with maximum optimization!
Notice that the string after the $error directive becomes the compilation error message the developer will see.

You can try this yourself with my LiveSQL script.

You might also use $error to mark "not done" parts in your code. It's a lot more effective than a comment like this:
/*TODO Finish section */
and it guarantees that partially written code will never make it into production, no matter how distracted you are. Here's an example. On line 24, I've got to deal with my ELSE condition, but no time right now! So I quick1y drop in a $error snippet I've created, and add the appropriate message. Notice that I include two other system defined directives, $$plsql_unit and $$plsql_line.
FUNCTION list_to_collection (
string_in IN VARCHAR2
, delimiter_in IN VARCHAR2 DEFAULT ','
)
RETURN DBMS_SQL.varchar2a
IS
l_next_location PLS_INTEGER := 1;
l_start_location PLS_INTEGER := 1;
l_return DBMS_SQL.varchar2a;
BEGIN
IF string_in IS NOT NULL
THEN
WHILE ( l_next_location > 0 )
LOOP
-- Find the next delimiter
l_next_location :=
NVL (INSTR ( string_in, delimiter_in, l_start_location ), 0);

IF l_next_location = 0
THEN
-- No more delimiters, go to end of string
l_return ( l_return.COUNT + 1 ) :=
SUBSTR ( string_in, l_start_location );
ELSE
$ERROR
'list_to_collection INCOMPLETE!
Finish extraction of next item from list.
Go to ' || $$PLSQL_UNIT || ' at line ' || $$PLSQL_LINE
$END
END IF;
l_start_location := l_next_location + 1;
END LOOP;
END IF;
RETURN l_return;
END list_to_collection;
When I try to compile the code, I see this error:
PLS-00179: $ERROR: list_to_collection INCOMPLETE! 
Finish extraction of next item from list.
Go to LIST_TO_COLLECTION at line 28
Those are two ideas I've come up with for $error. I bet you will come up with more of your own. When you do, please let us know by adding a comment to this post!

Conditional Compilation Series
1. An introduction to conditional compilation
2. Viewing conditionally compiled code: what will be run?
3. Writing code to support multiple versions of Oracle Database
4. Setting and using your own conditional compilation flags
5. How to make sure your code FAILS to compile

Make the Most of PL/SQL Bulk Processing

$
0
0
The bulk processing features of PL/SQL (BULK COLLECT and FORALL) are key tools for improving performance of programs that currently rely on row-by-row processing, an example of which is shown below.

Use this blog post to quickly get to some of the best resources on bulk processing - from articles to quizzes to workouts to tutorials.

LiveSQL Tutorial

I offer a 19-module tutorial on all things bulk processing here. I complement the explanations with lots of code to run and explore, along with:
  • Fill in the Blanks: partially-written code that you need to finish up, that reinforces the content of that module
  • Exercises: You do all the coding to solve the stated requirement (be on the lookout for copy/paste opportunities from the module to speed things up).

Oracle-BASE Content

You can always depend on Tim Hall to offer comprehensive coverage of SQL and PL/SQL features, with straightforward, easy-to-run code snippets to drive the points home. You'll find his coverage of bulk processing here.

Oracle Documentation

The Bulk SQL and Bulk Binding section of the PL/SQL Users Guide is packed full of syntax, links to related content, and tips on how to best take advantage of FORALL and BULK COLLECT.

Oracle Dev Gym Workouts

The Oracle Dev Gym offers multiple choices quizzes, workouts and classes on a wide variety of Oracle Database topics. Find below a set of four workouts (three featuring content by Tim Hall) on FORALL and BULK COLLECT.

BULK COLLECT by Tim Hall

Tim explores the BULK COLLECT feature of PL/SQL, which allows you to retrieve multiple rows with a single fetch. Note that Tim's article also covers FORALL, which is for multi-row, non-query DML (inserts, updates, deletes) and will be explored in a separate workout. After you read his article and check out the documentation, it's time to take four quizzes written by your truly to test your knowledge of this feature.

FORALL - Basic Concepts by Tim Hall

Tim offers a comprehensive review of bulk processing in PL/SQL; this workout focuses in on FORALL, covering the basic concepts behind this powerful performance enhancer. We complement Tim's article with a link to documentation and FORALL quizzes from the Dev Gym library.

FORALL and SAVE EXCEPTIONS by Tim Hall

Tim provides a comprehensive review of bulk processing in PL/SQL in this workout's leading exercise. Drill down to the SAVE EXCEPTIONS section of Tim's article to explore how to handle exceptions that may be raised when FORALL executes. Check out the documentation for more details. Then finish up with quizzes from your truly on SAVE EXCEPTIONS. Go beyond FORALL basics with this workout!

An Hour (more or less) of Bulk Processing Quizzes

Ten quizzes on FORALL and BULK COLLECT, ranging in difficulty from beginner to intermediate.

Other Blog Posts and Articles

My article in Oracle Magazine: Bulk Processing with BULK COLLECT and FORALL

Blog post: A checklist for Bulk Processing Conversions in PL/SQL

Best Type of Collection for FORALL?

$
0
0
I recently received this question in my In Box:

Is FORALL faster with Associative Arrays or Nested Tables? Oracle 12.2 documentation says: "The most efficient way to pass collections to and from the database server is to use associative arrays with the FORALL statement or BULK COLLECT clause." And a blog post claims Associative Arrays with "indices of" option is fastest in 10.2. Just wondering if you have noticed any differences and if so, how much faster Associative Arrays are in 12.2 than Nested Tables?

Quick Answer

  1. There is no significant difference I can see in the performance based on different collection types (with the limited tests I have run).
  2. Don't trust performance tests run on very old versions of Oracle Database (e.g,, 10.2).
  3. Use the documentation as a starting, not ending, point of your exploration.
  4. Try it yourself! Writing and running the code will teach you more than reading the doc or my blog post.
If you would like to read further, I will show you the code I used in my tests.

You can run these yourself on LiveSQL. I encourage you to build your own tests and post them on LiveSQL as well. I will be happy to add links in this blog post if you send them to me.

I create a table into which I will insert rows. I then create a package to help me calculate elapsed time of my test code.
CREATE TABLE parts
(
partnum NUMBER,
partname VARCHAR2 (15)
)
/

CREATE OR REPLACE PACKAGE tmr
IS
PROCEDURE start_timer;

PROCEDURE show_elapsed_time (message_in IN VARCHAR2);
END;
/

CREATE OR REPLACE PACKAGE BODY tmr
IS
l_start INTEGER;

PROCEDURE start_timer
IS
BEGIN
l_start := DBMS_UTILITY.get_cpu_time;
END start_timer;

PROCEDURE show_elapsed_time (message_in IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.put_line (
CASE
WHEN message_in IS NULL THEN 'Completed in:'
ELSE '"' || message_in || '" completed in: '
END
|| (DBMS_UTILITY.get_cpu_time - l_start)
|| ' cs');

/* Reset timer */
start_timer;
END show_elapsed_time;
END;
/

With Associative Arrays

Otherwise known as "index by tables" because of their use of the INDEX BY clause, these collections have been around in PL/SQL since 7.3, greatly enhanced over the years to offer string indexing and other fun stuff.

In the block below, I populate two collections, once with the part IDs and the other with the part names. I then use FORALL to insert them into the table. I use the tmr subprograms to set the starting time, and then display the number of hundredths of seconds (centi-seconds) that elapsed to do the FORALL inserts.
DECLARE
PROCEDURE compare_inserting (num IN INTEGER)
IS
TYPE numtab IS TABLE OF parts.partnum%TYPE
INDEX BY PLS_INTEGER;

TYPE nametab IS TABLE OF parts.partname%TYPE
INDEX BY PLS_INTEGER;

pnums numtab;
pnames nametab;
BEGIN
FOR indx IN 1 .. num
LOOP
pnums (indx) := indx;
pnames (indx) := 'Part ' || TO_CHAR (indx);
END LOOP;

tmr.start_timer;

FORALL indx IN 1 .. num
INSERT INTO parts
VALUES (pnums (indx), pnames (indx));

tmr.show_elapsed_time ('Inserted ' || num || ' rows');

ROLLBACK;
END;
BEGIN
DBMS_OUTPUT.put_line ('Associative Arrays');
compare_inserting (1000000);
END;

With Nested Tables

The nested table type was added way back in Oracle8, as part of the object-relational model. The definition of a nested table type looks just like the associative array, without the INDEX BY clause. That's because, officially, nested tables have no index, no order to their elements - they are multisets. But in practice, you can use the index value, which we certainly do in the FORALL statement.

With nested tables, you must initialize the collection using its constructor function, and explicitly extend the collection to make room for new elements.
DECLARE
PROCEDURE compare_inserting (num IN INTEGER)
IS
TYPE numtab IS TABLE OF parts.partnum%TYPE;

TYPE nametab IS TABLE OF parts.partname%TYPE;

pnums numtab := numtab();
pnames nametab := nametab();
BEGIN
pnums.extend (num);
pnames.extend (num);

FOR indx IN 1 .. num
LOOP
pnums (indx) := indx;
pnames (indx) := 'Part ' || TO_CHAR (indx);
END LOOP;

tmr.start_timer;

FORALL indx IN 1 .. num
INSERT INTO parts
VALUES (pnums (indx), pnames (indx));

tmr.show_elapsed_time ('Inserted ' || num || ' rows');

ROLLBACK;
END;
BEGIN
DBMS_OUTPUT.put_line ('Nested Table');
compare_inserting (1000000);
END;

With Varrays

Finally, varrays - a shortening of "varying arrays." Which is kind of funny, because varrays are the least varying of all the collection types. With varrays, you have to declare the maximum number of elements allowed in the collection up front.

With varrays, like nested tables, you must initialize the collection using its constructor function, and explicitly extend the collection to make room for new elements.
DECLARE
PROCEDURE compare_inserting (num IN INTEGER)
IS
TYPE numtab IS VARRAY (1000000) OF parts.partnum%TYPE;

TYPE nametab IS VARRAY (1000000) OF parts.partname%TYPE;

pnums numtab := numtab();
pnames nametab := nametab();
BEGIN
pnums.extend (num);
pnames.extend (num);

FOR indx IN 1 .. num
LOOP
pnums (indx) := indx;
pnames (indx) := 'Part ' || TO_CHAR (indx);
END LOOP;

tmr.start_timer;

FORALL indx IN 1 .. num
INSERT INTO parts
VALUES (pnums (indx), pnames (indx));

tmr.show_elapsed_time ('Inserted ' || num || ' rows');

ROLLBACK;
END;
BEGIN
DBMS_OUTPUT.put_line ('Varrays');
compare_inserting (1000000);
END;

Results

I ran each of the blocks three times, with the results you see below. I conclude that there is no significant difference in performance based on the type of collection you choose.
Associative Arrays
"Inserted 1000000 rows" completed in: 84 cs
"Inserted 1000000 rows" completed in: 78 cs
"Inserted 1000000 rows" completed in: 77 cs

Nested Table
"Inserted 1000000 rows" completed in: 78 cs
"Inserted 1000000 rows" completed in: 76 cs
"Inserted 1000000 rows" completed in: 77 cs

Varrays
"Inserted 1000000 rows" completed in: 81 cs
"Inserted 1000000 rows" completed in: 77 cs
"Inserted 1000000 rows" completed in: 77 cs
Now, obviously, this is not a comprehensive test. Will things go more slowly with nested tables for updates instead of inserts? Are associative arrays faster with small sets of data and nested tables better with super-duper large datasets? But it seems rather unlikely that these factors would affect overall performance, so I am not going to build scripts for those.

I do, however, encourage each of you to try this yourself. Feel free to download and modify my LiveSQL script. Test some variations, let me know what you discover.

In the meantime, I suggest that you pick the collection type that allows you to most easily build and manage the collections you will feed into the FORALL statement. Associative arrays are particularly good with sparse collections (not every index value filled between first and last). Nested tables offer lots of features (not connected to FORALL) for set-oriented management of collection contents. Varrays: well, honestly, I doubt you'll ever use a array. It's intended more for usage as nested columns in relational tables, offering some performance advantages there.

When Lazy is Good: Overloading and APIs

$
0
0
When more than one subprogram (procedure or function) in the same scope share the same name, the subprograms are said to be overloaded. PL/SQL supports the overloading of procedures and functions in the declaration section of a block (named or anonymous), package specifications and bodies, and object type definitions. Overloading is a very powerful feature, and you should exploit it fully to improve the usability of your software.

Before exploring some of the details of overloading, a short quiz:

Which of the following is another name for overloading?
  • Dynamic Polymorphism
  • Interface Inheritance
  • Static Polymorphism
  • Multiple Monomorphism
In a poll I conducted on Twitter, we saw the following results:
And I was very glad to see this, because Static Polymorphism is, indeed, another name for overloading, and here's why:

With overloading, at the time your code is compiled, PL/SQL resolves all references to named elements, such as a function invocation. If there is more than one subprogram with same name name, it must sort out which of those "fits". That process is described in detail below, but two salient facts are clear:

1. The resolution takes place at compile time ("static").
2. There are more than one subprogram with the same name but different "shape" (morphology, the study of the shape of things).

Dynamic polymorphism is a process specific to object-oriented languages, in which multiple classes in a hierarchy contain methods with the same name. The language processor decides at runtime which of these methods should be invoked. Runtime = dynamic. Dynamic polymorphism is supported for methods in Oracle object types, but not with PL/SQL procedures and functions. Check out my LiveSQL script for a demonstration.

Interface Inheritance is a real thing but it is not overloading. Multiple Monomorphism is some nonsense I made up. :-)

OK, that's the "theory". Let's take a look at some "practice" (actual overloading examples and features).

Here is a very simple example of three overloaded subprograms defined in the declaration section of an anonymous block (therefore, all are nested modules and can only be used within this block):
DECLARE
/* First version takes a DATE parameter. */
FUNCTION value_ok (date_in IN DATE) RETURN BOOLEAN IS
BEGIN
RETURN date_in <= SYSDATE;
END;

/* Second version takes a NUMBER parameter. */
FUNCTION value_ok (number_in IN NUMBER) RETURN BOOLEAN IS
BEGIN
RETURN number_in > 0;
END;

/* Third version is a procedure! */
PROCEDURE value_ok (number_in IN NUMBER) IS
BEGIN
IF number_in > 0 THEN
DBMS_OUTPUT.PUT_LINE (number_in || 'is OK!');
ELSE
DBMS_OUTPUT.PUT_LINE (number_in || 'is not OK!');
END IF;
END;

BEGIN
When the PL/SQL runtime engine encounters the following statement:
IF value_ok (SYSDATE) THEN ...
the actual parameter list is compared with the formal parameter lists of the various overloaded modules, searching for a match. If one is found, PL/SQL executes the code in the body of the program with the matching header.

Overloading can greatly simplify your life and the lives of other developers. This technique consolidates the call interfaces for many similar programs into a single module name, transferring the burden of knowledge from the developer to the software. You do not have to try to remember, for instance, the six different names for programs adding values (dates, strings, Booleans, numbers, etc.) to various collections. Instead, you simply tell the compiler that you want to add a value and pass it that value. PL/SQL and your overloaded programs figure out what you want to do and then do it for you.

When you build overloaded subprograms, you spend more time in design and implementation than you might with separate, standalone programs. This additional time up-front will be repaid handsomely down the line because you and others will find it much easier and more efficient to use your programs.

Benefits of Overloading

There are three different scenarios that benefit from overloading:

Supporting many data combinations

When applying the same action to different kinds or combinations of data, overloading does not provide a single name for different activities, so much as it provides different ways of requesting the same activity. This is the most common motivation for overloading.

Fitting the program to the user

To make your code as useful as possible, you may construct different versions of the same program that correspond to different patterns of use. This often involves overloading functions and procedures.

A good indicator of the need for this form of overloading is when you find yourself writing unnecessary code. For example, when working with DBMS_SQL, you will call the DBMS_SQL.EXECUTE function, but for DDL statements, the value returned by this function is irrelevant. Oracle should have overloaded this function as a procedure, so that I could simply execute a DDL statement like this:
BEGIN
DBMS_SQL.EXECUTE ('CREATE TABLE xyz ...');
as opposed to:
DECLARE
feedback PLS_INTEGER;
BEGIN
feedback := DBMS_SQL.EXECUTE ('CREATE TABLE xyz ...');
and then ignoring the feedback.

Overloading by type, not value

This is the least common application of overloading. In this scenario, you use the type of data, not its value, to determine which of the overloaded programs should be executed. This really comes in handy only when you are writing very generic software. DBMS_SQL.DEFINE_COLUMN is a good example of this approach to overloading. I need to tell DBMS_SQL the type of each of my columns being selected from the dynamic query. To indicate a numeric column, I can make a call as follows:
DBMS_SQL.DEFINE_COLUMN (cur, 1, 1);
or I could do this:
DBMS_SQL.DEFINE_COLUMN (cur, 1, DBMS_UTILITY.GET_TIME);
It doesn’t matter which I do; I just need to say “this is a number,” but not any particular number. Overloading is an elegant way to handle this requirement.

Let’s look at an example of the most common type of overloading and then review restrictions and guidelines on overloading.

Supporting many data combinations

Use overloading to apply the same action to different kinds or combinations of data. As noted previously, this kind of overloading does not provide a single name for different activities so much as different ways of requesting the same activity. Consider DBMS_OUTPUT.PUT_LINE. You can use this built-in to display the value of any type of data that can be implicitly or explicitly converted to a string.

Interestingly, in earlier versions of Oracle Database (7, 8, 8i, 9i), this procedure was overloaded. In Oracle Database 10g and later, however, it is not overloaded at all! This means that if you want to display an expression that cannot be implicitly converted to a string, you cannot call DBMS_OUTPUT.PUT_LINE and pass it that expression.

You might be thinking: so what? PL/SQL implicitly converts numbers and dates to a string. What else might I want to display? Well, for starters, how about a Boolean? To display an expression of type Boolean variable’s value, you must write an IF statement, as in:
IF l_student_is_registered
THEN
DBMS_OUTPUT.PUT_LINE ('TRUE');
ELSE
DBMS_OUTPUT.PUT_LINE ('FALSE');
END IF;
Now, isn’t that silly? And a big waste of your time? Fortunately, it is very easy to fix this problem. Just build your own package, with lots of overloadings, on top of DBMS_OUTPUT.PUT_LINE. Here is a very abbreviated example of such a package. You can extend it easily, as I do with the p.l procedure (why type all those characters just to say “show me,” right?). A portion of the package specification is shown here:
PACKAGE p
IS
PROCEDURE l (bool IN BOOLEAN);

/* Display a string. */
PROCEDURE l (stg IN VARCHAR2);

/* Display a string and then a Boolean value. */
PROCEDURE l (
stg IN VARCHAR2,
bool IN BOOLEAN
);
END do;
This package simply sits on top of DBMS_OUTPUT.PUT_LINE and enhances it. With p.l, I can now display a Boolean value without writing my own IF statement, as in:
DECLARE
v_is_valid BOOLEAN :=
book_info.is_valid_isbn ('5-88888-66');
BEGIN
p.l (v_is_valid);
END;
Would you like to use the p package? No problem! It's available on LiveSQL - download and install it in your environment and make your life just a little bit easier with overloading!

Restrictions on Overloading

There are several restrictions on how you can overload programs.

When the PL/SQL engine compiles and runs your program, it has to be able to distinguish between the different overloaded versions of a program; after all, it can’t run two different modules at the same time.

So when you compile your code, PL/SQL will reject any improperly overloaded modules. It cannot distinguish between the modules by their names because by definition they are the same in all overloaded programs.

Instead, PL/SQL uses the parameter lists of these sibling programs to determine which one to execute and/or the types of the programs (procedure versus function). As a result, the following restrictions apply to overloaded programs:

The datatype “family” of at least one of the parameters of overloaded programs must differ

INTEGER, REAL, DECIMAL, FLOAT, etc., are NUMBER subtypes. CHAR, VARCHAR2, and LONG are character subtypes. If the parameters differ only by datatype within the supertype or family of datatypes, PL/SQL does not have enough information to determine the appropriate program to execute. Note that there is an exception when it comes to some numeric datatypes, which is explored below.

Overloaded programs with parameter lists that differ only by name must be called using named notation

If you don’t use the name of the argument, how can the compiler distinguish between calls to two overloaded programs? Please note, however, that it is always risky to use named notation as an enforcement paradigm. You should avoid situations where named notation yields different semantic meaning from positional notation.

The parameter list of overloaded programs must differ by more than parameter mode

Even if a parameter in one version is IN and that same parameter in another version is IN OUT, PL/SQL cannot tell the difference at the point at which the program is called.

All of the overloaded programs must be defined within the same PL/SQL scope or block

You cannot define one "version" in one block (anonymous block, standalone procedure or function, or package) and define another subprogram with the same name in a different block. Oh wait, yes you can. :-) But they won't be overloading. They will be subprograms that happen to have the same name. The PL/SQL compiler will never look for a match outside of that scope. Either the subprogram inside the scope matches the invocation or you get a compilation error.

You also cannot overload two standalone programs; one simply replaces the other.

Overloaded functions must differ by more than their return type

At the time that the overloaded function is called, the compiler doesn’t know what type of data that function will return. The compiler therefore cannot determine which version of the function to use if all the parameters are the same.

Overloading with Numeric Types

While you cannot successfully overload two subprograms whose differences are CHAR vs VARCHAR2, you can overload two subprograms if their formal parameters differ only in numeric datatype. Consider the following block:
DECLARE
PROCEDURE proc1 (n IN PLS_INTEGER) IS
BEGIN
DBMS_OUTPUT.PUT_LINE ('pls_integer version');
END;

PROCEDURE proc1 (n IN NUMBER) IS
BEGIN
DBMS_OUTPUT.PUT_LINE ('number version');
END;
BEGIN
proc1 (1.1);
proc1 (1);
END;
When I run this code, I see the following results:

number version
pls_integer version

The PL/SQL compiler is able to distinguish between the two calls. Notice that it called the “number version” when I passed a noninteger value. That’s because PL/SQL looks for numeric parameters that match the value, and it follows this order of precedence in establishing the match: it starts with PLS_INTEGER or BINARY_INTEGER, then NUMBER, then BINARY_FLOAT, and finally BINARY_DOUBLE. It will use the first overloaded program that matches the actual argument values passed.

While it is very nice that the database now offers this flexibility, be careful when relying on this very subtle overloading—make sure that it is all working as you would expect. Test your code with a variety of inputs and check the results. Remember that you can pass a string such as “156.4” to a numeric parameter; be sure to try out those inputs as well.

You can also qualify numeric literals and use conversion functions to make explicit which overloading (i.e., which numeric datatype) you want to call. If you want to pass 5.0 as a BINARY_FLOAT, for example, you could specify the value 5.0f or use the conversion function, TO_BINARY_FLOAT(5.0).

Watch out for Ambiguous Overloading!

If you go "overboard" with overloading (lots of combinations, not too carefully thought out), you could end up with a situation where it is hard to use those overloading. How do you know you've got a problem with your overloading? PLS-00307 will raise its ugly head.

Here's an example: I overload two procedures, whose only difference is the datatype: CHAR vs VARCHAR2.
CREATE OR replace PACKAGE salespkg AUTHID DEFINER
IS
PROCEDURE calc_total (reg_in IN CHAR);

PROCEDURE calc_total (reg_in IN VARCHAR2);

END salespkg;
/

CREATE OR replace PACKAGE BODY salespkg
IS
PROCEDURE calc_total (reg_in IN CHAR)
IS
BEGIN DBMS_OUTPUT.PUT_LINE ('region'); END;

PROCEDURE calc_total (reg_in IN VARCHAR2)
IS
BEGIN DBMS_OUTPUT.PUT_LINE ('region'); END;

END salespkg;
/
It turns out that this is a rather interesting case. The package compiles without errors, but you cannot invoke either of these procedures. They are just too darn similar.
BEGIN  
salespkg.calc_total ('reg11');
END;
/

PLS-00307: too many declarations of 'CALC_TOTAL' match this call
And the problem can be more subtle than this, because parameters can have default values. At a glance the parameter lists of your overloaded subprograms look quite different. But depending on how they are called....
CREATE OR replace PACKAGE salespkg AUTHID DEFINER  
IS
PROCEDURE calc_total (zone_in IN CHAR, date_in in DATE DEFAULT SYSDATE);

PROCEDURE calc_total (zone_in IN VARCHAR2);

END salespkg;
/

>BEGIN
salespkg.calc_total ('reg11');
END;
/

PLS-00307: too many declarations of 'CALC_TOTAL' match this call

This LiveSQL script demonstrates the problems of ambiguous overloading.

Bottom line: make sure your overloading are sufficiently different to avoid ambiguity. This is unlikely to be a problem for most developers, but if you get all excited about overloading, you might over-do it. And end up with problems like these.

Thanks, O'Reilly Media!

Portions of this post have been lifted (gently, carefully, lovingly) from Oracle PL/SQL Programming, with permission from O'Reilly Media.


Some Curiosities of Oracle Error Codes

$
0
0
A blog post about Oracle error codes? Curiosities about them, even?

If you doubt that this might be possible or of interest, then answer these two questions:

Can an error in Oracle have more than one error code?
Are error codes positive or negative?

If you answered "yes" for the first and "yes and no" for the second, you probably don't need to read this post.

Oracle errors with more than one error code?

Well that wouldn't be very normalized, would it? :-)

But it is true that there at least one error that has two different error codes associated with it, and it's one of the most common "errors" you'll encounter in your code: 

The NO_DATA_FOUND exception

When I execute a SELECT-INTO statement, Oracle will raise NO_DATA_FOUND if no row is found for the query. It will raise TOO_MANY_ROWS if more than one row is found.

So what error code is associated with NO_DATA_FOUND?

The following code demonstrates this curiosity. I create a table with no data. My SELECT-INTO therefore finds no rows and the error message displayed (there was no exception handler) shows that the error code is -1403 (or is it 1403? I explore that curiosity later).
CREATE TABLE t (n NUMBER)
/

DECLARE
l_n NUMBER;
BEGIN
SELECT n INTO l_n FROM t;
END;
/

ORA-01403: no data found
Now I will handle the exception, first with WHEN OTHERS then with WHEN NO_DATA_FOUND, and display the value returned by SQLCODE.
DECLARE
l_n NUMBER;
BEGIN
SELECT n INTO l_n FROM t;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error code = ' || SQLCODE);
END;
/

Error code = 100

DECLARE
l_n NUMBER;
BEGIN
SELECT n INTO l_n FROM t;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line ('Error code = ' || SQLCODE);
END;
/

Error code = 100
100, not -1403!

That's certainly very odd. How can this be? Well, you what they say: go to the source for the best answer, and the source in this case is the code of the STANDARD package, which defines many datatypes and exceptions in the PL/SQL language. In STANDARD, you will find the following:
  NO_DATA_FOUND exception;
pragma EXCEPTION_INIT(NO_DATA_FOUND, 100);

....

TOO_MANY_ROWS exception;
pragma EXCEPTION_INIT(TOO_MANY_ROWS, '-1422');
That pragma is used to associate an error code with a named exception (explore this pragma on LiveSQL here). We can do that in our code as well (which I show you below, thereby introducing another oddity for NO_DATA_FOUND). In this case, the STANDARD package pre-defines a number of named exceptions that are commonly used. And as you can see, the association is made to 100 and not -1403.

I must confess I have not tracked down any official documentation on this, but it is pretty clear that 100 is the ANSI-standard error code for "no rows found". In fact, Intersystems elaborates a bit further as follow:
SQLCODE=100 indicates that the SQL operation was successful, but found no data to act upon. This can occur for a number of reasons. For a SELECT these include: the specified table contains no data; the table contains no data that satisfies the query criteria; or row retrieval has reached the final row of the table. For an UPDATE or DELETE these include: the specified table contains no data; or the table contains no row of data that satisfies the WHERE clause criteria. In these cases %ROWCOUNT=0.
Notice that it states that 100 indicates the "SQL operation was successful." I like this because it helps explain why the error code is not, say, -100. It also reinforces the point that just because a query does not return any rows does not mean that there is an error. It's just a data condition.

More NO_DATA_FOUND Oddities

Remember I said you could use the EXCEPTION_INIT pragma to associate a code with your own named exception? Here's an example:
DECLARE  
e_bad_date_format EXCEPTION;
PRAGMA EXCEPTION_INIT (e_bad_date_format, -1830);
BEGIN
DBMS_OUTPUT.put_line (TO_DATE ('2010 10 10 44:55:66', 'YYYSS'));
EXCEPTION
WHEN e_bad_date_format
THEN
DBMS_OUTPUT.put_line ('Bad date format');
END;
I can even use this pragma to assign an error code already assigned a pre-defined exception name, like TOO_MANY_ROWS:
DECLARE  
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT (my_exception, -1422);
BEGIN
RAISE my_exception;
END;
/

ORA-01422: exact fetch returns more than requested number of rows
And what about -1402? A big, fat no way!
DECLARE  
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT (my_exception, -1403);
BEGIN
RAISE my_exception;
END;
/

PLS-00701: illegal ORACLE error number -1403 for PRAGMA EXCEPTION_INIT

DECLARE
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT (my_exception, -1403);
BEGIN
RAISE my_exception;
END;
/

PLS-00701: illegal ORACLE error number -1403 for PRAGMA EXCEPTION_INIT
But 100 works just fine. :-) or is it :-(?
DECLARE  
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT (my_exception, 100);
BEGIN
RAISE my_exception;
END;
/

ORA-01403: no data found
Notice also that the inability to use EXCEPTION_INIT with 100 manifests as a compile-time error ("PLS") not a runtime Oracle error ("ORA").

Error Codes Negative or Positive?

I bet that most of you believe that error codes are (mostly) negative. That view would certainly be reinforced with code like this:
DECLARE  
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT (my_exception, -1422);
BEGIN
RAISE my_exception;
EXCEPTION
WHEN OTHERS
THEN
IF SQLCODE = -1422
THEN
DBMS_OUTPUT.PUT_LINE ('Negative!');
END IF;
END;
/

Negative!
Sure looks negative to me. And if I try to use that pragma with a positive number that is not 100 or 1 (the error code associated with a user-defined exception that has not been essociated with an error code by EXCEPTION_INIT), I get an error:
DECLARE  
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT (my_exception, 1422);
BEGIN
RAISE my_exception;
END;
/

PLS-00701: illegal ORACLE error number 1422 for PRAGMA EXCEPTION_INIT
So it's OK, so error codes are generally negative. Everyone agreed on that? Well, maybe everyone but a few people who work(ed) at Oracle and maybe everything but a few features in Oracle Database.

It turns out that sometimes error codes are stored without that pesky "-". Which is understandable, because there are clearly two ways to interpret the hyphen in this text:
ORA-01422
1. A negative sign
2. A hyphen

Let's first consider the SQLERRM function. Most people use it to obtain the error message of the current error (though you would be better off using DBMS_UTILITY.FORMAT_ERROR_STACK or the UTL_CALL_STACK API).

Relatively few developers know that you can also pass an error code to SQLERRM and it will return the generic message associated with that code. Here's an example:
BEGIN 
DBMS_OUTPUT.put_line (SQLERRM (-1422));
END;

ORA-01422: exact fetch returns more than requested number of rows
That's nice. But what if I leave off the "-"?
BEGIN 
DBMS_OUTPUT.put_line (SQLERRM (1422));
END;

-1422: non-ORACLE exception
Gee, that's telling it like it is.

But why didn't SQLERRM talk to SQL%BULK_EXCEPTIONS and get their story straight?

SQL%BULK_EXCEPTIONS is a pseudo-collection or records that is populated with any errors in the execution of FORALL statements. Check out my LiveSQL tutorial on bulk processing for lots more details.

Each record contains the index into the collection for each statement that failed along with the error code of the failure. Guess what? The error code is recorded without what that person clearly thought was a hyphen.

Notice in the code below (an excerpt from my LiveSQL script on SAVE EXCEPTIONS) I must multiply the error code value by -1 so that I can retrieve the error message.
EXCEPTION  
WHEN std_errs.failure_in_forall
THEN
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line (
'Oracle error is '
|| SQLERRM ( -1 * SQL%BULK_EXCEPTIONS (indx).ERROR_CODE));
END LOOP;

ROLLBACK;
END;
No big deal, once you aware of it. But....kind of odd, eh?

Well, there is some consistency in our inconsistency. If you use the LOG ERRORS feature for non-query DML (which allows you to suppress errors at the row level), then Oracle will automatically record the error code, message and more in an error logging table. And in this table, error codes are stored as unsigned integers, as you can see below in the output from this LiveSQL script on LOG ERRORS:


So those are my little discoveries on the nuances of error codes in Oracle Database.

Do you have your own story about Oracle error codes you'd like to share?

Use named notation for crystal clear code

$
0
0
The first and most important criteria of high quality code is that it meets user requirements (it's "correct").

The second most important criteria is that it fast enough to meet user needs. It doesn't matter that you might be able to make it go faster.

The third most important criteria of excellent code is that it is maintainable. Why, you might be wondering, is maintainabiliity so important?

Most of the code we write, especially code that lives in the database, sticks around for a long time. Well past the point when you were familiar with the code you wrote. And likely past the time when you are still working on that same code or project.

So the overall cost of development of that code is not just the initial build expense, but also must include the cost of maintaining that code. All too often we are so absorbed in meeting deadlines that we sacrifice clarity and readability to "get the job done."

And then we - or others - pay the price later. Conversely, if we take advantage of features in PL/SQL that enhance readability of code, the burden of maintaining it later will be reduced. Hey, it might even make it easier for you to debug your code today!

With named notation, you explicitly associate the formal parameter (the name of the parameter) with the actual parameter (the value of the parameter) right in the call to the program, using the combination symbol =>.

The general syntax for named notation is:
formal_parameter_name => argument_value 
Because you provide the name of the formal parameter explicitly, PL/SQL no longer needs to rely on the order of the parameters to make the association from actual to formal. So, if you use named notation, you do not need to list the parameters in your call to the program in the same order as the formal parameters in the header.

Suppose I define a function as follows:
FUNCTION total_sales
(company_id_in IN company.company_id%TYPE,
status_in IN order.status_code%TYPE := NULL)
RETURN std_types.dollar_amount;

Then I can call total_sales in either of these two ways:
new_sales :=
total_sales (company_id_in => order_pkg.company_id, status_in =>'N');

new_sales :=
total_sales (status_in =>'N', company_id_in => order_pkg.company_id);
It is also possible to mix named and positional notation in the same program call, as in:
l_new_sales := total_sales (order_pkg.company_id, status_in =>'N');
If you do mix notation, however, you must list all of your positional parameters before any named notation parameters, as shown in the above code. After all, positional notation has to have a starting point from which to keep track of positions, and the only starting point is the first parameter. If you place named notation parameters in front of positional notation, PL/SQL loses its place.

Both of the following calls to total_sales will fail. The first statement fails because the named notation comes first. The second fails because positional notation is used, but the parameters are in the wrong order. PL/SQL will try to convert ‘N’ to a NUMBER (for company_id):
l_new_sales := total_sales (company_id_in => order_pkg.company_id, 'N');
l_new_sales := total_sales ('N', company_id_in => order_pkg.company_id);

Benefits of named notation

Now that you are aware of the different ways to notate the order and association of parameters, you might be wondering why you would ever use named notation. Here are two possibilities:

1. Named notation is self-documenting

When you use named notation, the call to the program clearly describes the formal parameter to which the actual parameter is assigned. The names of formal parameters can and should be designed so that their purpose is self-explanatory. In a way, the descriptive aspect of named notation is another form of program documentation. If you are not familiar with all of the modules called by an application, the listing of the formal parameters helps reinforce your understanding of a particular program call. In some development environments, the standard for parameter notation is named notation for just this reason. This is especially true when the formal parameters are named following the convention of appending the passing mode as the last token. Then, the direction of data can be clearly seen simply by investigating the procedure or function call.

2. Named notation gives you complete flexibility over parameter specification

You can list the parameters in any order you want. (This does not mean, however, that you should randomly order your arguments when you call a program!) You can also include only the parameters you want or need in the parameter list. Complex applications may at times require procedures with literally dozens of parameters. Any parameter with a default value can be left out of the call to the procedure. Using named notation, the developer can use the procedure by passing only the values needed for that usage.

Let’s see how these benefits can be applied. Consider the following program header:
PROCEDURE so_many_parameters (
advertising_budget_in IN NUMBER
, contributions_inout IN OUT NUMBER
, start_date_in IN DATE DEFAULT SYSDATE
, bonus_out OUT NUMBER
, flags_in IN VARCHAR2 DEFAULT 'WHENEVER POSSIBLE'
);
An analysis of the parameter list yields these observations:
  • The minimum number of arguments that must be passed to so_many_parameters is three. To determine this, add the number of IN parameters without default values to the number of OUT or IN OUT parameters.
  • I can call this program with positional notation with either four or five arguments, because the last parameter has mode IN with a default value.
  • You will need at least two variables to hold the values returned by the OUT and IN OUT parameters.
  • Given this parameter list, there are a number of ways that you can call this program:
All positional notation, all actual parameters specified. Notice how difficult it is to recall the parameter (and significance) of each of these values.
    DECLARE
    l_max_bonus NUMBER;
    l_mucho_dollars NUMBER := 100000;
    BEGIN
    /* All positional notation */
    so_many_parameters (50000000
    , l_mucho_dollars
    , SYSDATE + 20
    , l_max_bonus
    , 'PAY OFF OSHA'
    );
    All positional notation, minimum number of actual parameters specified. Still hard to understand.
    so_many_parameters (50000000
    , l_mucho_dollars
    , SYSDATE + 20
    , l_max_bonus
    );
    All named notation, keeping the original order intact. Now my call to so_many_parameters is self-documenting.
    so_many_parameters
    so_many_parameters
    (advertising_budget_in => 50000000
    , contributions_inout => l_mucho_dollars
    , start_date_in => SYSDATE
    , bonus_out => l_max_bonus
    , flags_in => 'autonomous,plsql,sql'
    );
    Skip over all IN parameters with default values, another critical feature of named notation:
    so_many_parameters
    (advertising_budget_in => 50000000
    , contributions_inout => l_mucho_dollars
    , bonus_out => l_max_bonus
    );
    Change the order in which actual parameters are specified with named notation; also provide just a partial list:
    so_many_parameters
    (bonus_out => l_max_bonus
    , start_date_in => SYSDATE
    , advertising_budget_in => 50000000
    , contributions_inout => l_mucho_dollars
    );
    Blend positional and named notation. You can start with positional, but once you switch to named notation, you can’t go back to positional.
    so_many_parameters
    (50000000
    , l_mucho_dollars
    , start_date_in => SYSDATE
    , bonus_out => l_max_bonus
    );
    As you can see, there is a lot of flexibility when it comes to passing arguments to a parameter list in PL/SQL. As a general rule, named notation is the best way to write code that is readable and more easily maintained. You just have to take the time to look up and write the parameter names.

    Resources

    Demonstration of Named Notation -my Oracle LiveSQL script

    Calling Notation For PL/SQL Subroutines by Manish Sharma

    Thanks, O'Reilly Media!

    Portions of this post have been lifted (gently, carefully, lovingly) from Oracle PL/SQL Programming, with permission from O'Reilly Media.

    Change in ALL_ARGUMENTS as of 18c: no more composite "explosion"

    $
    0
    0
    The Oracle catalog contains hundreds of views (generally referred to as "data dictionary views") that provide information about the objects stored in the database (tables, views, PL/SQL program units, etc.). Several of these views are extremely helpful to PL/SQL developers in analyzing and managing their code.

    Here are a few examples:
    • ALL_OBJECTS - information about all database objects which can be accessed by the current user.
    • ALL_ARGUMENTS - information about every argument of every packaged subprogram and schema-level program unit for which the current user has EXECUTE authority.
    • ALL_IDENTIFIERS - information about identifiers in program units, gathered by the Oracle Database 11g PL/Scope feature.
    In this blog post, I explore a change in behavior for ALL_ARGUMENTS (and its USER* and DBA* variants) as of Oracle Database 18c.

    ALL_ARGUMENTS lists the arguments of the procedures and functions that are accessible to the current user. USER_ARGUMENTS, as you might know or guess, offers information about those arguments defined in procedures and functions in the currently connected schema.

    This view gives developers access to all sorts of interesting information, allowing them to create queries to perform checks on their code.

    You can, for example, determine if any arguments of an "undesireable" type, such as CHAR or LONG. You can also find out if a subprogram is a procedure or a function.

    You can also determine if a subprogram is a function or a procedure (though this is now also possible through PL/Scope and ALL_IDENTIFIERS). Here's an OracleLiveSQL script showing this usage.

    As you likely know, an argument could be a scalar (number, date, string. etc.) or a composite (consisting of multiple "pieces", such as a record or object type). Up until Oracle Database 12c Release 2, the way that ALL_ARGUMENTS recursively expanded composites. In other words, that view contained one row for the argument itself and then one row for each attribute of an object type or field of a record. You can see this below.

    I create a table, then a function that accepts a record as an in parameter.
    CREATE TABLE plch_employees
    (
    employee_id INTEGER
    , last_name VARCHAR2 (100)
    , first_name VARCHAR2 (100)
    , date_of_birth DATE
    , hire_date DATE
    , salary NUMBER
    )
    /

    CREATE OR REPLACE FUNCTION plch_get_info (
    employee_in IN plch_employees%ROWTYPE
    , info_type_in IN PLS_INTEGER
    , for_date_in IN DATE)
    RETURN BOOLEAN
    AUTHID DEFINER
    IS
    BEGIN
    RETURN NULL;
    END;
    /

    /* Verify choice correctness */

    SELECT COUNT (*) arg_count
    FROM user_arguments
    WHERE object_name = 'PLCH_GET_INFO'
    /

    ARG_COUNT
    ---------
    10
    Run this came code in 18c and above, and the count of rows is just 4. The following script shows a bit more detail.
    SQL> 
    select OBJECT_NAME,ARGUMENT_NAME,POSITION
    2 FROM user_arguments
    3 WHERE object_name = 'PLCH_GET_INFO'
    4 /

    OBJECT_NAME ARGUMENT_NAME POSITION
    -------------------- -------------------- ----------
    PLCH_GET_INFO 0
    PLCH_GET_INFO EMPLOYEE_IN 1
    PLCH_GET_INFO INFO_TYPE_IN 2
    PLCH_GET_INFO FOR_DATE_IN 3

    4 rows selected.

    12.2

    SQL> select OBJECT_NAME,ARGUMENT_NAME,POSITION
    2 FROM user_arguments
    3 WHERE object_name = 'PLCH_GET_INFO'
    4 /

    OBJECT_NAME ARGUMENT_NAME POSITION
    -------------------- -------------------- ----------
    PLCH_GET_INFO 0
    PLCH_GET_INFO EMPLOYEE_IN 1
    PLCH_GET_INFO EMPLOYEE_ID 1
    PLCH_GET_INFO LAST_NAME 2
    PLCH_GET_INFO FIRST_NAME 3
    PLCH_GET_INFO DATE_OF_BIRTH 4
    PLCH_GET_INFO HIRE_DATE 5
    PLCH_GET_INFO SALARY 6
    PLCH_GET_INFO INFO_TYPE_IN 2
    PLCH_GET_INFO FOR_DATE_IN 3

    If you relied on that expanded data in ALL_ARGUMENTS in any of your QA scripts, you will now need to join with one of the following views: ALL_PLSQL_TYPES, ALL_PLSQL_TYPE_ATTRS, and ALL_PLSQL_COLL_TYPES.

    I'll publish a post soon that explores how to do just that.

    Feedback requested on upcoming changes to Dev Gym UI

    $
    0
    0
    Hello there, Dev Gym members!

    We plan to release a new version of the Dev Gym (19.3 - we are aligning with the overall version schema followed by Oracle Database and sister sites, like Oracle LiveSQL).

    Mainly, it is a tweak of the UI, to bring our site in line with developer.oracle.com and also to make better use of our "real estate." Before we apply the changes, though, we'd love to get some feedback from our users.

    So when you have a moment or two, please scroll down the page, compare current and 19.3 pages, then let us know what you think.

    Thanks very much,
    Steven

    Home Page

    Current Site
    A few things to point out:
    • Orange theme
    • Large banner with search bar
    Dev Gym 19.3 Home Page

    Things to note:
    • Switch to black-grey-red theme (just a little bit of red)
    • Much smaller banner
    • Search bar moved to top of site, independent of page
    • Cleaner display of quizzes (we'd like to think)
    Moving on, in the workouts and classes pages, we've reduced the real estate for the banner (no need for another search bar), simplified the formats and also added a data-driven pie indicator of % completion.

    Workouts, Current Site

    Workouts, Dev Gym 19.3


    Things to note:
    • We show % of completion as a piechart for quick reinforcement of progress being made.

    Classes, Current Site

    Classes, Dev Gym 19.3

    Things to note:
    • We show % of completion as a piechart for quick reinforcement of progress being made.

    Player Profiles

    We've also reorganized and put more content on the profile page.

    Current Site
    Dev Gym 19.3

    Site Search

    Finally, we've reorganized separate search results pages into a single site-wide search results page that includes quizzes, workouts and classes together.

    Current Site
    Dev Gym 19.3
    All right, that should do it. We didn't change much in the way of functionality; more of that to come in another upgrade. For now, please let us know what think of the new UI. And of course if there are key features you'd like to see added to the Oracle Dev Gym, happy to hear about those as well

    PL/SQL 101: Introduction to Object Types, a.k.a., Classes

    $
    0
    0
    PL/SQL is a procedural language - mostly. But it can also be used to implement object-oriented features in Oracle Database. In "from the ground up" object-oriented languages like Java, everything is defined in classes. In Oracle Database, we have object types.

    In this post, I introduce you to the basics of object types, and kick off a series of posts exploring many of the features of these programming elements.

    You do not need to be an expert in object orientation to understand the basics of object types. I know this for a fact, because have no formal training in object orientation and was able to sort out at least the basics.

    Before proceeding, though, you might wonder when you would want to use object types. Use cases include:
    • You'd like to apply object-oriented principles in your PL/SQL-based development. This makes sense if you are an experienced O-O developer and new to PL/SQL.
    • You want to use the many features of Oracle Database that rely on object types for their implementation, such as Oracle Spatial.
    • You are building a pipelined table function that returns more than one column per row, and you are not yet running an Oracle Database 18c or higher database. In this case, you must use a nested table of object type instances (explained below); you cannot use record types.
    A great example of an O-O implementation using object types is utPLSQL v3. This is one of the most active and popular open source PL/SQL projects. It helps you automate testing of all kinds of PL/SQL program units. Check it out!
    Here's a quick summary of some terminology relevant to object orientation:
    • Attributes: tables have columns, records have fields. Object types have attributes (and there's no such thing as a private attribute).
    • Methods: packages have procedures and function. Object types have methods (which are procedures or functions). 
    • Inheritance: you can create object types that are subtypes of other types. A subtype inherits attributes and methods from the parent(s).
    • Constructors: functions that return a new instantiation of a type. Oracle provides a pre-defined constructor; you can also "roll your own."
    I could add more complex terminology like dynamic polymorphism, but then you might just decide to read a different blog post. So we explore more interesting and complex topics like that one later.

    The best way to learn new elements of a programming language is to look at and write code, so let's dive right in with some examples.

    I don't know about you, but I like to eat. And even if you don't like eating as much as I do, you need to eat. So I am going to build a little hierarchy of types and subtypes related to food.

    I start with the "root" type. Not surprisingly, I will call it "food", and add a suffix of "ot" to indicate it's an object type. I suppose it's my "relational roots", but I like to keep un-suffixed names reserved for relational tables, so I might have a food table and a food_ot object type. You, of course, are welcome and encouraged to come up with and follow whatever standards suit you and your team.

    Here is the food object type:
    CREATE TYPE food_ot AS OBJECT (
    name VARCHAR2(100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100)
    )
    NOT FINAL;
    It looks a lot like a table creation statement, doesn't it? For example:
    CREATE TABLE food_ot (
    name VARCHAR2(100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100)
    )
    Very similar. Of course a table does not have "NOT FINAL" clauses, but before I address that let's talk about the differences between a table and an object type.

    A table is a container for rows of data. So the table creation statement lists the columns and their datatypes. Of course, it can include much more, such as specifications of primary and foreign keys (although usually these are added using their own separate DDL statements).

    The bottom line, though, is that you create a table so you can stuff it full of data with insert statements, modify that data with update statements, remove rows with delete statements, and query data with select statements.

    An object type, on the other hand, is a description of a type of data (I suppose that's why it is called a type). It does not contain anything. You could, in fact, use an object type as the type of a column in a relational table!

    So an object type is a type, just like a record type is a type. Which means you can declare variables based on the type. A variable declared from a record type is a record. A variable declared or instantiated from an object type is called....wait for it....an object type instance.

    Yes, I suppose it should be called an object. But we use the term "object" or "database object" to refer to a wide variety of things stored in the database. So: object type instance.

    OK, so I've created my type. What can I do with it? Let's declare an instance and try to use it. But first...since I mentioned that an object type is like a record type, let's take a look at the kind of code I would write for a record type.
    DECLARE
    TYPE food_rt IS RECORD (
    name VARCHAR2 (100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100)
    );

    my_favorite_vegetable food_rt;
    BEGIN
    my_favorite_vegetable.name := 'Brussels Sprouts';
    DBMS_OUTPUT.put_line (
    my_favorite_vegetable.name || ' are yummy!');
    END;
    /

    Brussels Sprouts are yummy!
    And now I will simply use the object type instead of record type.
    DECLARE
    my_favorite_vegetable food_rt;
    BEGIN
    my_favorite_vegetable.name := 'Brussels Sprouts';
    DBMS_OUTPUT.put_line (
    my_favorite_vegetable.name || ' are yummy!');
    END;
    /

    ORA-06530: Reference to uninitialized composite
    Uh-oh. And here you see your first glimpse into how object types are handled differently than non-object-oriented elements of PL/SQL.

    With object type instances, just like with nested tables and varrays (all interested as part of the evolution to a object-relational database in Oracle8), you must initialize the instance before you can work with it. You initialize it by calling a constructor function:
    DECLARE
    my_favorite_vegetable food_ot
    := food_ot ('Brussels Sprouts', 'Vegetables', 'Dirt');
    BEGIN
    DBMS_OUTPUT.put_line (
    my_favorite_vegetable.name || ' are yummy!');
    END;
    /

    Brussels Sprouts are yummy!
    And in case you are aware of named notation and wondering if you can use it in a constructor...you bet!
    DECLARE
    my_favorite_vegetable food_ot
    := food_ot (name => 'Brussels Sprouts',
    food_group => 'Vegetables',
    grown_in => 'Dirt');
    BEGIN
    DBMS_OUTPUT.put_line (
    my_favorite_vegetable.name || ' are yummy!');
    END;
    /

    Brussels Sprouts are yummy!
    What if, however, you wanted to be able initialize a new food instance with just the name? In that case you will want to build your own constructor, by adding code to the object type. This means you will also need an object type body (and now you will see how object types also resemble packages).

    So I will re-create the type (another way that an object type is different from a table: you can use CREATE OR REPLACE - as long as other database objects do not depend on that type), and add two constructor functions: one that has no parameters and one that requires only the name to be provided.
    CREATE OR REPLACE TYPE food_ot AS OBJECT
    (
    name VARCHAR2 (100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100),
    CONSTRUCTOR FUNCTION food_ot
    RETURN SELF AS RESULT,
    CONSTRUCTOR FUNCTION food_ot (NAME_IN IN VARCHAR2)
    RETURN SELF AS RESULT
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY food_ot
    IS
    CONSTRUCTOR FUNCTION food_ot
    RETURN SELF AS RESULT
    IS
    BEGIN
    RETURN;
    END;

    CONSTRUCTOR FUNCTION food_ot (NAME_IN IN VARCHAR2)
    RETURN SELF AS RESULT
    IS
    BEGIN
    self.name := NAME_IN;
    RETURN;
    END;
    END;
    /
    Now I can initialize my instance in either of these two "styles":
    DECLARE
    my_favorite_vegetable food_ot
    := food_ot ('Brussels Sprouts');
    BEGIN
    DBMS_OUTPUT.put_line (
    my_favorite_vegetable.name || ' are yummy!');
    END;
    /

    DECLARE
    my_favorite_vegetable food_ot
    := food_ot ();
    BEGIN
    my_favorite_vegetable.name := 'Brussels Sprouts';
    DBMS_OUTPUT.put_line (
    my_favorite_vegetable.name || ' are yummy!');
    END;
    /
    OK, so that gets us up and running with declaring and assigning values to an object type instance.

    So about that NOT FINAL clause in the food type definition?

    You include that clause when you want to indicate that you plan to create subtypes of that supertype (in this case, also the root type): when you want to build an object type hierarchy. That's where object-oriented features get really interesting and I will cover that in my next blog post.

    A "SQL Guard" utility to help with SQL injection - available "AS IS"

    $
    0
    0
    I was asked the following question via a Twitter DM today:
    I stumbled upon a mention of "SQL Guard" and I was wondering what has become of it. Google confused me. Has it been published under some other name, or is it part of a bigger product?
    Great question! I sort of vaguely remembered creating something like that. So I searched around in my folders and found....SQL Guard!

    NOTE: SQL Guard is not a product I offer anywhere on the Internet. There are things out there called SQL Guard. I am not challenging their copyright or trademark or anything else.

    What you find will find below is the document I wrote in 2007 explaining my ideas and code.

    I am making it available AS IS through this site and a set of LiveSQL scripts, links below. I make no warranty about usability or usefulness or even basic validity of this code. If it helps you great. If it destroys your database I WARNED YOU.

    LiveSQL: create tables
    LiveSQL: create package specification
    LiveSQL: create package body
    LiveSQL: install metadata
    LiveSQL: demonstration script

    Overview

    SQL injection occurs when a program executing dynamic SQL or dynamic PL/SQL allows a malicious user to "inject" code into the dynamic statement that was not originally intended by the author of the program. SQL injection can cause major security problems in one's code. There are several things programmers can do to minimize the chance of SQL injection, such as:
    • Avoid concatenation inside your dynamic SQL statements. Instead use bind variables wherever possible.
    • If text will be concatenated, then check the contents of that text to see if you can find any characters indicating possible trouble.
    • In particular, do not allow the execution of supplied packages that interact (or can be used to interact) with the operating system, such as UTL_SMTP, UTL_FILE, UTL_TCP (and related packages) and DBMS_PIPE.
    • "Lock down" user accounts so that they have the minimal privileges needed to run the application; avoid granting privileges that malicious users can exploit.
    • When defining programs that use dynamic SQL, be sure to define the subprogram as AUTHID CURRENT_USER. That way, all SQL statements will be executed using the privileges of the currently-connected schema.
    The problem with these recommendations is that they rely on the proactive diligence of an individual developer or DBA to minimize the risk. That should be done, but perhaps something more could be offered to developers.
    The sql_guard package described in this document takes another approach: analyze the string provided by the user to see if it contains a risk of SQL injection. The programmer can then decide whether or not to execute that statement and perhaps to log the problematic text (note: a logging mechanism is not yet availabled in SQL Guard). 
    The tests used to determine if there is a risk of SQL injection can be configured by the user. In other words, SQL Guard comes with a set of pre-defined tests. You can remove from or add to that list of tests, to check for SQL injection patterns that may be specific to their own application environment.
    I don't think it is possible to ever come up with a mechanism that will with 100% certainty trap all possible SQL injection (in fact, it is quite possible that my SQL Guard idea is deemed by those with more experience in security to be worthless).  - Steven Feuerstein 
    Having said all that, if you decide to use SQL Guard, you should (it seems to me) be able to achieve the following:
    1. Increase awareness of SQL injection amongst your developers. 
    2. Thwart the most common SQL injection attacks.
    3. More easily analyze one's code base to identify possible injection pathways.
    There are three main areas of functionality to SQL Guard:
    • Define the tests used to identify possible SQL injection
    • Analyze text for possible SQL injection
    • Use the SQL Guard-processed text in your dynamic SQL statements 
    They are explained in the following sections.

    Installing SQL Guard

    SQL Guard consists of the following elements:
    • sql_guard package: all of the functionality of SQL Guard, in one package
    • sql_guard_tests table: each row in this table defines a test that will be run to identify possible SQL injection
    • sql_guard_drivers table: each row in this table defines a test case that can be used to exercise sql_guard's injection detection capabilities
    • sql_guard_seq sequence: used to generate primary keys for the drivers table
    To install SQL Guard, run the sql_guard_install.sql script in your Oracle schema.
    The sql_guard_demo.sql file contains some demonstrations of using SQL Guard.

    A quick guide to SQL Guard functionality

    Define tests used to identify possible SQL injection

    SQL Guard comes with a set of pre-defined tests for SQL injection. You can add your own tests as needed.

    To add your own tests, use one of the following programs. Here is a description of the arguments used in all three of the variations:
    Argument Name
    Usage
    test_string_in
    the dynamic code or string that will be applied to the SQL text to test for injection
    name_in
    the name of the test, and it must be unique. If you do not provide a name, SQL Guard will create one from the test string.
    operator_in
    the operator to be used when testing the test string against the SQL text. The default is LIKE; you can also specify that the test string is a PL/SQL expression which must evaluate to a Boolean or a regular expression that will be used in a call to the REGEXP_LIKE function of PL/SQL. Default is 'LIKE'. The other values that you can pass are:
    sql_guard.c_expression_test
    sql_guard.c_regexp_like_test
    surround_with_pct_in   
    Pass TRUE if you want the test string automatically surrounded by % to do LIKE wildcarding. Default is TRUE.
    apply_once_in
    Pass TRUE if you want this test to be used only in the next evaluation of your SQL text, and after that removed. Default is FALSE.
    This feature might be useful if you have specific injection tests to run for a single context, but in general should not be considered an issue.
    1. Add Test: this most generic of the add test programs adds a row of data to the sql_guard_tests table. You must specify all values for arguments as described above.
    2. Add PL/SQL Expression Test: use this variation to add a test which is made up of a PL/SQL expression.
    If you include the string '[SQLTEST]' in your expression, then SQL Guard will automatically replace that tag with the text being evaluated for SQL injection.
    3. Add Regular Expression Test: use this variation to add a test which uses a regular expression in a LIKE operation to detect possible injection.
    If you include the string '[SQLTEST]' in your expression, then SQL Guard will automatically replace that tag with the text being evaluated for SQL injection.
    To remove a test, call the sql_guard.remove_test program. You must identify your test by its unique name.

    Analyze text for possible SQL injection

    Someone "out there" has provided some text which is supposed to be concatenated into a dynamic SQL statement. Is it safe? Here are the programs you can call in SQL Guard to try to answer that question:
    1. analyzed_sql: you pass the SQL text to this function and whether or not you want to apply the current tests just for this occurrence (default FALSE) and it returns the SQL Guard handle. This handle is an opaque integer which points back to the full set of information produced by the analysis (see the next section for details on how to access the information referenced by the handle).
    The important point to note here is that after calling the analysis function, the programmer (user of SQL Guard) will no longer refer directly or pass as an argument the SQL text. Instead, she works with the handle.
    2. sql_safety_level: call this function if you simply want to find out if SQL Guard thinks the text is safe or not. 
    Generally, you will use the analysis function and not the second "quick check" function. I added sql_safety_level to make it easier to automatically test SQL Guard.

    Use SQL Guard-processed text

    I assume we all agree that the most fundamental rule regarding SQL injection is that any SQL text that can originate from outside the application itself (typed in by some sort of user; let's call it "external SQL") cannot be trusted, and must be checked for validation.
    So the first thing you do is identify all those programs in your application that accept external SQL. Then for each of these programs, make the following changes:
    • Before calling that program, call the sql_guard.analyzed_sql to analyze the SQL text and determine the safety of that text. This function returns a "handle" (a pointer) to a bundle of information maintained within the SQL Guard package about that SQL text. 
    • Then, instead of passing the SQL statement as an argument to your program, pass the SQL Guard handle. 
    • Inside your program, you call the sql_guard.sql_is_safefunction to determine if SQL statement is safe. If so, extract the SQL text from the SQL Guard via the handle, and then execute your statement. 
    Of course, you can also put all of these steps into your single program, but I think that there might be an advantage to separating out these different steps and setting a very simple rule:
    Programs that execute dynamic SQL statements can only be passed a SQL Guard handle, never the SQL text itself.
    If this rule is the standard in your organization, you can then see if all programs that execute dynamic SQL also use SQL Guard. Note: the sql_guard.show_no_sql_guard_useprocedure offers a very crude example of such an automated review mechanism. It needs to be improved with more sophisticated scanning of source code.
    Here are the programs in the sql_guard package that retrieve information for a given handle:
    FUNCTION sql_safety_level(sql_handle_inINanalyzed_sql_handle_t)
      RETURNPLS_INTEGER;
    An overloading that accepts the SQL Guard handle and returns the safety level associated with the SQL text for that handle.
    FUNCTION sql_is_safe(sql_handle_inINanalyzed_sql_handle_t)
      RETURNBOOLEAN;
    Returns TRUE if the SQL text was determined to be free of SQL injection dangers.
    FUNCTION injection_detected(sql_handle_inINanalyzed_sql_handle_t)
      RETURNBOOLEAN;
    Returns TRUE if the SQL text was determined to be contain possible SQL injection.
    FUNCTION sql_text(sql_handle_inINanalyzed_sql_handle_t)
      RETURNVARCHAR2;
    Returns the SQL text analyzed by SQL Guard. 
    FUNCTION injection_indicators_found(sql_handle_inINanalyzed_sql_handle_t)
      RETURNDBMS_SQL.varchar2s;
    Returns a list of the tests that SQL Guard used to find that the SQL text contained possible SQL injection. 

    Ideas for Enhancements

    here are the things I can think of that still could  be done with SQL Guard. 
    • Integrate logging: offer an option to log all possible SQL injection text for later analysis and fine tuning of rules.
    • Allow for specifying of different levels of danger in the tests. For example, text with a call to DBMS_SQL might be considered "code red" and always rejected, while text with the word "DROP" in it might only be something you want to track, log, and anlyze.
    • Application-specific sets of tests. For example, with the Oracle Applications or SAP, you can probably come up with a set of typical injection attempts against those tables and other database objects.

    The Ten Commandments of PL/SQL Development

    $
    0
    0
    A long, long time ago in a blog long ago lost to the shrouds and clouds of memory, I published a Ten Commandments of PL/SQL Development.

    A Twitter follower recently asked me for them, and lo and behold actually found them lurking in a Word doc on my drive. So I have decided to share them with you, largely unchanged. In some places I have struck through entirely irrelevant or outdated text, and also offered updates for 2019.

    1. Thou shalt encapsulate your SQL statements behind procedure and function calls.

    Sure, it's really, really easy to write SQL in your PL/SQL programs -- in fact, it's way too easy. SQL is the "Achilles' heel" of your application code. You code and you code and you code, and before you know it, you have dozens of variations of the same SQL statement making their negative performance impact known in your application. You can't analyze the impact of data structure changes and you find enhancements to be very expensive. Solution? Put all your SQL inside packaged procedures, functions, or cursors. Maximize re-use of pre-parsed statements. Build comprehensive package APIs to underlying tables or business objects.

    2. Thou shalt not hard-code breakage points into your programs.

    "I know this will never change." Famous last words! We all know you're supposed to avoid hard-coding, but what does that really mean in PL/SQL? Examples of hard-coding in PL/SQL include: "magic values" that will "never" change, declarations using explicit datatypes like VARCHAR2 and NUMBER (use %TYPE and %ROWTYPE instead), every SQL statement (they "freeze" your structures and relationships), cursors with local variables reference directly inside the query, FETCHing into a list of variables, and even more. Scary, isn't it?.

    3. Thou shalt use packages as the building block of all your applications.

    Ever tried to understand, much less fix, a program that stretched on for a thousand seemingly endless lines of poorly-documented, totally un-modularized code? There's gotta be a better way -- and in PL/SQL there is: use top-down design techniques, backed up by nested modules and reusable, packaged programs to create readable, manageable applications. Always start with a package, even for single programs, and you will develop and evolve your applications more smoothly.

    4. Thou shalt hide package-level data in the package body. 

    If you define data structures in the package specification, you lose control over that data. Any program with EXECUTE authority on that package can modify the data at will. Sound familiar? That's right, it acts an awful lot like the awful :GLOBAL variables of Oracle Forms. With specification-defined data, you cannot guarantee its integrity, you cannot track access to that data, you cannot easily change the structure you use to implement that data. Instead, build "get and set" programs around your data: a procedure to set the value and a function to retrieve the value. Thank me later.

    2019 update: In stateless execution environments like web pages and mobile apps, you should generally not rely on package-level data/state at all, and minimize the code that is executed in package initialization sections.

    5. Thou shalt learn about and use the built-in packages (DBMS_ and UTL_) from Oracle.  

    There's a lot more to the PL/SQL language than the language constructs and basic functions (defined, for the most part, in the default STANDARD package!). Ever felt the need to execute dynamic SQL method 4, run an operating system program, read/write files, run scheduled jobs from within the database, manipulate RAWs and LOBs or implement row-level security? You'd have a heck of a time doing any of that without using a built-in package. They are invaluable and should make regular appearances in your code.

    2019 update: Bookmark the link to the PL/SQL Packages and Types Reference, and scan through the "Changes in this release" section each time a new version is announced..

    6. Thou shalt leverage the Oracle Data Dictionary to analyze and generate code, and implement standards. 

    PL/SQL is a very different creature from many other languages: all source code must be compiled and stored in the Oracle Database before it can be executed. As a consequence, information about that code is available in a large number of data dictionary views. Access these views to generate standards-based code, verify compliance with standards, and manage your applications.

    2019 update: most important of all, take advantage of PL/Scope, a powerful code analysis utility built right into the PL/SQL compilation process.

    7. Thou shalt remove thy "Oracle Expert" chip from thy shoulder. 

    No one knows everything about anything. Too many Oracle developers (especially consultants?) carry enormous chips on their shoulders, with two alarming consequences: (a) they are most interested in showing everyone how smart they are, and (b) they don't want to share what they know because it might put a damper on their hourly rates. We will all benefit by reading, studying, and critiquing the code of others.

    And we should share freely that knowledge will others; the PL/SQL Pipeline at www.revealnet.com/plsql-pipeline is an excellent mechanism for doing this.

    2019 update: these days, of course, much of the action takes place on StackOverflow, with AskTOM still going plenty strong, due to the expertise and dedication of Connor McDonald and Chris Saxon, two Oracle Developer Advocates.

    8. Thou shalt complain about Oracle technology, but then do something about it. 

    We all love to complain, but complaints don't solve problems. If Oracle has implemented functionality which you love to whine about, then first you should see if you can fix it in your application. In some cases, for example, you can build an encapsulation around an incomplete Oracle implementation and then improve upon it.

    Check out the RevealNet PL/Vision code library and the Oracle Built-in Packages (O'Reilly and Associates) companion disk for lots of examples. I also offer an example of doing this for the LOG ERRORS feature on Oracle LiveSQL.

    2019 update: we also encourage you to add your idea for improving our software at the Database Ideas forum. And, of course, if you find a bug, please report it to Oracle Support.

    9. Thou shalt tune your System Global Area to execute PL/SQL code efficiently. 

    You are convinced that PL/SQL is the way to go. You write hundreds of programs and packages. You install the application on your database -- and users complain bitterly of slow performance. The problem? Sure, it might be your code, but don't forget to tune the SGA. Your shared pool area must be large enough to contain the commonly-executed elements of your application. You should also pin frequently-needed programs with the DBMS_SHARED_POOL package. Get your DBA involved in the code development and deployment process!

    2019 update: wow, does this feel dated. That's all you've got, Steven? Tune your SGA? :-) The key thing as I say at the end is "Get your DBA involved in the code development and deployment process!". But you should also check out the PL/SQL User Guide's Optimization and Tuning section

    10. Thou shalt not develop your code in Notepad and compile/test it in SQL*Plus. 

    Same goes for vi (! what can I say? I wrong this a long time ago :-)), though vi is admittedly not as brain-deadening a development tool as Notepad. Why should you work in an environment more primitive than that available to any Fortran or C programmer? Join the twentieth century before it's too late and get yourself a GUI (yes, almost all of them Windows-based) front-end for PL/SQL development, debugging and testing. You waste dozens, if not hundreds, of hours of development time each year by relying on "lowest common denominator" tools.

    2019 update: it probably goes without saying, but I will say it anyway: Download and try SQL Developer and its sister command-line tool, SQLcl. They are used by millions of developers, including me, run on Windows, macOS, Linux....and THEY ARE FREE.

    Some other Ten Commandments of Programming

    Lukas Eder offers his own Ten Commandments here, and as anyone familiar with Lukas would expect, they are entertaining and spot-on.




    PL/SQL 101: Inheritance and Object Types

    $
    0
    0
    In my first post on object-oriented programming in PL/SQL, I introduced the object type (our version of a class) and showed how you needed to instantiate an object type instance before you could work with it.

    In this post, I explore how to create a hierarchy of types. Type or class hierarchies are a key concept in object-oriented programming, because of the way that subtypes inherit attributes and methods from their parent types. All the code you see below can be run in Oracle LiveSQL through this script.

    Let's revisit the root type of my hierarchy: food.
    CREATE TYPE food_ot AS OBJECT (
    name VARCHAR2(100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100)
    )
    NOT FINAL
    ;
    A very simple type: it contains just three attributes and no methods (procedures or functions). It also contains the NOT FINAL clause. This means, rather obviously, that I am "not done." What that means in the context of object types is that I might want to create a subtype of this type.

    I don't have to do so. I can mark it as NOT FINAL and never create a subtype. But since this is a blog post about inheritance, let's do it!

    If food is the most general type of, well, food, what would a more specific type of food be? If you, like me, have a ferocious sweet tooth, you will know very well that desserts are a type of food. And specifically:
    Every dessert is a food, but not every item of food is a dessert.
    Brussels sprouts, for example, are not a dessert - well, at least in my experience and according to my palette. I suppose there may be a truly sprouts-crazy culture (or parallel universe) in which you eat all sorts of sprouts for every phase of a meal, and Brussels sprouts are a highly acclaimed dessert.

    However, in this universe, on this planet, in this blog post, no.

    Cakes, pies, ice cream, fruits, cookies, candies, sorbets, donuts....now, those are desserts, according to my way of thinking. And every single one of them are also food. That means that dessert is a subtype of food. So let's create a dessert object type.
    CREATE TYPE dessert_t UNDER food_ot (
    contains_chocolate CHAR(1),
    year_created NUMBER(4)
    )
    NOT FINAL
    ;
    Notice the UNDER food_ot syntax in place of the IS OBJECT. Since I am creating a new type "under" food, it clearly is an object type, so that verbiage is unnecessary and redundant and not needed. Get it? :-)

    Before going any further, let's use this type.
    DECLARE
    l_cherry_pie dessert_ot
    := dessert_ot ('Cherry Pie',
    'Baked Goods',
    'Oven',
    'N',
    1492);
    BEGIN
    DBMS_OUTPUT.put_line (l_cherry_pie.name);
    DBMS_OUTPUT.put_line (l_cherry_pie.year_created);
    END;
    /

    Cherry Pie
    1492
    [Confession: I don't really know when the first cherry pie was made.]

    Now, if you are familiar with object-oriented principles, this will come as no surprise. But if you are used to thinking in terms of tables and packages, say, what you see might be a real shock.

    The dessert type is declared with just two attributes; contains_chocolate and year_created. So how is it possible that I supply five values in the call to its constructor function and can display the value for name?

    The dessert instance inherited the name, food_group and grown_in attributes from its parent! They are automatically available in the constructor function (dessert's attributes are appended to the end of the list of food's attributes).

    And since the dessert type was also defined as NOT FINAL, I can keep going. Desserts are a fairly
    specific subset of foods, but then of course there are many different types of dessert. And so, I hereby give you....cakes!
    CREATE TYPE cake_ot UNDER dessert_ot (
    diameter NUMBER,
    inscription VARCHAR2 (200)
    );
    /

    DECLARE
    l_yummy cake_ot
    := cake_ot ('Marzepan Delight',
    'Baked Goods',
    'Oven',
    'N',
    1634,
    8,
    'Happy Birthday!');
    BEGIN
    DBMS_OUTPUT.put_line (
    'Nothing says ' || l_yummy.inscription || ' like ' || l_yummy.name);
    END;
    /

    Nothing says Happy Birthday! like Marzepan Delight
    Hopefully you can how powerful and useful inheritance is. Without it, you would have to duplicate attributes all the way down the hierarchy. And it's not just attributes. You also inherit methods - the procedures and functions defined in super types.

    My next post in the series will dive into methods, but let's start with a simple example to demonstrate inheritance right now.

    Inheritance and Methods

    Lots of people grow their own food, but most people buy it. So let's add a method to calculate and return the price of any instance of food. Since I am returning a value, I will build a price function. Since it returns a value for an instance, it is a member method (as opposed to a static method, a distinction that will be explored in the next post).

    Now, with a type as generic as food, and with so few attributes, it's hard to come up with any sort of sophisticated pricing model. Given that situation, I will implement this rule for price:
    Add the number of characters in the name, food group and grown in attributes and multiply by 10.
    Before I show you the new versions of the code, I must point out that since I have already created a hierarchy of two types in my schema, the dessert_ot type is dependent on the food_ot type. This means that I will not be able to "create or replace" the food type until I drop the dessert type, with:
    DROP TYPE dessert_ot FORCE
    /
    OK, now I can add the method to my type specification:
    CREATE OR REPLACE TYPE food_ot AS OBJECT (
    name VARCHAR2 (100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100),
    MEMBER FUNCTION pricevRETURN NUMBER
    )
    NOT FINAL;
    /
    The specification now includes the header for the price function. By using the "MEMBER" keyword, I indicate that this function is executed "for" a specific instance of this object type, following the syntax:

    object_type.member_name

    Next, my type body:
    CREATE OR REPLACE TYPE BODY food_ot
    IS
    MEMBER FUNCTION price
    RETURN NUMBER
    IS
    BEGIN
    DBMS_OUTPUT.put_line ('Food price!');

    RETURN ( LENGTH (self.name)
    + LENGTH (self.food_group)
    + LENGTH (grown_in))
    * 10;
    END;
    END;
    /
    Notice the use of the SELF keyword. This is how you say "Perform specified operation on the instance to which this subprogram is attached." In this case, I sum the lengths (number of characters) of the three attributes and multiply by 10.

    And now I will invoke the method using dot notation as shown above:
    DECLARE
    l_food food_ot := food_ot ('Ramen Noodle Soup', 'Salt', 'Microwave');
    BEGIN
    DBMS_OUTPUT.put_line ('What do we see?');
    DBMS_OUTPUT.put_line (
    'The price of ' || l_food.name || ' = ' || l_food.price());
    END;
    /

    What do we see?
    Food price!
    The price of Ramen Noodle Soup = 300
    Now it is time to revisit the dessert type - this is, after all, a post about inheritance. When I create a subtype of a type that has methods, I can either inherit the supertype method or override it.

    First, let's go with the inherit approach and see how that works.
    CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1),
    year_created NUMBER (4)
    )
    NOT FINAL;
    /

    Since I inherit, the dessert type looks the same as it did before. And just as I could reference attributes of the supertype in the instance of the subtype, I can do the same thing with methods:<
    DECLARE
    /* M is for Maybe */
    l_dessert dessert_ot
    := dessert_ot ('Ice Cream',
    'Sugar',
    'Cow',
    'M',
    200);
    BEGIN
    DBMS_OUTPUT.put_line ('What do we see?');
    DBMS_OUTPUT.put_line (
    'The price of ' || l_dessert.name || ' = ' || l_dessert.price());
    END;
    /

    What do we see?
    Food price!
    The price of Ice Cream = 170
    But what if I have a completely different calculation for the price of a dessert? After all - it might contain chocolate, which is worth a pretty penny, indeed!

    In this case, I will override the parent method, replacing it completely with my new algorithm, influenced by the presence of chocolate and how long ago the dessert was created. Notice the use of the OVERRIDING keyword before MEMBER in the code below. It means exactly what it says. :-)
    CREATE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
    , year_created NUMBER (4)
    , OVERRIDING MEMBER FUNCTION price
    RETURN NUMBER
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY dessert_ot
    IS
    OVERRIDING MEMBER FUNCTION price
    RETURN NUMBER
    IS
    multiplier NUMBER := 1;
    BEGIN
    DBMS_OUTPUT.put_line ('Dessert price!');

    IF SELF.contains_chocolate = 'Y'
    THEN
    multiplier := 2;
    END IF;

    IF SELF.year_created < 1900
    THEN
    multiplier := multiplier + 0.5;
    END IF;

    RETURN (10.00 * multiplier);
    END;
    END;
    /
    Now let's see how the price methods work for both food and dessert.
    DECLARE
    l_food food_ot := food_ot ('Ramen Noodle Soup', 'Salt', 'Microwave');

    /* M is for Maybe */
    l_dessert dessert_ot
    := dessert_ot ('Ice Cream',
    'Sugar',
    'Cow',
    'M',
    200);
    BEGIN
    DBMS_OUTPUT.put_line ('What do we see?');
    DBMS_OUTPUT.put_line (
    'The price of ' || l_food.name || ' = ' || l_food.price());
    DBMS_OUTPUT.put_line (
    'The price of ' || l_dessert.name || ' = ' || l_dessert.price());
    END;
    /

    What do we see?
    Food price!
    The price of Ramen Noodle Soup = 300
    Dessert price!
    The price of Ice Cream = 15
    As you can see, the different price functions for each of the types were properly invoked.

    And the price of ice cream has plummeted!

    There you have it: inheritance of attributes and methods.

    And now that I've introduced methods, my next post in the series will dive into that topic more deeply, and touch on:
    • Static methods
    • Non-instantiable methods
    • Dynamic polymorphism
    It's gonna be fun, don't miss it! :-)

    P.S. Don't forget that if you'd like to try out all this code for yourself, all you have to do is run this LiveSQL script


    Object Type Methods, Part 3

    $
    0
    0
    Packages have subprograms (procedures and functions). Object types have methods.

    Object type methods are, still, procedures and functions. But there are also different types and characteristics of methods that only make sense in an object type, which supports inheritance and dynamic polymorphism.

    In this post, 3rd in my series on object types, I explore
    • Static methods 
    • Member methods 
    • Non-instantiable methods 
    • Invoking methods of super types
    All the code you see below can be run in Oracle LiveSQL through this script.

    Member Methods

    Member methods are methods applied to an instance of the type. Almost all the methods you ever write for an object type will be a member method. Assuming you are already familiar with writing PL/SQL functions and procedures, the most important thing to come up to speed on is the SELF value.

    Member methods have a built-in (implicit) parameter named SELF that denotes the object instance currently invoking the method.

    We'll explore member methods, including a reminder about overriding, with a to_string function. it's quite common for a class to have a method that returns a string representation. That's what the to_string method will do in our object types.
    CREATE OR REPLACE TYPE food_ot AS OBJECT (
    NAME VARCHAR2 (100),
    food_group VARCHAR2 (100),
    grown_in VARCHAR2 (100),
    MEMBER FUNCTION to_string RETURN VARCHAR2
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY food_ot
    IS
    MEMBER FUNCTION to_string RETURN VARCHAR2
    IS
    BEGIN
    RETURN 'FOOD! '
    || SELF.NAME
    || ' - '
    || SELF.food_group
    || ' - '
    || SELF.grown_in;
    END;
    END;
    /

    DECLARE
    squirrels_love_them food_ot :=
    food_ot ('Acorn', 'Protein', 'Tree');
    BEGIN
    DBMS_OUTPUT.put_line (squirrels_love_them.to_string());
    END;
    /

    FOOD! Acorn - Protein - Tree
    I could declare SELF explicitly as well, and it works exactly the same:
    CREATE OR REPLACE TYPE food_ot AS OBJECT (
    NAME VARCHAR2 (100),
    food_group VARCHAR2 (100),
    grown_in VARCHAR2 (100),
    MEMBER FUNCTION to_string (SELF IN food_ot) RETURN VARCHAR2
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY food_ot
    IS
    MEMBER FUNCTION to_string (SELF IN food_ot) RETURN VARCHAR2
    IS
    BEGIN
    RETURN 'FOOD! '
    || SELF.NAME
    || ' - '
    || SELF.food_group
    || ' - '
    || SELF.grown_in;
    END;
    END;
    /

    DECLARE
    squirrels_love_them food_ot :=
    food_ot ('Acorn', 'Protein', 'Tree');
    BEGIN
    DBMS_OUTPUT.put_line (squirrels_love_them.to_string());
    END;
    /

    FOOD! Acorn - Protein - Tree
    What if I want to change the value of an instance's attribute inside my member method? In that case, if it is a function, I must include SELF as a parameter and make it IN OUT. Suppose for example that I want to enforce upper case on all attribute values:
    CREATE OR REPLACE TYPE food_ot AS OBJECT (
    NAME VARCHAR2 (100),
    food_group VARCHAR2 (100),
    grown_in VARCHAR2 (100),
    MEMBER FUNCTION to_string (SELF IN OUT food_ot) RETURN VARCHAR2
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY food_ot
    IS
    MEMBER FUNCTION to_string (SELF IN OUT food_ot) RETURN VARCHAR2
    IS
    BEGIN
    /* Enforce upper case for all values */
    SELF.name := UPPER (SELF.name);
    SELF.food_group := UPPER (SELF.food_group);
    SELF.grown_in := UPPER (SELF.grown_in);

    RETURN 'FOOD! '
    || SELF.NAME
    || ' - '
    || SELF.food_group
    || ' - '
    || SELF.grown_in;
    END;
    END;
    /

    DECLARE
    squirrels_love_them food_ot :=
    food_ot ('Acorn', 'Protein', 'Tree');
    BEGIN
    DBMS_OUTPUT.put_line (squirrels_love_them.to_string());
    DBMS_OUTPUT.put_line ('Still upper case? ' || squirrels_love_them.name);
    END;
    /

    FOOD! ACORN - PROTEIN - TREE
    Still upper case? ACORN
    But as Vasily Suvorov so kindly pointed out in the comments for this post, that is not necessary if your member method is a procedure. Thanks, Vasily!

    Static Methods

    A static method is a method that is the same (unchanging, static) for all instances of the type. Another way to think about it is that it is a type-level method.

    Suppose, for example, that I want to keep track of a version number for my object type. It wouldn't change per instance of the type. It is a characteristic of the type itself. I would define and use that function as follows.
    CREATE OR REPLACE TYPE food_ot AS OBJECT
    (
    name VARCHAR2 (100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100),
    STATIC FUNCTION version RETURN VARCHAR2
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY food_ot
    IS
    STATIC FUNCTION version RETURN VARCHAR2
    IS
    BEGIN
    /*
    Version history
    2018-09-14 1.0.1 Type deployed to production
    2019-03-22 1.0.2 Added grown_in attribute
    */
    RETURN '1.0.2';
    END;
    END;
    /

    BEGIN
    DBMS_OUTPUT.put_line ('Version = ' || food_ot.version);
    END;
    /

    Version = 1.0.2
    Note that the SELF value isn’t available inside static method bodies; static methods have no “current object.” If you try, you will get this error:
    PLS-00201: identifier 'SELF.attribute_name' must be declared
    Static methods are inherited.
    CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
    , year_created NUMBER (4)
    );
    /

    BEGIN
    DBMS_OUTPUT.put_line (dessert_ot.version);
    END;
    /

    1.0.2
    They cannot be over-ridden using the OVERRIDING syntax. Instead, when you create a static method with the same name as a supertype, well, that takes precedence.
    CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
    , year_created NUMBER (4)
    , STATIC FUNCTION version RETURN VARCHAR2
    );
    /

    CREATE OR REPLACE TYPE BODY dessert_ot
    IS
    STATIC FUNCTION version RETURN VARCHAR2
    IS
    BEGIN
    RETURN 'v10.4.5';
    END;
    END;
    /

    BEGIN
    DBMS_OUTPUT.put_line (dessert_ot.version);
    END;
    /

    10.4.5
    What if a supertype has a static function and a subtype has a static procedure with same name? No problem!
    CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
    , year_created NUMBER (4)
    , STATIC PROCEDURE version
    );
    /

    CREATE OR REPLACE TYPE BODY dessert_ot
    IS
    STATIC PROCEDURE version
    IS
    BEGIN
    DBMS_OUTPUT.PUT_LINE ('v10.4.5');
    END;
    END;
    /

    BEGIN
    DBMS_OUTPUT.put_line (dessert_ot.version);
    dessert_ot.version;
    END;
    /

    1.0.2
    v10.4.5
    That was fun! OK, let's move on to a very interesting nuance when it comes to methods.

    Non-instantiable Methods

    This is not a third type of method. It is a way that you can define a member method. Use the NOT INSTANTIABLE clause to define requirements for the API you are building with your type hierarchy and methods.

    When you declare a method to be instantiable, you are define the header of the method, but not its implementation. This also means that an instance of that type cannot invoke the member method (after all, it's not implemented).

    Which also means that a subtype must implement an overriding method of the same signature for it to be instantiable (invoked as a method on an instance of the type).

    Let's take a look. I will re-create the food type and specify that any subtype must implement a price function for it to be instantiable (the logic here is that "food" all by itself is simply too generic to be able to calculate a price for it - but all subtypes must do so).
    CREATE OR REPLACE TYPE food_ot AS OBJECT (
    name VARCHAR2 (100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100),
    NOT INSTANTIABLE MEMBER FUNCTION price
    RETURN NUMBER
    )
    NOT FINAL NOT INSTANTIABLE;
    /

    DECLARE
    l_food food_ot := food_ot ('a', 'b', 'c');
    BEGIN
    DBMS_OUTPUT.PUT_LINE (l_food.name);
    END;
    /

    PLS-00713: attempting to instantiate a type that is NOT INSTANTIABLE
    I can no longer declare variables based on the food_ot type.

    Notice that when I declare at least one member method to be NOT INSTANTIABLE, I must also do the same for the type as a whole.

    Now I will declare the dessert subtype, and implement a price method. I can then instantiate variables of this type.
    CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
    , year_created NUMBER (4)
    , OVERRIDING MEMBER FUNCTION price
    RETURN NUMBER
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY dessert_ot
    IS
    OVERRIDING MEMBER FUNCTION price
    RETURN NUMBER
    IS
    multiplier NUMBER := 1;
    BEGIN
    DBMS_OUTPUT.put_line ('Dessert price!');

    IF SELF.contains_chocolate = 'Y'
    THEN
    multiplier := 2;
    END IF;

    IF SELF.year_created < 1900
    THEN
    multiplier := multiplier + 0.5;
    END IF;

    RETURN (10.00 * multiplier);
    END;
    END;
    /

    DECLARE
    l_apple dessert_ot := dessert_ot ('Apple', 'Fruit', 'Tree', 'N', -5000);
    BEGIN
    DBMS_OUTPUT.PUT_LINE (l_apple.name);
    END;
    /

    Apple
    A subtype doesn't have to provide an implementation for the price function. It can "pass along" that responsibility to a subtype of its own. But then that type will have to declared NOT INSTANTIABLE.

    Invoking Supertype Methods

    What if you don't want to completely override a supertype method? What if you want to use it, but also modify its behavior? In that case, it will come in very handy to be able to explicitly invoke a supertype's method.

    In the example below, I override the to_string method of the food_ot type to include dessert-specific information in the string returned. But I want to still take advantage of the parent's to_string function. To achieve this effect I use the SELF as parent_type syntax.
    CREATE TYPE food_ot AS OBJECT (
    name VARCHAR2 (100),
    food_group VARCHAR2 (50),
    grown_in VARCHAR2 (100),
    MEMBER FUNCTION to_string RETURN VARCHAR2
    )
    NOT FINAL;
    /

    CREATE OR REPLACE TYPE BODY food_ot
    IS
    MEMBER FUNCTION to_string RETURN VARCHAR2
    IS
    BEGIN
    RETURN 'FOOD! '
    || SELF.NAME
    || ' - '
    || SELF.food_group
    || ' - '
    || SELF.grown_in;
    END;
    END;
    /CREATE OR REPLACE TYPE dessert_t UNDER food_ot (
    contains_chocolate CHAR (1)
    , year_created NUMBER (4)
    , OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2
    );
    /

    CREATE OR REPLACE TYPE BODY dessert_ot
    IS
    OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2
    IS
    BEGIN
    /* Add the supertype (food) string to the subtype string.... */
    RETURN 'DESSERT! With Chocolate? '
    || contains_chocolate
    || ' created in '
    || SELF.year_created
    || chr(10)
    || '...which is a...'
    || (SELF as food_ot).to_string;
    END;
    END;
    /
    And as you can see, that nested invocation of the supertype method works just fine:
    DECLARE
    TYPE foodstuffs_nt IS TABLE OF food_ot;

    fridge_contents foodstuffs_nt
    := foodstuffs_nt (
    food_ot ('Eggs benedict', 'PROTEIN', 'Farm')
    , dessert_ot ('Strawberries and cream'
    , 'FRUIT', 'Backyard', 'N', 2001)
    );
    BEGIN
    FOR indx in 1 .. fridge_contents.COUNT
    LOOP
    DBMS_OUTPUT.put_line (RPAD ('=', 60, '='));
    DBMS_OUTPUT.put_line (fridge_contents (indx).to_string);
    END LOOP;
    END;
    /

    ============================================================
    FOOD! Eggs benedict - PROTEIN - Farm
    ============================================================
    DESSERT! With Chocolate? N created in 2001
    ...which is a...FOOD! Strawberries and cream - FRUIT - Backyard

    Method Chaining

    Method chaining occurs when you connect up a sequence of methods (all of them functions at least until you get to the very end, when you could have a procedure) with dot notation. 

    It's not something you can do with packaged subprograms, and it can look awfully strange to procedural developers. Here's an example.

    Suppose I create this object type:
    CREATE OR REPLACE TYPE chaindemo_ot AS OBJECT (
    x NUMBER, y VARCHAR2(10), z DATE,
    MEMBER FUNCTION setx (x IN NUMBER) RETURN chaindemo_ot,
    MEMBER FUNCTION sety (y IN VARCHAR2) RETURN chaindemo_ot,
    MEMBER FUNCTION setz (z IN DATE) RETURN chaindemo_ot);
    I can then
    DECLARE
    c chaindemo_ot := chaindemo_ot (NULL, NULL, NULL);
    BEGIN
    c := c.setx(1).sety('foo').setz(sysdate);
    END;
    The executable statement above really just acts as the equivalent of:
    c := c.setx(1);
    c := c.sety('foo');
    c := c.setz(sysdate);
    Each function returns a typed object as the input to the next function in the chain. The implementation of one of the methods appears in the following code (the others are similar):
    MEMBER FUNCTION setx (x IN NUMBER) RETURN chaindemo_t IS
    l_self chaindemo_t := SELF;
    BEGIN
    l_self.x := x;
    RETURN l_self;
    END;
    Here are some rules about chaining :
    • You cannot use a function’s return value as an IN OUT parameter to the next function in the chain. Functions return read-only values.
    • Methods are invoked in order from left to right.
    • The return value of a chained method must be of the object type expected by the method to its right. 
    • A chained call can include at most a single procedure.
    • If your chained call includes a procedure, it must be the rightmost method in the chain.

    Comparing Two Object Type Instances

    Can you compare two instances of the same object type for equality? What if you want to implement the concept of an order to an object type, so you can answer the question:
    Is Instance A > Instance B?
    For that, you need to build a comparison method (MAP or ORDER). I will cover this in my next post in this series. So make sure to subscribe to my blog and my Twitter feed so you will see the announcement when I finish it.

    Check Out the Series

    Visit this post that gives you quick access to all the articles in the series.

    P.S. Don't forget that if you'd like to try out all this code for yourself, all you have to do is run this LiveSQL script.
    Viewing all 312 articles
    Browse latest View live