Using Oracle Database 10g PL/SQL New Features
In this tutorial, you learn about the new PL/SQL features that are introduced in Oracle Database 10g.
Approximately 30 minutes
This tutorial covers the following topics:
| Overview | |
| Prerequisites | |
| Using PL/SQL Conditional Compilation | |
|
Examining Bulk Binding with the INDICES OF and VALUES OF Keywords |
|
| Summary |
Place the cursor over this icon to load and view all the screenshots for this tutorial. (Caution: This action loads all screenshots simultaneously, so, depending on your Internet connection, may result in a slow response time.)
Note: Alternatively, you can place the cursor over an individual icon in the following steps to load and view only the screenshot associated with that step. You can hide an individual screenshot by clicking it.
In Oracle Database 10g, several enhancements are available in the PL/SQL language. In this overview section, you cover the following topics:
| 1. | |
| 2. | Examining Bulk Binding Enhancements |
| 3. | Debugging PL/SQL with JDeveloper |
PL/SQL Conditional Compilation
Oracle Database 10g Release 2 introduces support for the conditional compilation of PL/SQL. New syntax allows the compiler to select parts of a program, based on specified conditions, for compilation. Application developers use conditional compilation by embedding directives in their PL/SQL source program. When the PL/SQL program is submitted for compilation, the directives are evaluated to select the intended parts of the program to be compiled. This feature is very useful in the following cases:
|
Applications often contain debugging or tracing code that is written specifically for testing purposes during the development cycle. You don’t want this code to run when the application runs in production. In earlier releases, you had either to remove this code completely for production (hindering subsequent debugging) or to surround it with run-time IF tests (incurring a run-time cost in both program size and execution speed). Now you can enable debugging or tracing functionality in the development environment and disable that functionality in the production application.
|
|
Applications often contain self-checking code, to comply with good programming practices. Such code raises an exception if an invariant on which the program design depends is violated. Such exceptions ideally should never be raised in the production environment. However these self-tests incur a run-time cost especially when they occur inside deeply nested loops. But to remove them all, after system testing is done and before the application goes live, would compromise the ability of the debugging team to diagnose bugs that were first seen in the production environment. Few development shops maintain parallel copies of an application’s source: one for debugging and one for production. PL/SQL conditional compilation supports a new best practice: by selectively compiling the self-checks, the diagnosing benefit is retained without cost for the production environment.
|
|
Packages usually contain private "helper" subprograms and, of course, these need to be tested during development. But because they're private, they can't be tested by an external test harness. By declaring these "private" subprograms within a conditional directive in the package specification, you can call these programs from outside the package during development and testing and then conceal them in the production environment.
|
|
Application code often calls “service” subprograms. Such a service subprogram might return a PL/SQL record describing an employee when called with the employee's unique identifier. Best practice dictates that this service routine raises a well-defined exception if the supposedly unique key finds more than one record. (This would occur in the event of data corruption, caused by a different, and buggy, component of the total system.) Best practice further dictates that the application code that calls the service subprogram must behave correctly when this exception is raised, for example by showing the user a polite and relatively non-specific “problem occurred” message and by logging all the relevant information to specify the context in which the exception was raised to a trace file. Of course, this robustness feature of the application code must be tested during the development cycle. It's often tricky and time-consuming to contrive the corrupt data that would cause the service subprogram to raise the exception. Conditional compilation lets you achieve the same effect cheaply by conditionally including code in the service subprogram to raise the given exception at will.
|
|
During development, programmers often create two different versions of a compilation unit that accomplish the same result. Both versions have to be tested to determine the most efficient version. Usually, the two different versions are substantially textually identical but differ in small critical spots distributed over the whole source file. In such cases it's easier, and less prone to human error, to write one program containing both versions rather than to write two separate programs. Conditional compilation comfortably supports this approach where earlier a hand-crafted scheme for generating the two versions of the source code would have been needed.
|
|
Some applications must be written to run on not only the latest version of Oracle Database but also on earlier versions. Typically, the development shop wants to maintain only a single body of source code. This has forced developers to avoid using the new and powerful features (exposed by new PL/SQL syntax) in the latest release which has penalized users who do deploy the code on the latest release. Conditional compilation can be used to select the optimal version of the program source code when the program is installed on the latest release and a fallback when it is installed on an earlier release.
|
Conditional compilation has been made available in Oracle Database 10g Release 10.1.0.4 and later.
Examining Bulk Binding Enhancements
Oracle Database 10g extends the enhancements that were introduced in Oracle9i in the area of bulk binding. The SAVE EXCEPTIONS syntax was introduced in Oracle9i to capture exceptions while bulk inserting (deleting or updating) rows. Although this feature enables the data manipulation language (DML) to continue (saving any exceptions in the SQL%BULK_EXCEPTIONS collection), the performance of the operation is greatly affected. In a scenario in which the collection being processed is sparse due to the application logic involved, this would be an unnecessary overhead. In Oracle Database 10g, you can overcome these issues with the new INDICES OF and VALUES OF features.
Debugging PL/SQL with JDeveloper
Starting with version 9.0.3, JDeveloper supports PL/SQL debugging. This is achieved with the Java Debugging Wire Protocol (JDWP). With Oracle9i, release 2 (and later versions), the debugger uses the industry standard, whereas for earlier releases (Oracle8i and Oracle9i, release 1), the DBMS_DEBUG package is used. The debugging UI gives you the ability to single step, step into, and step over PL/SQL objects as well as the ability to set PL/SQL expressions in the Watch and Inspector windows, set conditional breakpoints, and view collection data.
Before your perform this tutorial, you should:
| 1. |
Perform the Installing Oracle Database 10g on Windows tutorial. |
|
| 2. |
Download and unzip the plsql.zip file into your working directory (i.e. c:\wkdir). |
|
This section shows you how to use conditional compilation and gives some usage examples.
|
|
|
|
|
|
| Error Directive | |
|
|
|
Directives are composed of directive control tokens "$"and ordinary PL/SQL text. Conditional compilation uses three directives, selection, inquiry, and error. A special trigger character '$' denotes the conditional compilation directives. The selection directive is essential to the conditional compilation mechanism; the inquiry and error directives support useful extra functionality.
The selection directive evaluates conditional expressions and selects code to be included in the compilation based on the outcome of that evaluation.The code that is not selected is ignored completely. There is no interference with existing programs, which do not use conditional compilation. A conditional selection directive starts with $if and has the general syntax:
$if <B-expr > $then <PL/SQL-code-fragment> $elsif <B-expr > $then <PL/SQL-code-fragment> … $else <PL/SQL-code-fragment> $end
Where <B-Expr> stands for static BOOLEAN expression. A static BOOLEAN expression is any combination of one or more package constants or one or more inquiry directives. An example of a selection directive employing a package constant as the static BOOLEAN expression is:
$if Trace_Pkg.Trace > 2 $then … $end
Where Trace_pkg is the name of the package and Trace is a constant declared as a PLS_INTEGER. Notice that when a constant declared in package Trace_pkg is used in a selection directive in a PL/SQL compilation unit U , then U has a dependency on Trace_pkg just as if Trace_pkg had been referred to in regular PL/SQL code.
The inquiry directive allows access to the compilation environment, so that selection may be based on the current environment. An inquiry directive takes the form $$<PL/SQL-identifier>. For example, the value of a PL/SQL compiler parameter (such as PLSQL_OPTIMIZE_LEVEL) for the unit being compiled. Such an inquiry can be used in conjunction with the conditional selection directive to select the parts of the program to compile. For example, you can use the inquiry directive to query the value of a PL/SQL compiler parameter. An inquiry directive starts with two trigger characters $$ such as:
$if $$debug_level > 3 $then … $end
The identifier debug_level is defined using the plsql_ccflags initialization parameter (new in Oracle Database 10g Release 2) thus:
...plsql_ccflags = 'debug_level:4, ...'
An error directive takes the form
$error <VARCHAR2-expression> $end.
It causes the compiler to report a compilation error including the message provided in the VARCHAR2 expression.
Predefined Compiler Parameters
The Oracle Database 10g Release 2 provides the following PL/SQL compiler parameters that can be used in conditional inquiry directives:
|
PLSQL_CCFLAGS. |
|
PLSQL_DEBUG |
|
PLSQL_OPTIMIZE_LEVEL |
|
PLSQL_CODE_TYPE |
|
PLSQL_WARNINGS |
|
NLS_LENGTH_SEMANTICS |
The values of the PL/SQL compiler parameters at the time of compilation are stored with the compilation unit and can be inspected using the all_plsql_object_settings view family. In addition the predefined inquiry directives $$PLSQL_UNIT, $$PLSQL_LINE are available. For more information and details on the syntax for conditional compilation directives, refer to the PL/SQL User Guide and Reference manual.
Example 1: Tracing and Debugging using Package Constants
Using package constants in conditional compilation directives provides a method to control one or more PL/SQL compilation units with a single mechanism. For example suppose that your application consists of many PL/SQL compilation units. Within the application you have embedded methods that perform debugging or tracing. These methods can be turned on with conditional compilation directives using package constants. Therefore you can change the value of the constants by recompiling the package at anytime. When the package is recompiled all dependent objects are automatically recompiled therefore accepting the new values of the package constant. This can be used for turning tracing and debugging on throughout the entire application. When you are done tracing and debugging, recompile the package specification with new values of the constants that guard the tracing and debugging code. This will invalidate all dependent PL/SQL units so that on next use the tracing and debugging will not be selected for compilation. The use of package constants is an effective mechanism to control all of the dependent PL/SQL units that use the packaged constants within selection directives for conditional processing. The following example demonstrates this use case.
| 1. | Open a terminal window and execute the following commands: cd\wkdir sqlplus hr/hr
|
|
| 2. |
Create the package STATIC_CONSTANTS to declare package constants that can be used in conditional compilation. From your SQL*Plus session, execute the following script: @static_constants The static_constants.sql script contains the following: CREATE OR REPLACE PACKAGE static_constants is
|
|
| 3. | Create two procedures CHECK_DEBUG which checks whether the value of the package constant debug is TRUE and CHECK_TRACE which checks if the value of the package trace is TRUE. From your SQL*Plus session, execute the following script: @debug_trc The debug_trc.sql script contains the following: CREATE OR REPLACE PROCEDURE check_Debug IS
|
|
| 4. |
Set serveroutput on using the new SIZE UNLIMITED syntax available in Oracle Database 10g Release 2. Now execute both the procedures. You will see that it displays that both tracing and debugging are tuned off. Note: You can copy paste each line individually into sqlplus however do not copy and paste the 3 statements together. set serveroutput on size unlimited exec check_debug exec check_trace
|
|
| 5. | Now you will change the values of the package constants. From your SQL*Plus session, execute the following script: @reset_const The reset_const.sql script contains the following: CREATE OR REPLACE PACKAGE static_constants is
|
|
| 6. | Now execute the two procedures again. You will see that both debugging and tracing are on automatically. When a package is recompiled all dependent objects become invalid and get recompiled at the next execution. Therefore the new values of the package constants apply to all the dependent objects immediately. exec check_debug exec check_trace
This can be extended to any number of dependent programs. This method is most useful when a large number of programs need to be controlled with a single mechanism. Other examples of use cases are changing tax by state for the same application or changing software features based on licensing options and so on.
|
|
Example 2 : Using Compiler Warnings and PLSQL_CCFLAGS
You can use the conditional parameter PLSQL_CCFLAGS to provide fine-grained access to specific programs by setting the value of PLSQL_CCFLAGS. You can either use an ALTER SESSION command or an ALTER...COMPILE command to do this. The ALTER COMPILE command only affects the program being compiled. The ALTER SESSION command can be used to affect more than one program by recompiling these programs with CREATE or REPLACE following the ALTER SESSION statement.
In the following example you are creating a procedure called GET_RECORD that accepts the value of an employee_id and displays the corresponding record. To maintain good programming practice, this procedure calls a CHECK_UNIQUE procedure that verifies the uniqueness of each employee record. In the rare case that a duplicate exists, an user friendly message is received and an alert with the employee id is sent to the DBA using a procedure called SEND_MESSAGE_TO_DBA. However since the data in the testing database does not contain any duplicates you can simulate duplicates using PLSL_CCFLAGS with a static BOOLEAN expression in an inquiry directive.
1. |
Alter the session to show all compiler warnings. Execute the following from your SQL*Plus session: ALTER SESSION SET plsql_warnings = 'enable:all';
Note that all conditional compilation directive have been removed.
|
2. |
Create the SEND_MESSAGE_TO_DBA and CHECK_UNIQUE procedures. The CHECK_UNIQUE procedure contains two different pieces of code, one that is used in development for testing and the other that is deployed in the final production environment. The development code uses conditional compilation directives and the production code uses a traditional IF-THEN-ELSE logic to check for duplicate records. From your SQL*Plus session, execute the following scripts: @dba_email @chk_unq show errors The dba_email.sql script contains the following: CREATE OR REPLACE PROCEDURE send_message_to_DBA(emp_id number) The chk_unq.sql script contains the following: Create or replace Procedure check_unique(emp_id NUMBER) is
|
| 3. |
Now alter the session to set the value of $$force to FALSE. Execute the following command from SQL*Plus. ALTER SESSION SET PLSQL_CCFLAGS='force:FALSE';
|
| 4. |
Now execute chk_unq.sql again.
The warning does not appear as the variable $$force now has a value of FALSE;
|
| 5. | Create the GET_RECORD procedure. This procedure accepts an employee id and returns the employee record. If a duplicate exists as checked by the CHECK_UNIQUE procedure a friendly message is displayed . Execute the following script in your SQL*Plus session: @get_record The get_record.sql script contains the following: Create or replace procedure get_record(emp_id IN NUMBER)
|
| 6. | Now execute the GET_RECORD procedure passing in 100 as the argument. exec GET_RECORD(100)
Since there are no duplicates the procedure executes successfully .
|
| 7. |
Now to test whether the message is displayed when there are duplicates, alter the session and set the value of $$force to TRUE and execute GET_RECORD again. Execute the following commands from your SQL*Plus session. ALTER SESSION SET PLSQL_CCFLAGS='force:TRUE';
exec GET_RECORD(100)
The procedure is not affected as it has not been recompiled.
|
| 8. |
Recompile the CHECK_UNIQUE procedure using ALTER...COMPILE statement and provide the new value of the PLSQL_CCFLAGS parameter. Use the REUSE SETTINGS option. Then execute GET_RECORD again for a value of 100. Execute the following commands from your SQL*Plus session. ALTER PROCEDURE CHECK_UNIQUE compile plsql_ccflags = 'force:TRUE' REUSE SETTINGS; exec GET_RECORD(100)
The PLSQL_CCFLAGS value now is in effect and therefore the program issues the message. This method can be used to affect specific programs within a session as is post production debugging. However each program that needs the new setting has to be recompiled either using CREATE OR REPLACE or ALTER...COMPILE statements.
|
Example 3: Using DBMS_PREPROCESSOR Procedures to Print or Retrieve Source Text
DBMS_PREPROCESSOR subprograms print or retrieve the post-processed source text of a PL/SQL unit after processing the conditional compilation directives. This post-processed text is the actual source used to compile a valid PL/SQL unit. Perform the following steps:
1. |
Execute the following command to see the actual source code used to compile a valid PL/SQL program: EXEC DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE('PROCEDURE', 'HR', 'CHECK_UNIQUE');
Note that all conditional compilation directive have been removed.
|
Example 4: Using Conditional Compilation to Branch Code to Determine Version with Best Performance
Oracle Database 10g introduces the BINARY_DOUBLE datatype that can be used for arithmetic intensive operations. In this example, you will test how the BINARY_DOUBLE datatype compares with the NUMBER datatype. You will create the same code in two different versions, one using NUMBER and the other using BINARY_DOUBLE. You can then include both versions within the same procedure to test using PLSQL_CCFLAGS. Perform the following steps:
1. |
Issue the ALTER SESSION to set flags to choose between the two versions. First you are choosing to use the NUMBER datatype. From your SQL*Plus session, execute the following command: ALTER SESSION SET PLSQL_CCFLAGS = ' numversion:TRUE';
|
2. |
You want to see which code executes faster. You will create the CALC_CIRCLE procedure, which calculates the area and circumference of a circle for a given radius. From your SQL*Plus session, execute the following script: @num_bdbl The num_bdbl.sql script contains the following: CREATE or REPLACE PROCEDURE Calc_circle( RADIUS $IF $$NUMVERSION $THEN NUMBER
|
3. |
Execute the CALC_CIRCLE procedure passing in the number 1234567890 as the radius set timing on
exec calc_circle(1234567890)
|
4. |
Change the value of $$numversion to FALSE using ALTER COMPILE command to compile CALC_CIRCLE and execute CALC_CIRCLE again with the same argument. ALTER PROCEDURE calc_circle COMPILE plsql_ccflags = 'numversion :false' REUSE SETTINGS;
exec calc_circle(1234567890)
set timing off
You can see the difference in performance. This example illustrates that it is possible to test two different versions of code selectively within the same program using conditional compilation.
|
Example 5: Using Conditional Compilation with Different Versions of the Oracle Database
The following example shows the use of a DBMS_DB_VERSION constant with conditional compilation. Both the Oracle database version and release are checked. This CHECK_VERSIONS procedure uses the COMMIT WRITE IMMEDIATE NOWAIT command which has been introduced in Oracle Database 10g Release 2. If this procedure were to be executed in a prior release of the Oracle Database the command would not be recognized. Therefore the procedure uses DBMS_DB_VERSION package to check for the version of the database. If the version returned is lower the 10.2 then a regular COMMIT is used to commit the transaction. When the procedure is executed in a version of the database that is 10.2 or higher then the COMMIT WRITE IMMEDIATE NOWAIT command is used to complete the transaction.
| 1. | Now you can test the version of the Oracle Database. Execute the following script from your terminal window: @check_version The check_version.sql script contains the following: CREATE OR REPLACE PROCEDURE check_version AS
|
|
| 2. |
Now execute the check_version procedure by executing the following command in your SQL*Plus window. exec check_version quit
The output of check_version shows that regardless of the version the procedure completes successfully and is transparent to the end user. Therefore there is no reason to have different program units in different versions of the database.
|
|
The following example illustrates the use of the INDICES OF and VALUES OF keywords for bulk binding in PL/SQL.
The INDICES OF keyword can be used in a scenario in which a collection of records (which is dense) is validated programmatically and invalid records (those not meeting the specified criterion) are removed from the collection. This results in a sparse collection of valid elements that must then be bulk inserted into a table. By using the INDICES OF keyword, the exceptions for the missing records are not generated.
The VALUES OF keyword can be used in a scenario in which a collection of records (sparse or dense) must be copied to one or more collection variables, based on some condition whereby certain records may or may not be copied, and then inserted into a table. This can be efficiently done by using the "VALUES OF" syntax and using a pointer array whose elements are pointers to the selected records within the original collection. This reduces the need to create multiple copies of data. Exception handling is done per pointer record, which means that if two or more pointer records are pointing to the same "original" data, any exceptions that are reported indicate the iteration number (pointer element number).
This example uses the EMPLOYEES table to build a collection of employees that is deliberately sparse.
| 1. |
You must first create a NEW_EMPLOYEES table from the existing EMPLOYEES table (in the hr schema) and create a unique index on the EMPLOYEE_ID column. This generates an exception when inserting a duplicate record. Execute the following commands from a terminal window: sqlplus hr/hr @setup The setup.sql script contains the following: drop table new_employees; create table new_employees as select * from employees where 1=2; create unique index new_employees_employee_id on new_employees (employee_id);
|
|
| 2. |
You next create the P_BULK_BIND package. This package contains various procedures that create the sparse collection and bulk insert the records into the NEW_EMPLOYEES table using three different approaches. @cr_p Click cr_p.sql to see what is contained in this script.
|
|
| 3. |
To see how bulk binding works in Oracle Database 10g, we first look at how it was done in Oracle9i. The FORALL statement uses the SAVE EXCEPTIONS syntax to suppress exceptions raised due to missing records, which are later printed in the EXCEPTION block. Execute the following commands: set serverout on size 100000 exec p_bulk_bind.Bulk_Insert_Pre_10g (i_make_sparse => true)
-- this code is located in the Bulk_Insert_Pre_10g procedure ... forall j in g_emp_recs.First()..g_emp_recs.Last() save exceptions insert into new_employees values g_emp_recs(j); From the output, you see that each missing record has generated an exception (ORA-22160). As mentioned earlier, even though the remaining records are processed via the SAVE EXCEPTIONS syntax, this results in a performance degradation if the number of deleted records is fairly high.
|
|
| 4. |
The next step is to use the Oracle Database 10g INDICES OF syntax to ignore the missing records and thereby obtain much better performance. Execute the following commands: exec p_bulk_bind.Bulk_Insert_With_Indices_Of (i_cause_exception => false) In this case, the Boolean input value is used to ignore a block of code that is responsible for creating an exception condition (as you will see in the next step).
-- this code is located in the Bulk_Insert_With_Indices_Of -- procedure ... forall j in indices of g_emp_recs save exceptions insert into new_employees values g_emp_recs(j); ... This time there are no exceptions raised because of the missing records.
|
|
| 5. |
The preceding procedure can also be run with the input parameter set to true. This causes an exception while inserting that must be trapped (by SAVE EXCEPTIONS) and later processed. The condition causing the exception is simulated via a procedure call, which nulls the EMAIL columns for the employees with the last name of "Ernst" and "Urman." Because the EMAIL column has a NOT NULL restriction, this causes an exception when the two corresponding records are inserted. The exceptions are later processed from the SQL%BULK_EXCEPTIONS collection. Execute the following commands: exec p_bulk_bind.Bulk_Insert_With_Indices_Of (i_cause_exception => true)
-- this code is located in the exception area -- in the Bulk_Insert_With_Indices_Of procedure ... forall j in indices of g_emp_recs -- between g_emp_recs.First() and g_emp_recs.Last() -- optional save exceptions insert into new_employees values g_emp_recs(j); Note: The error is reported for the iteration number. This is new to the exception handling in Oracle Database 10g.
|
|
| 6. |
Another approach is to use the VALUES OF clause to create a second collection of pointers to the elements of the original array. Execute the following commands: exec p_bulk_bind.Bulk_Insert_With_Values_Of - (i_null_email => true, i_violate_pk => false) The Point_To_Sparse procedure is used to create a collection of pointers based on the same logic as before. That is, it points to the elements with the first occurrence of each letter in the last name. It is called from the Bulk_Insert_With_Values_Of procedure.
-- this code is located in the Bulk_Insert_With_Values_Of -- procedure ... forall j in values of g_values_of_tab save exceptions insert into new_employees values g_emp_recs(j); ... The result is the same as in step 5: An exception is raised for the fifth and nineteenth iterations.
|
|
| 7. |
To see the effect of reporting the iteration number (or pointer element number) as opposed to the index of the erroneous record, you can run the preceding procedure with i_violate_pk => true. To simulate the error condition, the Cause_Exception_For_Values_Of procedure is used to insert a duplicate index entry from the bound collection (G_EMP_RECS) into the pointer collection (G_VALUES_OF_TAB). Because of this, the unique key constraint (created in step 1) is violated and the exception is raised upon bulk insert into the NEW_EMPLOYEES table. Execute the following commands: exec p_bulk_bind.Bulk_Insert_With_Values_Of - (i_null_email => false, i_violate_pk => true)
|
|
In this tutorial, you learned how to:
| How PL/SQL Conditional Compilation can be Performed | ||
| Examine the Relaxation of Overall Limit for DBMS_OUTPUT PL/SQL Package. | ||
| Examine the Bulk Binding Feature | ||