06/06/22 Copyright 2001 Steven Feuerstein PL/SQL Advanced Techniques - page 1 Achieving PL/SQL Excellence Oracle PL/SQL Advanced Techniques Oracle7 thru Oracle8i Steven Feuerstein www.StevenFeuerstein.com www.Quest.com www.OReilly.com and contributions most excellent from Bill Pribyl and Dick Bolz
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
– Expand your knowledge and awareness of important and new features of the PL/SQL language.
Outline– Building with Packages (Oracle7+)– PL/SQL Collections (Oracle7 and Oracle8+)– Cursor Variables (Oracle7 and Oracle8+)– Dynamic SQL: DBMS_SQL and Native Dynamic SQL (8i)– Calling Java from PL/SQL (Oracle8i) and C (Oracle8)– Oracle Advanced Queuing with DBMS_AQ (Oracle8)– Managing Large Objects with DBMS_LOB (Oracle8)– Other Oracle8i New Features
» Autonomous Transactions (Oracle8i)» Invoker Rights Model (Oracle8i)» Row Level Security: DBMS_RLS
Software Used in Training PL/Vision: a library of packages installed on top of PL/SQL.
– PL/Vision Lite - use it, copy, change it for free -- unless you build software to be sold commercially.
– Active PL/SQL Knowledge Base: contains PL/Vision Professional, the fully supported and enhanced version.
Demonstration scripts executed in the training can be found on the RevealNet PL/SQL Pipeline:– http://www.revealnet.com/Pipelines/PLSQL/index.htm– Archives surfboard, Miscellaneous, PL/SQL Seminar Files– See filedesc.doc for a listing of many of the files.
The PL/SQL IDE (Integrated Development Environment).– You no longer have to use SQL*Plus and a crude editor! Choose from
What is a Package? A collection of code elements, from procedures and
functions to TYPE, variable and cursor declarations.– Single-most important structure within PL/SQL, and almost
certainly one of the most under-utilized.– Conceptually very simple, it can take some time to fully grasp the
implications and potential of the package.
The method of choice by Oracle and other software developers for extending the PL/SQL language. – You will find packages in the database, in Oracle Developer/2000, in
Oracle Application Server.
Let’s review some of the benefits of packages.tmr.pkg
Package Initialization The initialization section is a block of code at the end of
the package body that is executed once per session, the first time any package element is referenced.– The PL/SQL runtime engine determines when and if this code
should be run.
Does the package have an
init section?
Program referencespackage elementthe first time in each session.
Configure Session with Init. SectionPACKAGE BODY sessinit IS /* No declared package elements at all! */BEGIN /* Get user preferences for this user. */ SELECT lov_flag, tb_flag, defprinter INTO show_lov, show_toolbar, printer FROM user_config WHERE user_id = USER;
EXCEPTION WHEN NO_DATA_FOUND THEN /* No record for this user. */ show_lov := 'Y'; show_toolbar := 'Y'; printer := 'lpt1';
WHEN OTHERS THEN RAISE_APPLICATION_ERROR (-20000, 'No profile for ' || USER);END sessinit;
Populate Collections The PL/Vision Date package, PLVdate, employs several
PL/SQL tables to convert strings and to perform date arithmetic.– This increases the flexibility of the date conversion process.– The datemgr.pkg file demonstrates the basic technique (and the
reliance on an initialization section) used to achieve this flexibility.
Program Overloading When you overload programs, you give
two or more programs the same name.– You can overload modules in any declaration
section and in packages. myproc
myproc
myproc
Overloading is a critical feature when building comprehensive programmatic interfaces (APIs) or components using packages.– If you want others to use your code, you need
to make that code as smart and as easy to use as possible.
– Overloading transfers the "need to know" from the user to the overloaded program.
How Overloading Works For two or more modules to be overloaded, the compiler must be able to
distinguish between the two calls at compile-time. There are two different "compile times":– 1. When you compile the package or block containing the overloaded code.– 2. When you compile programs that use the overloaded code.
Distinguishing characteristics:– The formal parameters of overloaded modules must differ in number, order or
datatype family (CHAR vs. VARCHAR2 is not different enough).– The programs are of different types: procedure and function.
Undistinguishing characteristics:– Functions differ only in their RETURN datatype.– Arguments differ only in their mode (IN, OUT, IN OUT).– Their formal parameters differ only in datatype and the datatypes are in the same
Overload Wherever Possible Supporting Many Data Combinations
– Apply the same action to different kinds or combinations of data. In this case, the overloading does not provide a single name for different activities, so much as providing different ways of requesting the same activity.
– The DBMS_OUTPUT.PUT_LINE procedure illustrates this technique -- and the PL/Vision p.l substitute does an even better job.
Fitting the Program to the User – To make your code as useful as possible, you may construct different
versions of the “same” program which correspond to different patterns of use.
Overloading by Type, not Value– A less common application of overloading. You use the type of data and not
its value to determine which of the overloaded programs should be executed.
PACKAGE p IS PROCEDURE l (date_in IN DATE, mask_in IN VARCHAR2 := ‘Month DD, YYYY - HH:MI:SS PM'); PROCEDURE l (number_in IN NUMBER); PROCEDURE l (char_in IN VARCHAR2);
PROCEDURE l (char_in IN VARCHAR2, number_in IN NUMBER);
PROCEDURE l (char_in IN VARCHAR2, date_in IN DATE, mask_in IN VARCHAR2 := 'Month DD, YYYY - HH:MI:SS PM');
PROCEDURE l (boolean_in IN BOOLEAN);
PROCEDURE l (char_in IN VARCHAR2, boolean_in IN BOOLEAN);END p;
p.spsp.spb
Many different datatype combinations, allowing the user to pass data to the "display engine" without writing "pre-processing" code.
A single piece of functionality, such as "display data" or "create a file", can be applied or needed under very different circumstances.
If you take these different circumstances into account when you design your package specification, the user of your package can benefit from writing less code.– Your code is a a more natural "fit" under a variety of
requirements. In my experience, few developers are considerate enough of
their users to try to anticipate their needs.– If you want to write software that is admired,
appreciated...and taken completely for granted, think about the way it will be used.
In other words, you have to declare the record to hold the file handle, even though you are simply going to close the file immediately after opening it.
Of course, sometimes you will want to create a file and then perform additional operations, so this is just the way it has to be, right? WRONG!
Procedure and Function Overloaded Why not overload a "create file" program so that you can pick the one that most closely fits your situation? Consider the PLVfile package of PL/Vision.
PACKAGE PLVfileIS /* Procedure */ PROCEDURE fcreate (file_in IN VARCHAR2, line_in IN VARCHAR2 := NULL);
/* Function */ FUNCTION fcreate (file_in IN VARCHAR2, line_in IN VARCHAR2 := NULL) RETURN UTL_FILE.FILE_TYPE;
Defining Dynamic SQL Columns The DBMS_SQL.DEFINE_COLUMN procedure defines the datatype of a column.
– To make it easier to accomplish this task, you only need to pass a value -- any value -- of the correct type. – The three code blocks below are equivalent, from the perspective of DBMS_SQL.DEFINE_COLUMN.
BEGIN DBMS_SQL.DEFINE_COLUMN (cur, 1, 1); DBMS_SQL.DEFINE_COLUMN (cur, 2, 'a', 30);
BEGIN DBMS_SQL.DEFINE_COLUMN (cur, 1, DBMS_UTILITY.GET_TIME); DBMS_SQL.DEFINE_COLUMN (cur, 2, USER, 30);
BEGIN DBMS_SQL.DEFINE_COLUMN (cur, 1, v_empno); DBMS_SQL.DEFINE_COLUMN (cur, 2, v_ename, 30);
The Frustrations of Overloading Watch out! An overloading can compile successfully, but
you might later found out that you cannot actually call any of the overloaded programs.
PACKAGE profitsIS PROCEDURE calc (comp_id_IN IN NUMBER);
PROCEDURE calc (comp_id_IN IN company.comp_id%TYPE);END;
In the above example, I rely on an anchored type (%TYPE) to establish the datatype of the second calc’s parameter.– When I compile profits, PL/SQL does not sense a conflict with
above overloading even though comp_id is a numeric column.
Can I overload two programs which have parameters that differ only by name, like calc_totals shown above?– If not, why not?– If so, how would you do it? (Don't peek at the next page!)
PACKAGE salesIS PROCEDURE calc_total (zone_in IN VARCHAR2);
» Practically speaking. Valid row numbers range: -2,147,483,647 to 2,147,483,647» You will not actually create tables this large. Instead, this broad range allows you to employ the row number as
an intelligent key.– Sparse
» Data does not have to be stored in consecutive rows of information.– Homogeneous
» Data in each row has the same structure.– Available only in PL/SQL
TYPE <table_type> IS TABLE OF <datatype> INDEX BY BINARY_INTEGER;
DECLARE TYPE inmem_emp_t IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER; emp_copy inmem_emp_t;
» Each row contains the same structure of data.– Unbounded, but only with explicit EXTEND requests
» Practically speaking. Valid row numbers range: 1 to 2,147,483,647– Initially dense, but can become sparse if you DELETE inner rows– Available both in PL/SQL and SQL (as a column in a table)– The order of elements is not preserved in the database
[CREATE OR REPLACE] TYPE <table_type> IS TABLE OF <datatype> [NOT NULL];
DECLARE TYPE when_t IS TABLE OF DATE; birthdays when_t;
Defining Collections First, you define the TYPE of the collection.
– For index-by tables, this can only occur in a PL/SQL declaration section. Best option: package specification.
– For nested tables and VARRAYs, you can define the TYPE in the database with a CREATE statement, or in a PL/SQL declaration section.
Then you declare an instance of that type, a collection, from the TYPE.– You can declare multiple collections from that TYPE.
CREATE OR REPLACE PACKAGE tabtypesIS TYPE integer_ibt IS TABLE OF INTEGER INDEX BY BINARY_INTEGER; TYPE integer_nt IS TABLE OF INTEGER; TYPE integer_vat IS VARRAY(10) OF INTEGER; ...END tabtypes;
ALL_COLL_TYPES– The types you have created (or have access to) in the database
ALL_TYPE_ATTRS– Attributes of the data type used in the TYPE definition.– The code used to define the collection TYPE
There is no information in the data dictionary available for index-by tables.
colldd.sql
SELECT A.attr_name || ' - ' || A.attr_type_name Attributes FROM all_coll_types T, all_type_attrs A WHERE T.owner = USER AND T.owner = A.owner AND T.type_name IN ('NAMES_VT', 'TMRS_VT') AND T.elem_type_name = A.type_name;
EXTEND before assigning a value to a row.– Not necessary for index-by tables, but you must do it for VARRAYs and nested tables.
For index-by tables, you must reference existing rows or a NO_DATA_FOUND exception is raised.– Use the EXISTS method to determine if a row existed.– For VARRAYs and nested tables, once extended, the row exists, even if you haven't assigned a
value explicitly.
CREATE TYPE names_t IS TABLE OF VARCHAR2(30);/DECLARE greedy_ceos names_t := names_t ();BEGIN greedy_ceos(1) := 'Hamilton, Jordan';END;/
You can EXTEND one or more rows. – Assign a default value with a second, optional argument.– Pre-extending a large number of rows in advance can
improve performance. Include a handler for NO_DATA_FOUND or use the EXISTS
method to avoid these exceptions.
BEGIN -- Extend by 10 since I know I will need that many. -- Set the value of each new row to the contents of the first row. salaries.EXTEND (10, salaries(salaries.FIRST));
BEGIN IF salaries.EXISTS (v_employee_id) THEN -- We are OK. ELSE DBMS_OUTPUT.PUT_LINE ('Data for employee not available.'); END IF;
Collection Methods Obtain information about the collection
– COUNT returns number of rows currently defined in the table.– EXISTS returns TRUE if the specified row is defined.– FIRST/LAST return lowest/highest numbers of defined rows.– NEXT/PRIOR return the closest defined row after/before the specified row.– LIMIT tells you the max. number of elements allowed in a VARRAY.
Modify the contents of the collection– DELETE deletes one or more rows from the index-by table.– EXTEND adds rows to a nested table or VARRAY.– TRIM removes rows from a VARRAY.
The built-in package plitblm (PL/sql Index-TaBLe Methods) defines these methods.
Using Collections Inside SQL Nested tables and VARRAYs can be defined as columns of a
table and referenced directly within SQL. You can also apply SQL operations to the contents of
nested tables and VARRAYs with these operators:– THE - Maps a single column value in a single row to a virtual
database table– CAST - Maps a collection of one type to a collection of another type– MULTISET - Maps a database table to a collection– TABLE - Maps a collection to a database table
Index-by tables are programmatic constructs only. – You cannot make a direct reference to an index-by table in SQL.– Instead, so do indirectly with a PL/SQL function.
Using the THE Operator Use THE to manipulate (retrieve, INSERT, UPDATE, DELETE) contents of a nested table in a
database table.– Can only use with nested tables, not VARRAYs or index-by tables.– Only accessible from within SQL statements in PL/SQL.
CREATE TYPE action_list_t IS TABLE OF VARCHAR2(100);/CREATE TABLE inflation_beater ( focus_area VARCHAR2(100), activities action_list_t) NESTED TABLE activities STORE AS activities_tab;
SELECT VALUE (act) FROM THE (SELECT activities FROM inflation_beater WHERE focus_area = 'FORTUNE 100') act;
UPDATE THE (SELECT activities FROM inflation_beater WHERE focus_area = 'FORTUNE 100') SET COLUMN_VALUE = 'DISBAND OSHA' WHERE COLUMN_VALUE = 'SIDESTEP OSHA';
Using the TABLE and CAST Operators Use CAST to convert a collection from one type to another, TABLE to convert a TYPE into a database
table.– Cannot use with index-by tables.– Useful when you would like to apply SQL operations against a PL/SQL collection (ie, one not stored in a database table).
DECLARE nyc_devolution cutbacks_for_taxcuts := cutbacks_for_taxcuts ('Stop rat extermination programs', 'Fire building inspectors', 'Close public hospitals');BEGIN DBMS_OUTPUT.PUT_LINE ( 'How to Make the NYC Rich Much, Much Richer:'); FOR rec IN (SELECT COLUMN_VALUE ohmy FROM TABLE (CAST (nyc_devolution AS cutbacks_for_taxcuts))) LOOP DBMS_OUTPUT.PUT_LINE (rec.ohmy); END LOOP;END;
Using the MULTISET Operator MULTISET is the inverse of TABLE, converting a set of data (table, view, query) into a VARRAY or nested table.
– Cannot use with index-by tables.– You can use MULTISET to emulate or transform relational joins into collections, with potential client-server performance impact.
DECLARE CURSOR bird_curs IS SELECT b.genus, b.species, CAST(MULTISET(SELECT bh.country FROM bird_habitats bh WHERE bh.genus = b.genus AND bh.species = b.species) AS country_tab_t) FROM birds b; bird_row bird_curs%ROWTYPE;BEGIN OPEN bird_curs; FETCH bird_curs into bird_row;END; multiset.sql
Retrieves all detail information for the master in one trip.
CREATE OR REPLACE PACKAGE ibtab IS FUNCTION rowval (indx IN PLS_INTEGER) RETURN DATE; PRAGMA RESTRICT_REFERENCES (rowval, WNPS, WNDS);END;
CREATE OR REPLACE PACKAGE BODY ibtab IS TYPE date_tab IS TABLE OF DATE INDEX BY BINARY_INTEGER; hiredates date_tab; FUNCTION rowval (indx IN PLS_INTEGER) RETURN DATE IS BEGIN RETURN hiredates (indx); END;END;
Referencing IB Tables inside SQL You can't directly reference an index-by table's contents
inside SQL. Instead, call functions that retrieve the table's data, but hide
CREATE OR REPLACE PACKAGE bidirIS /* Iterate through rows in the result set */ PROCEDURE setRow (nth IN PLS_INTEGER); FUNCTION getRow RETURN employee_plus%ROWTYPE; PROCEDURE nextRow; PROCEDURE prevRow;END;
Bi-Directional Cursor Emulation Oracle does not yet support the ability to move back and
forth (and at random) through a cursor's result set.– A talked-about feature for Oracle9i -- nope, didn't make it!
Instead, deposit your data in a collection and then provide programs to access that data in the necessary fashion.
This is particularly useful (read: efficient) when you need to perform multiple passes against the data.
CREATE OR REPLACE TRIGGER Rank_Sales_Strg AFTER INSERT OR UPDATE OR DELETE ON rank_salesBEGIN rank.rank_depts;END;
Statement Level Trigger
All details of theranking are hidden
in the package body.
CREATE OR REPLACE TRIGGER Rank_Sales_Rtrg AFTER insert OR update OF sales_amt ON rank_sales FOR EACH ROW WHEN (OLD.sales_amt != NEW.sales_amt)BEGIN rank.add_dept (:new.dept_id);END;
The Ranking Package, Continued PROCEDURE rank_depts IS v_deptid PLS_INTEGER := dept_tab.FIRST; BEGIN
IF NOT in_process THEN in_process := TRUE; LOOP EXIT WHEN v_deptid IS NULL; perform_ranking (v_deptid); v_deptid := dept_tab.NEXT (v_deptid); END LOOP; END IF;
in_process := FALSE; dept_tab.DELETE; END rank_dept;
Which Collection Type Should I Use? Index-by tables
– Need to use in both Oracle7 and Oracle8 applications– Want to take advantage of sparse nature for "intelligent keys".
Nested tables– You want to store large amounts of persistent data in a column.– You want to use inside SQL.
VARRAYs– You want to preserve the order in which elements are stored.– Set of data is relatively small (avoid row chaining).– You want to use inside SQL.– You don't want to have to worry about sparseness.
A cursor variable points to an underlying cursor object in the database.– The cursor object in turns points to (and keeps its place in) a result set.
The cursor variable can be passed between programs (even between, say, a Java servlet and a PL/SQL stored procedure).– Static SQL only -- until Oracle8i.
Benefits of Cursor Variables Share cursor management between programs, even across
the client-server divide.– You don't have to pass the result sets of a cursor in order to allow
the client-side program to have direct access to the data in the result set.
– Oracle Developer 2.1 utilizes cursor variables when you choose to construct a "base table block" around stored procedures instead of a database table.
Share the same code across multiple, different queries. – Since the cursor name is no longer hard-coded, you can use a
single block of code (say, a reporting program) against different queries.
– We will try out this technique at the end of the section.
Strong vs. Weak Cursor Types A strong (or constrained) cursor type has a defined
return data specification.– Can only reference cursor objects which return the same data
specification, which can be any single SQL datatype or any previously defined record structure.
– Datatype mismatches are identified at compile time.TYPE cur_typ_name IS REF CURSOR [ RETURN return_type ];
TYPE cur_typ_name IS REF CURSOR RETURN emp%ROWTYPE; /* Strong */
TYPE cur_typ_name IS REF CURSOR; /* Weak */
The weak (or unconstrained) cursor type does not have a RETURN clause.– It can reference any cursor object, be opened FOR any query.– Datatype mismatches can only be identified at runtime.
When you open a cursor variable (whether of the weak or strong variety), you must provide the SQL query that identifies the result set.– If the variable has not yet been assigned to cursor object, the
OPEN FOR statement implicitly creates an object for the variable.– If the variable is already pointing to a cursor object, the OPEN
FOR reuses the existing object and attaches the new query to that cursor object.
Remember, the cursor object is nothing more than a memory location. – It is maintained independently of the query itself.
STRONG cursor data specifications must match or be compatible with the structure of the SELECT statement.– You can establish the return type based on a database table, a
cursor or a programmer-defined record.
DECLARE
TYPE emp_curtype IS REF CURSOR RETURN emp%ROWTYPE;
emp_curvar emp_curtype;
BEGIN OPEN emp_curvar FOR SELECT * from emp; ...END;
A weak cursor TYPE doesn't define the RETURN structure; you can associate any SELECT statement with a weak cursor variable.
FUNCTION open_emp_or_dept (get_type_in IN VARCHAR2) RETURN pkg.cv_typeIS retval pkg.cv_type;BEGIN IF get_type_in = ‘EMP’ THEN OPEN retval FOR SELECT * FROM emp; ELSIF get_type_in = ‘DEPT’ THEN OPEN retval FOR SELECT * FROM dept; END IF; RETURN retval;END;
PACKAGE pkg IS TYPE cv_type IS REF CURSOR;END;
Either query will "do".Verification will takeplace at the FETCH.
Fetching with cursor variables follows the same rules as those with static cursors.
The INTO structure must match in number and datatype to:– FOR STRONG cursor types, it match the cursor type data
specification.– FOR WEAK cursor types, it match the OPEN FOR statement
structure.
Compatibility checks are performed prior to fetching row.– The ROWTYPE_MISMATCH exception is raised on failure.– Fetching can continue with a different INTO clause.
FETCH cursor_var_name INTO record_name;FETCH cursor_var_name INTO var_name, var_name, ...;
Consolidating Different Cursors The open function simply opens FOR a different SELECT based on the
criteria passed to it.
CREATE OR REPLACE PACKAGE BODY allcursIS FUNCTION open (type_in IN INTEGER) RETURN cv_t IS retval cv_t; BEGIN IF type_in = bydept THEN OPEN retval FOR SELECT empno FROM emp ORDER BY deptno; ELSIF type_in = bysal THEN OPEN retval FOR SELECT empno FROM emp ORDER BY SAL; END IF; RETURN retval; END;END;/
Dynamic SQL and PL/SQL Execution "Dynamic SQL" mean that you construct the SQL statement
or PL/SQL block at runtime and then execute it. – Available in PL/SQL since Release 2.1 and DBMS_SQL.– Also supported with "native dynamic SQL" in Oracle8i.
What can you do with Dynamic SQL? Build ad-hoc query and update applications.
– Very common requirement on the Web.
Execute DDL inside PL/SQL programs.– Construct powerful DBA utilities; you no longer have to write SQL
to generate SQL to get your job done.
Execute dynamically-constructed PL/SQL programs.– One example: implement indirect referencing in PL/SQL.
Four Methods of Dynamic SQL Method 1: non-queries without host variables, executed a single time. Method 2: non-queries with a fixed number of host variables, execute
one or more times. Method 3: queries with a fixed number of items in the SELECT list and a
fixed number of host variables. Method 4: queries with a variable number of items in the SELECT list
and/or non-queries with a variable number of host variables.
These methods are in increasing order of complexity. If you can recognize the types, you can more quickly figure out how to code your solution. – Different methods require the use of different programs in DBMS_SQL. – NDS does not support method 4.
Native Dynamic SQL Prior to Oracle8i, you would use the DBMS_SQL built-in
package to execute dynamic SQL. – But this package is very complex, difficult to use, and relatively slow
(performance did improve significantly as of Oracle8).
The new "native dynamic SQL" or NDS of Oracle8i offers two native statements in the PL/SQL language to implement most of your dynamic SQL requirements:– EXECUTE IMMEDIATE <sql string>, used for DDL, DML and single
row fetches.– OPEN FOR <sql string>, used for multi-row queries.
COUNT(*) For Any Table Here's a handy and simple utility based on NDS:
IF tabCount ('citizens', 'insured = ''NO''') > 40,000,000THEN DBMS_OUTPUT.PUT_LINE ( 'Not the best health care system in the world... and not much of a democracy either!');END IF;
tabcount81.sfcompare with:
tabcount.sf
CREATE OR REPLACE FUNCTION tabCount ( tab IN VARCHAR2, whr IN VARCHAR2 := NULL, sch IN VARCHAR2 := NULL) RETURN INTEGERIS retval INTEGER;BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || NVL (sch, USER) || '.' || tab || ' WHERE ' || NVL (whr, '1=1') INTO retval; RETURN retval;END;
Other Execute Immediate Examples Perform an update...CREATE OR REPLACE PROCEDURE updnumval ( tab_in IN VARCHAR2, col_in IN VARCHAR2, start_in IN DATE, end_in IN DATE, val_in IN NUMBER) ISBEGIN EXECUTE IMMEDIATE 'UPDATE ' || tab_in || ' SET ' || col_in || ' = :val WHERE hiredate BETWEEN :lodate AND :hidate' USING val_in, start_in, end_in;END;
Pass in bind variables with USING clause.
Execute a stored procedure...PROCEDURE runprog (pkg_in IN VARCHAR2, name_in IN VARCHAR2) IS v_str VARCHAR2 (100);BEGIN v_str := 'BEGIN ' || pkg_in || '.' || name_in || '; END;'; EXECUTE IMMEDIATE v_str;EXCEPTION WHEN OTHERS THEN pl ('Compile Error "' || SQLERRM || '" on: ' || v_str); END;
PROCEDURE add_profit_source ( hosp_name IN VARCHAR2, pers IN Person, cond IN preexisting_conditions)ISBEGIN EXECUTE IMMEDIATE 'INSERT INTO ' || tabname (hosp_name) || ' VALUES (:revenue_generator, :revenue_inhibitors)' USING pers, cond;END;
Using Objects and Collections in NDS One of the key advantages to NDS over DBMS_SQL is that it
works with Oracle8 datatypes, including objects and collections.– No special syntax needed...– In the following example, the USING clause allows me to pass an
object and nested table to an INSERT statement with a variable table name.
Multiple Row Queries and NDS Oracle extends the cursor variable feature of Oracle7 to support
multi-row dynamic queries.– Here is a simple utility the displays the values of any date, number or
string column in any table.
CREATE OR REPLACE PROCEDURE showcol ( tab IN VARCHAR2, col IN VARCHAR2, whr IN VARCHAR2 := NULL)IS TYPE cv_type IS REF CURSOR; cv cv_type; val VARCHAR2(32767); BEGIN OPEN cv FOR 'SELECT ' || col || ' FROM ' || tab || ' WHERE ' || NVL (whr, '1 = 1'); LOOP FETCH cv INTO val; EXIT WHEN cv%NOTFOUND; DBMS_OUTPUT.PUT_LINE (val); END LOOP; CLOSE cv;END;
Updates with Bind Variables Update salaries for date range using binding.
CREATE OR REPLACE PROCEDURE updnumval ( col_in IN VARCHAR2, start_in IN DATE, end_in IN DATE, val_in IN NUMBER)IS cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR; fdbk PLS_INTEGER;BEGIN DBMS_SQL.PARSE (cur, 'UPDATE emp SET ' || col_in || ' = ' || val_in || ' WHERE hiredate BETWEEN :lodate AND :hidate', DBMS_SQL.NATIVE);
FOR each-column-in-table LOOP DBMS_SQL.DEFINE_COLUMN (cur, nth_col, datatype); END LOOP; LOOP fetch-a-row; FOR each-column-in-table LOOP DBMS_SQL.COLUMN_VALUE (cur, nth_col, val); END LOOP; END LOOP;END;
Makes it easy to execute and fetch a single row from a query. – Very similar to the implicit SELECT cursor in native PL/SQL, which returns a
single row, raises NO_DATA_FOUND or raises the TOO_MANY_ROWS exception.– If exact_match is TRUE, then EXECUTE_AND_FETCH will raise the
TOO_MANY_ROWS exception if more than one row is fetched by the SELECT.– Even if the exception is raised, the first row will still be fetched and available.
FUNCTION execute_and_fetch (cursor_in IN INTEGER, exact_match IN BOOLEAN DEFAULT FALSE) RETURN INTEGER;
Dynamic Formula Execution Suppose I am building a user interface that allows a user to
select a formula for execution, and enter the arguments.– Using static PL/SQL, I would have to modify my screen every time a
new formula was added. – With DBMS_SQL, a single function will do the trick.
FUNCTION dyncalc ( oper_in IN VARCHAR2, nargs_in IN INTEGER := 0, arg1_in IN VARCHAR2 := NULL, arg2_in IN VARCHAR2 := NULL, arg3_in IN VARCHAR2 := NULL, arg4_in IN VARCHAR2 := NULL, arg5_in IN VARCHAR2 := NULL, arg6_in IN VARCHAR2 := NULL, arg7_in IN VARCHAR2 := NULL, arg8_in IN VARCHAR2 := NULL, arg9_in IN VARCHAR2 := NULL, arg10_in IN VARCHAR2 := NULL ) RETURN VARCHAR2;
Recommendations for Dynamic SQL Bind (vs. concatenate) whenever possible.
– Increased chance of reusing parsed SQL, and easier code to write.– But remember: you cannot pass schema elements (table names,
column names, etc.) through the USING clause. Encapsulate statements to improve error handling.
– With NDS, only possible for statements that do not need USING and INTO clauses, though you could write variations for those as well.
– Encapsulate DBMS_SQL.PARSE so that you include the trace in that program.
Use the Oracle8i invoker rights model whenever you want to share your dynamic SQL programs among multiple schemas.– Otherwise that SQL will be executed under the authority
of the owner of the code, not the invoker of the code. effdsql.sqlopenprse.pkgwhichsch.sql
NDS or DBMS_SQL: Which is Best? Dynamic SQL and PL/SQL is very useful, but DBMS_SQL is hard to use. Both
implementations will still come in handy...– If, of course, you have upgraded to Oracle8i!
Major Advantages of NDS:– Ease of use– Performance– Works with all SQL
datatypes (including user-defined object and collection types)
– Fetch into records
When You'd Use DBMS_SQL:– Method 4 Dynamic SQL– DESCRIBE columns of cursor– SQL statements larger than 32K– RETURNING into an array– Reuse of parsed SQL statements– Bulk dynamic SQL– Available from client-side PL/SQL
Key Features of Oracle AQ Leverage full power of SQL
– Messages are stored in database tables Database high availability, scalability and reliability all carry
over to queues– Strong history and retention– Backup and recovery– Comprehensive journaliing
Rich message content increases usefulness of queueing– Use object types to define highly structured payloads
New to Oracle8i, AQ now offers a publish/subscribe style of messaging between applications. – Rule-based subscribers, message propagation, the listen feature and
– Multiple queues– Resetting order and priority of queued items– Queue management using only SQL & PL/SQL– Multiple message recipients– Propagation of queue to remote servers
More Interesting Enqueue ExampleDECLARE ... Same setup as previous page ...BEGIN my_msg := message_type ( 'First Enqueue', 'May there be many more...');
LOB columns [usually] contain pointers, not the LOBs themselves
Updating the LOB changes the pointer
This complicates use of LOBs– LOB locators cannot span transactions– Programs must lock records containing LOBs before updating– Programs performing DML must accommodate dynamic LOB locator
Example: Piecewise CLOB programmatic updateDECLARE CURSOR hcur IS SELECT htmlloc FROM web_pages WHERE url = 'http://www.oodb.com' FOR UPDATE; the_loc CLOB; str_offset INTEGER;BEGIN OPEN hcur; FETCH hcur INTO the_loc; CLOSE hcur;
str_offset := DBMS_LOB.INSTR(lob_loc => the_loc, pattern => 'oodb'); IF str_offset != 0 THEN DBMS_LOB.WRITE(lob_loc => the_loc, amount => 4, offset => str_offset, buffer => 'cool'); END IF;END;
Some Important Things to Remember Java is a case sensitive language...
– string is definitely not the same as String.
Everything is a class (or an object instantiated from a class)...– Before you can call a (non-static) class method, you have to
instantiate an object from that class.– Well, everything exception the primitive datatypes.
You don't have to know how to do everything with Java to get lots of value out of it...– Don't get overwhelmed by all the classes and all the strange quirks.
toString method automatically used by System.out.println
main method is used to test the class.
Entry points must be public static in most cases
Classes may call other classes
Avoid GUI calls
class Corporation extends Person { long layoffs; long CEOCompensation;
public Corporation ( String Pname, long Playoffs, long PceoComp) { name = Pname; layoffs = Playoffs; CEOCompensation = PceoComp; } public String toString () { return name + " is a transnational entity with " + layoffs + " laid-off employees, paying its" + " Chief Executive Officer " + CEOCompensation; } public static void main (String[] args) { // A very scary company Corporation TheGlobalMonster = new Corporation ( "Northrup-Ford-Mattel-Yahoo-ATT", 5000000, 50000000);
CREATE OR REPLACE FUNCTION hello_emp (empno_in IN NUMBER)RETURN VARCHAR2AS LANGUAGE JAVA NAME 'datacraft.bill.Hello.Emp(int) return java.lang.String';/
CREATE [ OR REPLACE ] { PROCEDURE | FUNCTION } <name> [ RETURN <sqltype> ][ ( <args> ) ][ AUTHID { DEFINER | CURRENT_USER } ]AS LANGUAGE JAVANAME '<method fullname> (<Java type fullname>, ...)[ return <Java type fullname> ]';
Publish in PL/SQL Package Spec Designate Java module in PL/SQL package spec...
CREATE OR REPLACE PACKAGE hello_pkgAS FUNCTION hi_emp (empno_in IN NUMBER) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'datacraft.util.Hello.Emp(int) return java.lang.String';END;/
Publish as module in package body ...or in package body
CREATE OR REPLACE PACKAGE hello_pkg2AS FUNCTION hi_emp (empno_in IN NUMBER) RETURN VARCHAR2;END;/
CREATE OR REPLACE PACKAGE BODY hello_pkg2AS FUNCTION hi_emp (empno_in IN NUMBER) RETURN VARCHAR2 IS LANGUAGE JAVA NAME 'datacraft.util.Hello.Emp(int) return java.lang.String';END;/
spec:CREATE OR REPLACE TYPE foo_t AS OBJECT ( bar VARCHAR2(30), MEMBER FUNCTION hello_emp RETURN VARCHAR2 IS LANGUAGE JAVA NAME 'datacraft.util.Hello.Emp(int) return java.lang.String');/
CREATE OR REPLACE TYPE foo_t AS OBJECT ( bar VARCHAR2(30), MEMBER FUNCTION hello_emp RETURN VARCHAR2);/
CREATE OR REPLACE TYPE BODY foo_t AS MEMBER FUNCTION hello_emp RETURN VARCHAR2 IS LANGUAGE JAVA NAME 'datacraft.util.Hello.Emp(int) return java.lang.String';END;/
Passing object using JPub-generated Java After generating and uploading classes with JPub,
they become available to use in mapping Example of passing an Account_t object:
CREATE OR REPLACE PROCEDURE account_save (new_acct IN Account_t)IS LANGUAGE JAVA NAME 'datacraft.bill.AccountRuntime.save (datacraft.bill.Account_t)';/
My Own Java Class for File Manipulation Accept the name of a file and return its length.
import java.io.File;public class JFile2 { public static long length (String fileName) { File myFile = new File (fileName); return myFile.length(); }}
Take each of these steps:– Import the File class to resolve reference.– Instantiate a File object for the specified name.– Call the method of choice against that object and return
Build a Package over Java Method Let's put it in a package; we will certainly want to
add more functionality over time.– I translate the Java long to a PL/SQL NUMBER.
CREATE OR REPLACE PACKAGE xfileIS FUNCTION length (file IN VARCHAR2) RETURN NUMBER;END;/CREATE OR REPLACE PACKAGE BODY xfileIS FUNCTION length (file IN VARCHAR2) RETURN NUMBER AS LANGUAGE JAVA NAME 'JFile.length (java.lang.String) return long';END;/ xfile2.pkg
Translate Boolean to Number Accept the name of a file and return its length.
import java.io.File;
public class JFile3 { public static int canRead (String fileName) { File myFile = new File (fileName); boolean retval = myFile.canRead(); if (retval) return 1; else return 0; }}
Translate TRUE to 1 and FALSE to 0.– And don't forget: this is a boolean primitive, not a Boolean class.
Wrapper for Pseudo-Boolean function Simple translation back to PL/SQL Boolean.
– Avoid the hard-codings with named constants...CREATE OR REPLACE PACKAGE xfile IS FUNCTION canRead (file IN VARCHAR2) RETURN BOOLEAN;END;/CREATE OR REPLACE PACKAGE BODY xfileIS FUNCTION IcanRead (file IN VARCHAR2) RETURN NUMBER AS LANGUAGE JAVA NAME 'JFile3.canRead (java.lang.String) return int';
FUNCTION canRead (file IN VARCHAR2) RETURN BOOLEAN AS BEGIN RETURN IcanRead (file) = 1; END;END;/
Passing Objects to Java with STRUCT You can pass Oracle object information to Java without relying on JPub by using the
STRUCT class.
public class UnionBuster {
public static void wageStrategy (STRUCT e) throws java.sql.SQLException { // Get the attributes of the labor_source object. Object[] attribs = e.getAttributes();
Viewing Output from Java Methods Java provides a "print line" method similar to
DBMS_OUTPUT.PUT_LINE: System.out.println.– Call it within methods and output will display in your Java
environment.
When called within a PL/SQL wrapper, you can redirect the output to the DBMS_OUTPUT buffer. – Here is a good nucleus for a login.sql file:
public class HelloAll { public static void lotsaText ( int count) { for (int i = 0; i < count; i++) { System.out.println ( "Hello Hello Hello Hello Hello All!"); }}}
SET SERVEROUTPUT ON SIZE 1000000CALL DBMS_JAVA.SET_OUTPUT (1000000);
Error Handling with Java-PL/SQL Java offers a very similar, but more robust error handling
mechanism than PL/SQL.– Exceptions are objects instantiated from the Exception class or a
subclass of it, such as java.sql.SQLException.– Instead of raising and handling, you "throw" and "catch".– Use two methods, getErrorCode() and getMessage() to obtain
information about the error thrown.
Any error not caught by the JVM (Java virtual machine) will be thrown back to the PL/SQL block or SQL statement.– And also spew out the entire Java error stack! (at least through
Trapping and Identifying Errors Currently, the entire Java stack is displayed on your screen, and you have to
do some digging to extract the Oracle error information.
SQL> BEGIN 2 dropany ('TABLE', 'blip'); 3 EXCEPTION 4 WHEN OTHERS 5 THEN 6 DBMS_OUTPUT.PUT_LINE (SQLCODE); 7 DBMS_OUTPUT.PUT_LINE (SQLERRM); 8 END; 9 /java.sql.SQLException: ORA-00942: table or view does not exist at oracle.jdbc.kprb.KprbDBAccess.check_error(KprbDBAccess.java) at oracle.jdbc.kprb.KprbDBAccess.parseExecuteFetch(KprbDBAccess.java) at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java) at oracle.jdbc.driver.OracleStatement.doExecuteWithBatch(OracleStatement.java) at oracle.jdbc.driver.OracleStatement.doExecute(OracleStatement.java) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java) at oracle.jdbc.driver.OracleStatement.executeUpdate(OracleStatement.java) at DropAny.object(DropAny.java:14)-29532ORA-29532: Java call terminated by uncaught Java exception: java.sql.SQLException: ORA-00942: table or view does not exist
CREATE OR REPLACE PACKAGE disk_utilAS FUNCTION get_disk_free_space (root_path IN VARCHAR2, sectors_per_cluster OUT PLS_INTEGER, bytes_per_sector OUT PLS_INTEGER, number_of_free_clusters OUT PLS_INTEGER, total_number_of_clusters OUT PLS_INTEGER) RETURN PLS_INTEGER;
CREATE OR REPLACE PACKAGE BODY disk_util AS FUNCTION get_disk_free_space (root_path IN VARCHAR2, sectors_per_cluster OUT PLS_INTEGER, bytes_per_sector OUT PLS_INTEGER, number_of_free_clusters OUT pls_integer, total_number_of_clusters OUT PLS_INTEGER) RETURN PLS_INTEGER IS EXTERNAL LIBRARY nt_kernel NAME "GetDiskFreeSpaceA" LANGUAGE C CALLING STANDARD PASCAL PARAMETERS (root_path STRING, sectors_per_cluster BY REFERENCE LONG, bytes_per_sector BY REFERENCE LONG, number_of_free_clusters BY REFERENCE LONG, total_number_of_clusters BY REFERENCE LONG, RETURN LONG);END disk_util;
Autonomous Transactions Prior to Oracle8i, a COMMIT or ROLLBACK in any program
in your session committed or rolled back all changes in your session.– There was only one transaction allowed per connection.
With Oracle8i, you can now define a PL/SQL block to execute as an "autonomous transaction".– Any changes made within that block can be saved or reversed
without affecting the outer or main transaction.
CREATE OR REPLACE PROCEDURE loginfo ( code IN PLS_INTEGER, msg IN VARCHAR2)AS PRAGMA AUTONOMOUS_TRANSACTION;
Logging with ATs Don't forget that ROLLBACK in the exception section!
logger.splog81.pkglog81*.tst
CREATE OR REPLACE PACKAGE BODY log IS PROCEDURE putline ( code_in IN INTEGER, text_in IN VARCHAR2 ) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO logtab VALUES (code_in, text_in, SYSDATE, USER, SYSDATE, USER, rec.machine, rec.program );
COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; END;END;
The Invoker Rights Model Prior to Oracle8i, whenever you executed a stored program,
it ran under the privileges of the account in which the program was defined.– This is called the …
With Oracle8i, you can now decide at compilation time whether your program or package will execute in the definer's schema (the default) or the schema of the invoker of the code.– This is called the …
Tips and Gotchas for Invoker Rights Does not apply to code objects, only data
– When your stored code references another stored code element, the definer rights model is always applied to resolve the reference.
– Both static and dynamic SQL is supported. Once a definer rights program is called, all other calls in
stack are resolved according to definer rights.– AUTHID CURRENT_USER is ignored.
Information about the rights model is not available in the data dictionary
What if you want to maintain a single version of your code for both pre-Oracle8i and Oracle8i installations, taking advantage of the invoker rights model whenever possible?– A creative use of SQL*Plus substitution variables comes in very
handy. Note: cannot use with wrapped code. invdefinv.sqloneversion.sql
– A massive, popular uprising in the United States has forced the establishment of a national healthcare system. No more for-profit hospitals pulling billions of dollars out of the system; no more private insurance companies soaking up 30 cents on the dollar; all children are vaccinated; all pregnant women receive excellent pre-natal care.
We need a top-notch, highly secure database for NHCS. The main tables are patient, doctor, clinic and regulator.
Here are some rules:– Doctors can only see patients who are assigned to their clinic.– Regulators can only see patients who reside in the same state. – Patients can only see information about themselves.
Create the Security Policy Use the DBMS_RLS.ADD_POLICY procedure.
– The following procedure call specifies that the WHERE clause of any query, update or delete against the SCOTT.PATIENT table will be modified by the string returned by the person_predicate function of the SCOTT.nhc_pkg package.
Create Logon Trigger, Setting Context By setting the context in the logon trigger, we guarantee that the context
is set (and the predicate applied) no matter which product is the entry point to Oracle.
CREATE OR REPLACE TRIGGER set_id_on_logon AFTER LOGON ON DATABASEBEGIN nhc_pkg.set_context;EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE ( 'Error ' || SQLCODE || ' setting context for ' || USER');END;
Exception handling in trigger is critical! If you allow an exception to go
Queries Transparently Filtered What you see is determined automatically by who you are.
Context Information for "SWALLACE": Type: DOCTOR ID: 1060 Predicate:home_clinic_id IN (SELECT home_clinic_id FROM doctor WHERE doctor_id = SYS_CONTEXT ('patient_restriction', 'person_id'))
Patients Visible to "SWALLACE":CSILVA - Chris Silva - ILVSILVA - Veva Silva - IL
Context Information for "CSILVA": Type: PATIENT ID: Predicate: schema_name = 'CSILVA'
Patients Visible to "CSILVA":CSILVA - Chris Silva - IL
Test UTL_FILE Access About the hardest part to working with UTL_FILE is simply
getting started. So before you write anything fancy, modify your
initialization file, restart your database, and then run the following test script (it can't get much simpler than this):
DECLARE fid UTL_FILE.FILE_TYPE;BEGIN /* Change the directory name to one to which you at least || THINK you have read/write access. */ fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W'); UTL_FILE.PUT_LINE (fid, 'hello'); UTL_FILE.FCLOSE (fid);END;/
You can use PUT, PUT_LINE or PUTF. – PUTF is like the C printf program, allowing for some formatting.
Call FFLUSH to make sure that everything you have written to the buffer is flushed out to the file.– The file buffers are automatically flushed when you close a file or exit
your session.
DECLARE fid UTL_FILE.FILE_TYPE;BEGIN fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W'); UTL_FILE.PUT_LINE (fid, 'UTL_FILE'); UTL_FILE.PUT (fid, 'is so much fun'); UTL_FILE.PUTF (fid, ' that I never\nwant to %s', '&1'); UTL_FILE.FCLOSE (fid);END;
UTL_FILE relies on a combination of user-defined exceptions and STANDARD exceptions to communicate errors.– NO_DATA_FOUND when you try to read past the end of the file.– UTL_FILE-named exceptions in other cases.
You have to take special care to trap and handle the named exceptions.– They all share a common SQLCODE of 1.
Trap locally by name; record the error, translating the generic user-defined exception into an understandable message.
Re-raise exception if you want it to propagate from that block.
EXCEPTION WHEN UTL_FILE.INVALID_PATH THEN recNgo (PLVfile.c_invalid_path); RAISE; WHEN UTL_FILE.INVALID_MODE THEN recNgo (PLVfile.c_invalid_mode); RAISE; WHEN UTL_FILE.INVALID_FILEHANDLE THEN recNgo (PLVfile.c_invalid_filehandle); RAISE; WHEN UTL_FILE.INVALID_OPERATION THEN recNgo (PLVfile.c_invalid_operation); RAISE; WHEN UTL_FILE.READ_ERROR THEN recNgo (PLVfile.c_read_error); RAISE; WHEN UTL_FILE.WRITE_ERROR THEN recNgo (PLVfile.c_write_error); RAISE; WHEN UTL_FILE.INTERNAL_ERROR THEN recNgo (PLVfile.c_internal_error); RAISE;END;
Overview of DBMS_JOB DBMS_JOB provides an API to the Oracle job queues, which in
turn offers job scheduling capabilities within the Oracle Server. Built by Oracle to support snapshots and replication. Made its debut in PL/SQL Release 2.1, but only “publicly
available” and supported in PL/SQL Release 2.2. You can use DBMS_JOB to:
– Replicate data between different database instances.– Schedule regular maintenance on instances.– Schedule large batch jobs to run on "off hours".– Create a listener program to poll the contents of a pipe and take action.– Spawn background processes to avoid blocking client process.
A job is a call to a stored procedure or an anonymous block– It must end with a semi-colon and can contain “hard-coded” arguments.
When you submit a job, you specify the date on which it should next execute, and then the job’s execution interval (frequency of execution).– In the above example, I run calculate_totals immediately and then on a daily basis thereafter. Notice that the start time
is a DATE expression, while the interval is a string (this is dynamic PL/SQL!) You can also call DBMS_JOB.ISUBMIT and supply the job number, instead of having DBMS_JOB generate one
This block submits three jobs to the job queue, numbered 1,2, and 3. – Job 1 passes a string and number into procedure MY_JOB1, runs it in one hour and executes every day
thereafter.– Job 2 passes a date into procedure MY_JOB2, executes for the first time tomorrow and every 10 minutes
thereafter. – Job 3 is a PL/SQL block which does nothing, executes immediately, and will be removed from the queue
Specifying Job Times & Frequencies Probably the most complicated part of using DBMS_JOB is to
get the string expression of the job interval right.– Since it's a string, you must use 2 single quotes to embed strings.– Use date arithmetic to request intervals smaller than a day.
Other Job Queue Operations Remove a job from the queue.
– Only if the job was submitted from same schema
Export jobs from the queue.– Produces a string that can be used to recreate an existing job in the job queue.– Uses DBMS_JOB.ISUBMIT, retaining current job number.
expjob.sql
BEGIN FOR rec IN (SELECT * FROM USER_JOBS) LOOP DBMS_JOB.REMOVE (rec.job); END LOOP;
Run a job immediately.– Performs an implicit COMMIT in current session.
Setting up the Job Facility Make sure that the correct access is set up for the
DBMS_JOB package.– The default is PUBLIC access. You will have to take special DBA
action if you want to restrict who can run jobs.
You will need to set three parameters in the init.ora (initialization) file for your database instance:– job_queue_processes=N where n is the number of concurrent
background processes permitted. The valid range is 0 through 36.– job_queue_interval=N where N is the interval in seconds to check
the job queue. The valid range is 1 to 3600 (a maximum, therefore, of one hour).
Error Handling with DBMS_JOB What if your stored procedure fails?
– After 16 attempts, the job facility will mark your job as broken.– Do you want it to try 16 times?– In addition, if your failure raises an unhandled exception, it may
cause the background processes to fail and no longer run any jobs at all.
To avoid unexpected and unhandled failures of scheduled jobs:– Always use the RUN built-in to execute your job in a “test” mode.
Then you can go ahead and submit it.– Always include a WHEN OTHERS exception handler in your job
program which traps any and all problems and automatically sets the job status to broken.
The WHEN OTHERS exception handler of the calc_totals procedure traps any kind of failure. – Obtains the job number from a packaged function by passing the name of
the procedure.– Uses a call to BROKEN to set the status of the job to “broken”. – Calls log program of package to record that failure took place.– Now the job facility will not try to run this program again. You can go in
and fix the problem.
PROCEDURE calc_totals ISBEGIN ...EXCEPTION WHEN OTHERS THEN job# := job_pkg.job (‘calc_totals’) DBMS_JOB.BROKEN (job#, TRUE); job_pkg.log (‘calc_totals’, ‘FAIL’);END;
A pipe is a named object that uses the System Global Area to provide a non-transaction based conduit of information.– The pipe sends/receives a message, which can be composed of one
or more separate packets, using a maximum of 4096 bytes. – Names can be up to 128 chars long (do not use names beginning
Creating Public and Private Pipes There are two ways to create pipes: implicitly and
explicitly. – Send messages to non-existent pipe implicitly create it. – Use CREATE_PIPE to create a pipe explicitly.PROCEDURE newpipe IS stat INTEGER;BEGIN stat := DBMS_PIPE.CREATE_PIPE ( pipename => 'bbs', maxpipesize => 20000, private => TRUE);
An explicit pipe can be private (accessible only to sessions with matching userID or SYSDBA privileges).– Specify TRUE for private argument of CREATE_PIPE.
A public pipe is accessible as long as you know its name. – Implicitly-created pipes are always public.
First you pull the message from the pipe and place it in buffer with RECEIVE_MESSAGE.– Specify pipe and number of seconds you will wait before you time out.– Pipe status of 0 means the message was read successfully.
FUNCTION receive_message (pipename IN VARCHAR2, timeout IN INTEGER DEFAULT maxwait)RETURN INTEGER;
PROCEDURE unpack_message (item OUT VARCHAR2);
PROCEDURE unpack_message (item OUT NUMBER);
PROCEDURE unpack_message (item OUT DATE);
Then you call UNPACK_MESSAGE to extract individual packets from the message.– You need to know the datatype of packet or check it using NEXT_ITEM_TYPE.
IF pipe_status = 0 THEN DBMS_PIPE.UNPACK_MESSAGE (prod_total); analyze_production (SYSDATE, prod_total); ELSE RAISE_APPLICATION_ERROR ( -20000, 'Production data unavailable.'); END IF; END LOOP;END;
Since a message can be composed of packets of different datatypes, you have to make sure that you unpack a packet into the right kind of variable. Either:– You know the datatype and therefore can “hard-code” the correct
variable into the call to UNPACK_MESSAGE.– Or you use the built-in NEXT_ITEM_TYPE to tell you in advance the
datatype of the next packet in the message and take appropriate action.
FUNCTION next_item_type RETURN INTEGER;
Value Description or Data type0 No more items in buffer6 NUMBER9 VARCHAR211 ROWID12 DATE23 RAW
Parallelizing Your Code with Pipes Oracle uses DBMS_PIPE to improve RDBMS performance;
you can do the same for your application if:– You have multiple CPUs available.– You have processes which can run in parallel.
Suppose I want to calculate my net profit. In order to do so I must first compute total sales, total office expenses and total compensation.– These programs each take 15 minutes, but are not dependent on
each other. Without pipes, I must execute them sequentially and incur
an elapsed time of 45 minutes before I calculate the profits.– The CEO is decidedly unhappy about the delay.
Code to Kick Off and CalculatePROCEDURE kick_off_sales_calc IS stat INTEGER;BEGIN DBMS_PIPE.PACK_MESSAGE (1995); stat := DBMS_PIPE.SEND_MESSAGE (‘sales’);END;
Send message to start calculations.
PROCEDURE calculate_sales IS stat INTEGER;BEGIN stat := DBMS_PIPE.RECEIVE_MESSAGE (‘sales’); IF stat = 0 THEN lots_of_number_crunching; DBMS_PIPE.PACK_MESSAGE (sales$); stat := DBMS_PIPE.SEND_MESSAGE (‘sales’); ELSE DBMS_PIPE.PACK_MESSAGE (NULL); stat := DBMS_PIPE.SEND_MESSAGE (‘sales’); END IF;END;
Waiting for ConfirmationPROCEDURE wait_for_confirmationIS stat INTEGER;BEGIN stat := DBMS_PIPE.RECEIVE_MESSAGE (‘sales’); DBMS_PIPE.UNPACK_MESSAGE (sales$);
stat := DBMS_PIPE.RECEIVE_MESSAGE (‘offexp’); DBMS_PIPE.UNPACK_MESSAGE (offexp$);
stat := DBMS_PIPE.RECEIVE_MESSAGE (‘comp’); DBMS_PIPE.UNPACK_MESSAGE (comp$);END;
Other DBMS_PIPE Examples Simple trace package that sends it output either to screen or to pipe.
– Demonstrates the use of toggles and switches in packages.
watch.pkgp_and_l.pkg
Implementation of a system-wide, in-memory cache.– A request for data is passed to a "central" pipe. – A listener program grabs the request (including the return pipe
name), obtains the data, and sends it to the pipe.– The requester reads the information from the pipe.
EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE ( DBMS_UTILITY.FORMAT_CALL_STACK);END;
Accessing the Execution Call Stack
"Little known facts" about FORMAT_CALL_STACK:– Contains embedded new-line characters, equivalent to CHR(10).– Most recent program at beginning of "report".– Does not show package elements, only the package name.
----- PL/SQL Call Stack -----object line objecthandle number name
Resolving Names of Stored Code Code names have many components; the way they are
attached also follows a complicated syntax.– Use NAME_RESOLVE to break down an identifier string into its
components easily.
PROCEDURE DBMS_UTILITY.NAME_RESOLVE (name IN VARCHAR2, context IN NUMBER, schema OUT VARCHAR2, part1 OUT VARCHAR2, part2 OUT VARCHAR2, dblink OUT VARCHAR2, part1_type OUT NUMBER, object_number OUT NUMBER);
Possible "Part 1" Types
5 Synonym
7 Procedure
8 Function
9 Package
showcomp.spsnc.pkg
What a chore! All those arguments...but don't see it as a problem, see it as an opportunity...for encapsulation!