Documente Academic
Documente Profesional
Documente Cultură
Objective
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
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.
plsql_ides.txt
Overview
Initialization section Overloading
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.
custrules.pkg insga.pkg
Improve transaction integrity by hiding data structures behind the package interface.
Instead of writing SQL directly in your programs, you call the packaged procedures and functions instead.
te_employee.pks te_employee.pkb
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.
yes
no
Useful for:
Performing complex setting of default or initial values. Setting up package data which does not change for the duration of a session. Confirming that package is properly instantiated.
7/10/2013 Copyright 2001 Steven Feuerstein
An unusual package!
Specification contains only variables. Body contains only initialization section.
PACKAGE 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 show_lov := show_toolbar := printer :=
PACKAGE sessinit IS show_lov CHAR(1); show_toolbar CHAR(1); printer VARCHAR2(60); END sessinit;
WHEN OTHERS THEN RAISE_APPLICATION_ERROR (-20000, 'No profile for ' || USER); END sessinit;
PL/SQL Advanced Techniques - page 9
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.
BEGIN fmts(1) := 'DD-MON-RR'; fmts(2) := 'DD-MON-YYYY'; fmts(3) := 'DD-MON'; fmts(4) := 'MM/DD'; ... fmts(9) := 'MM/DD/YYYY'; fmts(10) := 'MMDDYYYY'; fmts(11) := 'YYYYMMDD'; fmts(12) := 'RRMMDD'; fmt_count := 12; END dt;
datemgr.pkg dates.sql
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
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.
myproc myproc
Without overloading, you would have to deal with something like this:
date_string := TO_CHAR_FROM_DATE (SYSDATE, 'MMDDYY'); number_string := TO_CHAR_FROM_NUMBER (10000);
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 12
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 family.
which one?
too_similar.calc ('123');
PACKAGE only_returns IS FUNCTION func1 (val IN VARCHAR2) RETURN DATE; FUNCTION func1 (val IN VARCHAR2) RETURN VARCHAR2; END only_returns;
which one?
PACKAGE param_modes IS PROCEDURE proc1 (val IN VARCHAR2); PROCEDURE proc1 (val IN OUT VARCHAR2); END param_modes;
which one?
param_modes.proc1 (v_value);
PL/SQL Advanced Techniques - page 14
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.
Many different datatype combinations, allowing the user to pass data to the "display engine" without writing "pre-processing" code.
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');
p.sps p.spb
p.l (print_report_fl);
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.
PL/SQL Advanced Techniques - page 18
Suppose a developer needs to create a file to be used as a "flag" in the operating system. She doesn't care what's in it. It just needs to be present. Here is the code required by UTL_FILE:
DECLARE fid UTL_FILE.FILE_TYPE; BEGIN fid := UTL_FILE.FOPEN ('tmp/flags', 'exists.flg', 'W'); UTL_FILE.PUT_LINE (fid, 'blah'); UTL_FILE.FCLOSE (fid); END;
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!
PL/SQL Advanced Techniques - page 19
Why not overload a "create file" program so that you can pick the one that most closely fits your situation?
Overloading of FCreate
PACKAGE PLVfile IS /* 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; END PLVfile;
7/10/2013 Copyright 2001 Steven Feuerstein
Use as Function
DECLARE fid UTL_FILE.FILE_TYPE; BEGIN fid := PLVfile.fcreate ('temp.ini', v_user); PLVfile.put_line (fid, TO_CHAR (SYSDATE));
Use as Procedure
BEGIN PLVfile.fcreate ('exists.flg'); END;
custrules.pkg
"By-Type" Overloading
In some situations, the user does not need to pass data, but the type of data.
For example, when you use DBMS_SQL to set up a dynamic query, you must call the DEFINE_COLUMN procedure to define the datatype of the Nth column in the cursor.
Nasty hard-coding...
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);
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 23
In PLVgen, the user indicates the type of function to be generated by providing a value. The particular value itself is of no importance. Any number, any date, any string, any Boolean will do.
PACKAGE PLVgen IS PROCEDURE func (name_in IN VARCHAR2, type_in IN VARCHAR2); PROCEDURE func (name_in IN VARCHAR2, type_in IN NUMBER); PROCEDURE func (name_in IN VARCHAR2, type_in IN DATE); SQL> exec plvgen.func ('last_date', SYSDATE) SQL> exec plvgen.func ('total_salary', 1) A date function, please!
Watch out! An overloading can compile successfully, but you might later found out that you cannot actually call any of the overloaded programs.
PACKAGE profits IS 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 calcs parameter.
When I compile profits, PL/SQL does not sense a conflict with above overloading even though comp_id is a numeric column.
PL/SQL Advanced Techniques - page 25
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!)
BEGIN sales.calc_total ('NORTHWEST'); sales.calc_total ('ZONE2'); END;
sales.pkg
Explicit
association between the formal parameter (the "name") with the actual parameter (the "value"). of named notation include:
Code is more "self-documenting". This is especially useful when working with infrequently used built-in programs. You can skip over (not specify values for) any IN parameters that have default values. That way you don't have to know and pass default values.
DBMS_JOB.submit ( job => v_jobno, what => 'DBMS_DDL.ANALYZE_OBJECT ' || '(''TABLE'',''LOAD1'',''TENK''' || ',''ESTIMATE'',null,estimate_percent=>50);', next_date => TRUNC (SYSDATE + 1), interval => 'TRUNC(SYSDATE+1)');
namednot.sql
Advantages
PL/SQL Collections
Maintain any kind of list of related information for use in your programs.
Index-By Tables
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;
Sparse
Data does not have to be stored in consecutive rows of information.
Homogeneous
Data in each row has the same structure.
Index_by Tables
TYPE declaration
PACKAGE BODY family IS TYPE child_list_type IS TABLE OF VARCHAR2 (30) INDEX BY BINARY_INTEGER;
Variable declaration
children
child_list_type;
children 6306 6412 Barbara Anne Gary Richard Adam Russell Lisa Nadezhka
datemgr.pkg
children
6306
6412 6904
Component Selection
kid := children (4);
6810
6904
Error: NO_DATA_FOUND
7/10/2013 Copyright 2001 Steven Feuerstein
Nested Tables
[CREATE OR REPLACE] TYPE <table_type> IS TABLE OF <datatype> [NOT NULL];
DECLARE TYPE when_t IS TABLE OF DATE; birthdays when_t;
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
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 32
Nested Tables
CREATE OR REPLACE TYPE child_table_type IS TABLE OF VARCHAR2 (30); CREATE TABLE db_family (surname VARCHAR2 (30), kids child_table_type) NESTED TABLE kids STORE AS kids_ntab;
ntdemo.sql
Variable Arrays
[CREATE OR REPLACE] TYPE <table_type> IS VARRAY (N) OF <datatype> [NOT NULL];
DECLARE TYPE numbers_t IS VARRAY (10) OF NUMBER; salaries numbers_t;
Bounded
Upper limit established when the TYPE is defined. Maximum value: 2,147,483,647
Dense
Never any gaps between defined rows, can be EXTENDed or TRIMmed.
Variable Arrays
CREATE OR REPLACE TYPE child_va_type IS VARRAY (8) OF VARCHAR2 (30); CREATE TABLE db_family (surname VARCHAR2 (30), kids child_va_type);
db_family
surname kids BOLZ BOND 1 Barbara Anne 2 Gary Richard 3 Lisa Marie 1 Eric Thomas 2 Max Richard
vademo.sql
Defining Collections
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 tabtypes IS 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
Initializing Collections
Collections of Composites
Starting with Oracle 7.3, the homogeneous contents of an index-by table's row can be a record .
Can easily create an index-by table with the same structure as a database table by declaring a record with %ROWTYPE.
Starting with Oracle8, the datatype for any of the collection types can also be an object.
But you cannot have nested composite datatypes.
DECLARE TYPE comp_rectype IS RECORD (comp_id company.company_id%TYPE, total_rev NUMBER); TYPE comp_tabtype IS TABLE OF comp_rectype INDEX BY BINARY_INTEGER;
Here we have a three step process. Again, consider putting TYPEs in database or packages.
Sparse is Nice
The sparse characteristic of index-by tables and nested tables can be put to good use.
In an index-by table, a row exists in the table only when a value is assigned to that row. In this way, it is very similar to a database table.
Especially handy when caching data from relational tables in user memory.
In almost every case, your collections will contain a row's worth of information.
This package moves the entire contents of the emp table into its corresponding collection. Some questions:
Why would I put this collection table in a package? When is the collection loaded with the data? What rows in that collection are utilized?
psemp.pkg psemp.tst
Collection Gotchas
CREATE TYPE names_t IS TABLE OF VARCHAR2(30); / DECLARE greedy_ceos names_t := names_t (); BEGIN greedy_ceos(1) := 'Hamilton, Jordan'; END; / Error -6533! You've got to EXTEND first!
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.
preextend.tst
Include a handler for NO_DATA_FOUND or use the EXISTS method to avoid these exceptions.
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
The built-in package plitblm (PL/sql Index-TaBLe Methods) defines these methods.
PL/SQL Advanced Techniques - page 44
You can delete one or more rows from a collection using DELETE:
BEGIN -- Delete all rows myCollection.DELETE; -- Delete one (the last) row myCollection.DELETE (myCollection.LAST);
DELETE releases memory, but you may also want to call DBMS_SESSION.FREE_UNUSED_USER_MEMORY.
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 45
Use FIRST and NEXT to move from beginning to end. Use LAST and PRIOR to move from end to beginning.
rowind PLS_INTEGER := birthdays.FIRST; -- birthdays.LAST BEGIN LOOP EXIT WHEN rowind IS NULL; DBMS_OUTPUT.PUT_LINE (birthdays(rowind).best_present);
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
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';
7/10/2013 Copyright 2001 Steven Feuerstein
the.sql
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; cast.sql END;
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; Retrieves all detail bird_row bird_curs%ROWTYPE; information for the BEGIN master in one trip. OPEN bird_curs; FETCH bird_curs into bird_row; END; multiset.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 the index-by table structure.
Make accessible in SQL for Oracle8 and below.
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;
7/10/2013 Copyright 2001 Steven Feuerstein
ibtab_in_sql.sql
Emulation of bi-directional cursor operations Avoid mutating table problems in database triggers.
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 PACKAGE bidir IS /* Iterate through rows in the result set */ PROCEDURE setRow (nth IN PLS_INTEGER); FUNCTION getRow RETURN employee_plus%ROWTYPE; PROCEDURE nextRow; PROCEDURE prevRow; END; Notice that the collection itself is hidden.
bidir.pkg bidir.tst
Database triggers can be attached to the SQL statement and/or the individual row operations on a table.
Statement Level
Row Level
UPDATE row N
Row level triggers cannot query from or change the contents of the table to which it is attached; it is "mutating". So what are you supposed to do when a row-level operation needs to "touch" that table? mutating.sql
Note: in Oracle8i, you can use autonomous transactions to relax restrictions associated with queries.
Since you cannot perform the processing desired in the row-level trigger, you need to defer the action until you get to the statement level. If you are going to defer the work, you have to remember what you needed to do.
an index-by table is an ideal repository for this reminder list.
1st row trigger fires Nth row trigger fires
Writes to list Work List (PL/SQL Table)
Writes to list
Statement Trigger
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 56
A table holds the rankings based on the amount of annual sales of salespeople within a department.
As the sales amount is updated in this table, the rankings must also change to show the new standings for that department only.
Department ID Salesperson ID Sales Amount Rank
3 1 2 6
PROCEDURE add_dept (dept_id_in IN INTEGER) IS BEGIN IF NOT in_process THEN dept_tab (dept_id_in) := TRUE; END IF; END add_dept
7/10/2013 Copyright 2001 Steven Feuerstein
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.
Get creative!
Don't always fill and use the index-by table sequentially. If you can somehow translate your application data to an integer, it can be used as a row number, and therefore offers indexed access. Julian date formats and DBMS_UTILITY.GET_HASH_VALUE offer two different methods.
Cursor Variables
PGA
Cursor Variable
Cursor Variable
Result Set
Cursor Object
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.
PL/SQL Advanced Techniques - page 64
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.
company_curvar company_curtype;
Declare cursor variable based on that type. Declare a record from cursor variable. OPEN cursor variable, specifying the query. FETCH from the cursor variable.
Both hard-coded cursors and cursor variables work with static SQL.
The SQL is fixed at compile-time. The difference is that with cursor variables, you get to decide which static query is opened.
Cursors are declared in two steps, just like programmerdefined records and PL/SQL tables.
1. Define a cursor TYPE -- either "weak" or "strong". 2. Define a cursor variable.
DECLARE
Declare a WEAK referenced cursor TYPE.
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.
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.
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.
FUNCTION open_emp_or_dept (get_type_in IN VARCHAR2) RETURN pkg.cv_type IS retval pkg.cv_type; BEGIN IF get_type_in = EMP THEN Either query will "do". OPEN retval FOR SELECT * FROM emp; Verification will take ELSIF get_type_in = DEPT place at the FETCH. THEN OPEN retval FOR SELECT * FROM dept; END IF; RETURN retval; END;
A weak cursor TYPE doesn't define the RETURN structure; you can associate any SELECT statement with a weak cursor variable.
PL/SQL Advanced Techniques - page 72
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.
mismatch.sql
Make it easier for calling programs (especially non-PL/SQL programs) to manipulate result sets.
JDBC recognizes cursor variables.
Define a base table block in Forms Builder (formerly Oracle Forms) on stored procedures rather than a table directly.
hccursor.sql
The following package specification hides the SQL behind a single open function.
It also creates the data structures you will need to call the function.
CREATE OR REPLACE PACKAGE allcurs IS bydept CONSTANT INTEGER := 1; bysal CONSTANT INTEGER := 2; TYPE int_rt IS RECORD (key INTEGER);
The open function simply opens FOR a different SELECT based on the criteria passed to it.
CREATE OR REPLACE PACKAGE BODY allcurs IS 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; /
The following block demonstrates that you can alter which SQL statement to query -- in this case, change the ORDER BY clause -without having to change the code you write.
DECLARE cv allcurs.cv_t; v_empno emp.empno%TYPE; BEGIN cv := allcurs.open (&1); LOOP FETCH cv INTO v_empno; EXIT WHEN cv%NOTFOUND; p.l (v_empno); END LOOP; CLOSE cv; END; /
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 77
Cursor Variables
Let's summarize
Flexibility
Choose which static SQL statement is executed at run-time.
Dynamic SQL
Dynamic
"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.
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.
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.
EXECUTE IMMEDIATE
EXECUTE IMMEDIATE sql-string
Use this statement to execute any dynamic SQL statement (including a PL/SQL block) except for multi-row queries.
The INTO clause allows you to pass values from the select list of a single row query into local variables, including objects, collections and records. The USING clause allows you to specify bind arguments or variables to be passed into the SQL string before execution.
PL/SQL Advanced Techniques - page 83
CREATE OR REPLACE FUNCTION tabCount ( tab IN VARCHAR2, whr IN VARCHAR2 := NULL, sch IN VARCHAR2 := NULL) RETURN INTEGER IS Specify schema, table and retval INTEGER; WHERE clause... BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || NVL (sch, USER) || '.' || tab || ' WHERE ' || NVL (whr, '1=1') INTO retval; RETURN retval; END; IF tabCount ('citizens', 'insured = ''NO''') > 40,000,000 THEN DBMS_OUTPUT.PUT_LINE ( 'Not the best health care system in the world... and not much of a democracy either!'); END IF;
7/10/2013 Copyright 2001 Steven Feuerstein
Perform an update...
Pass in bind variables with USING clause.
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) IS BEGIN EXECUTE IMMEDIATE 'UPDATE ' || tab_in || ' SET ' || col_in || ' = :val WHERE hiredate BETWEEN :lodate AND :hidate' USING val_in, start_in, end_in; END;
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; 7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 85
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.
PROCEDURE add_profit_source ( hosp_name IN VARCHAR2, pers IN Person, cond IN preexisting_conditions) IS BEGIN EXECUTE IMMEDIATE 'INSERT INTO ' || tabname (hosp_name) || ' VALUES (:revenue_generator, :revenue_inhibitors)' USING pers, cond; END;
7/10/2013 Copyright 2001 Steven Feuerstein
health$.pkg
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 Familiar cursor FETCH cv INTO val; variable syntax! EXIT WHEN cv%NOTFOUND; DBMS_OUTPUT.PUT_LINE (val); END LOOP; CLOSE cv; showcol.sp END; ndsutil.pkg
You cannot pass schema elements (table names, column names, etc.) through the USING clause. You cannot pass the NULL literal directly in the USING clause. Instead, pass a variable with a NULL value. The USING clause for a query can only have IN bind arguments. You can have duplicate placeholders (for bind arguments).
If dynamic SQL, then you provide a value for each placeholder (by position). If dynamic PL/SQL, provide a value for each distinct placeholder (by name).
str2list.pkg
Prior to Oracle8i, the only way to perform dynamic SQL was with the DBMS_SQL package. DBMS_SQL is a very large and complex package, with many rules to follow and lots of code to write. Supports all four methods of dynamic SQL, in particular method 4. The overhead for using DBMS_SQL has decreased significantly in Oracle8 and again in Oracle8i.
You should only use DBMS_SQL when you cannot use NDS.
DDL
Create an index from within PL/SQL
DML
Update rows in a table
Queries
Method 3 and a dynamic WHERE clause
PL/SQL
Create a generic calculation program
creind.sp
updnval1.sp
updnval2.sp updnval3.sp
CREATE OR REPLACE PROCEDURE showemps (where_in IN VARCHAR2 := NULL) IS cur INTEGER := DBMS_SQL.OPEN_CURSOR; rec emp%ROWTYPE; fdbk INTEGER; BEGIN DBMS_SQL.PARSE (cur, 'SELECT empno, ename FROM emp ' || ' WHERE ' || NVL (where_in, '1=1'), DBMS_SQL.NATIVE); DBMS_SQL.DEFINE_COLUMN (cur, 1, 1); DBMS_SQL.DEFINE_COLUMN (cur, 2, 'a', 60);
fdbk := DBMS_SQL.EXECUTE (cur); LOOP EXIT WHEN DBMS_SQL.FETCH_ROWS (cur) = 0; DBMS_SQL.COLUMN_VALUE (cur, 1, rec.empno); DBMS_SQL.COLUMN_VALUE (cur, 2, rec.ename); DBMS_OUTPUT.PUT_LINE (TO_CHAR (rec.empno) || '=' || rec.ename); END LOOP; DBMS_SQL.CLOSE_CURSOR (cur); END;
7/10/2013 Copyright 2001 Steven Feuerstein
Method 4 example: the number of columns queried changes with each table.
The resulting code is much more complicated.
BEGIN FOR each-column-in-table LOOP add-column-to-select-list; END LOOP; DBMS_SQL.PARSE (cur, select_string, 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;
PL/SQL Advanced Techniques - page 96
intab.sp
Using EXECUTE_AND_FETCH
FUNCTION execute_and_fetch (cursor_in IN INTEGER, exact_match IN BOOLEAN DEFAULT FALSE) RETURN INTEGER; numrows := DBMS_SQL.EXECUTE_AND_FETCH (cur); numrows := DBMS_SQL.EXECUTE_AND_FETCH (cur, TRUE);
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 := arg1_in IN VARCHAR2 := arg3_in IN VARCHAR2 := arg5_in IN VARCHAR2 := arg7_in IN VARCHAR2 := arg9_in IN VARCHAR2 := ) RETURN VARCHAR2;
arg2_in IN VARCHAR2 := NULL, arg4_in IN VARCHAR2 := NULL, arg6_in IN VARCHAR2 := NULL, arg8_in IN VARCHAR2 := NULL, arg10_in IN VARCHAR2 := NULL
dyncalc.sf dyncalc.pkg
Use BIND_VARIABLE to bind any placeholders in the string -- even OUT arguments which are not being bound to any values. Use VARIABLE_VALUE to extract a value from any variable you have bound. You must have a BEGIN-END around the code. Possibilities inherent in dynamic PL/SQL are mind-boggling!
dynplsql.sql dynplsql.sp dynplsql.tst
Oracle Forms offers support for indirect referencing with the NAME_IN and COPY built-ins.
PL/SQL does not support indirect referencing, but you can accomplish much of the same thing with dynamic PL/SQL execution. Here is an example of a "PL/SQL NAME_IN":
FUNCTION valbyname (nm IN VARCHAR2) RETURN VARCHAR2 IS v_cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR; fdbk PLS_INTEGER; retval PLV.dbmaxvc2; BEGIN DBMS_SQL.PARSE (v_cur, 'BEGIN :val := ' || nm || '; END;', DBMS_SQL.NATIVE); DBMS_SQL.BIND_VARIABLE (v_cur, 'val', 'a', 2000); fdbk := DBMS_SQL.EXECUTE (v_cur); DBMS_SQL.VARIABLE_VALUE (v_cur, 'val', retval); DBMS_SQL.CLOSE_CURSOR (v_cur); RETURN retval; END;
PL/SQL Advanced Techniques - page 100
dynvar.pkg dynvar.tst
The package offers a set of modules to return information about the lastoperated cursor in your session.
Call these immediately after your usage to make sure they refer to your cursor.
IS_OPEN
Is the cursor already open?
LAST_ERROR_POSITION
Returns relative column position in cursor of text causing error condition.
LAST_ROW_COUNT
Returns the cumulative count of rows fetched from the cursor.
LAST_ROW_ID
Returns the ROWID of last row processed.
LAST_SQL_FUNCTION_CODE
Returns SQL function code of cursor.
DBMS_SQL provides special procedures so that you can extract values from a LONG column in a table.
DBMS_SQL.DEFINE_COLUMN_LONG DBMS_SQL.COLUMN_VALUE_LONG
Then you retrieve the column using the special COLUMN_VALUE_LONG variant.
Transfer the LONG contents into an index-by table so that you can transfer a value of more than 32K bytes into your PL/SQL program.
dumplong.pkg dumplong.tst
Before PL/SQL8, it was not possible to determine the datatypes of the columns defined in a cursor.
Now you can call the DBMS_SQL.DESCRIBE_COLUMNS.
The following script shows the individual steps you will need to perform in order to use this feature.
CREATE OR REPLACE PROCEDURE show_columns IS cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR; cols DBMS_SQL.DESC_TAB; ncols PLS_INTEGER; BEGIN DBMS_SQL.PARSE (cur, 'SELECT hiredate, empno FROM emp', DBMS_SQL.NATIVE); DBMS_SQL.DESCRIBE_COLUMNS (cur, ncols, cols); FOR colind IN 1 .. ncols LOOP DBMS_OUTPUT.PUT_LINE (cols(colind).col_name); END LOOP; DBMS_SQL.CLOSE_CURSOR (cur); END;
PL/SQL8 now allows you to specify the use of "arrays", i.e., index tables, when you perform updates, inserts, deletes and fetches. Instead of providing a scalar value for an operation, you specify an index table. DBMS_SQL then repeats your action for every row in the table. It really isn't "array processing".
In actuality, DBMS_SQL is executing the specified SQL statement N times, where N is the number of rows in the table.
This technique still, however, can offer a significant performance boost over Oracle7 dynamic SQL.
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.sql openprse.pkg whichsch.sql
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!
In the distributed world of the Internet, systems are now heavily reliant on the ability of components to communicate with each other in a dependable, consistent manner.
Distribution Center Coffee Beans Producer Shipping Service
Retailer
Consumer
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 111
Auction portals
Any e-commerce application
Database high availability, scalability and reliability all carry over to queues
Strong history and retention Backup and recovery Comprehensive journaliing
AQ architectural overview
Queue Monitor process
Queue table
Queue
Producers
Enqueued messages
Message 4 Message 3 Message 2 Message1
Consumers
Dequeued messages
Oracle AQ Highlights
In 8.0, AQ supports:
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
Oracle8i adds:
Rules-based publish & subscribe Listening on multiple queues Easier monitoring Native Java interface
PL/SQL Advanced Techniques - page 115
AQ Components
The DBMS_AQ package offers enqueue and dequeue capabilities The DBMS_AQADM package provides administrative functionality to manage queues and queue tables. Underlying database tables and views The queue monitor (background process)
Set # of processes with the AQ_TM_PROCESSES initialization parameter.
DBMS_AQADM Highlights
CREATE_QUEUE_TABLE DROP_QUEUE_TABLE CREATE_QUEUE Assigns name, payload type, storage clause, sort column, whether multiple consumers Drops table if all queues in the table have been stopped Associates queue table with queue; assigns retry and retention properties to queue
DROP_QUEUE
START_QUEUE STOP_QUEUE ADD_SUBSCRIBER
ENQUEUE puts a message into a specified queue, and returns a RAW message handle
aq.sql
aqenq*.*
Place the message on the specified queue and get a msg ID in return.
PL/SQL Advanced Techniques - page 120
Modify the dequeue sequence by changing the deviation field and relative msg ID.
Dequeue Example
DECLARE queueopts DBMS_AQ.DEQUEUE_OPTIONS_T; msgprops DBMS_AQ.MESSAGE_PROPERTIES_T; msgid aq.msgid_type; /* defined in aq.pkg */ my_msg message_type; PROCEDURE getmsg (mode_in IN INTEGER) IS BEGIN queueopts.dequeue_mode := mode_in; DBMS_AQ.DEQUEUE ( 'msgqueue', queueopts, msgprops, my_msg, msgid); END; BEGIN getmsg (DBMS_AQ.BROWSE); getmsg (DBMS_AQ.REMOVE); getmsg (DBMS_AQ.REMOVE); END;
aqdeq*.*
Prioritized Payloads
You can assign priorities to individual payloads and then dequeue according to those priorities.
The lower the numeric priority value, the higher the priority.
Oracle AQ - Summary
LOB Terms
LOB = Large OBject: a category of datatype allowing storage of unstructured data up to 4 gigabytes LOB datatype can be:
Column in table Attribute in object type Element in nested table
Internal LOBs
BLOB: unstructured binary data CLOB: single-byte fixedwidth character data NCLOB: multi-byte fixedwidth character data (or varying width in )
External LOBs
BFILE: pointer to an operating system file
Temporary LOBs
Internal LOBs that do not participate in transactions, improving performance.
Package DBMS_LOB
Supports for all LOBs: Reading, substring and instring searches, comparison and length checking For internal LOBs: Write, append, copy, erase, trim For external LOBs: File existence test, open, close
Updating
This
LOB locators cannot span transactions Programs must lock records containing LOBs before updating Programs performing DML must accommodate dynamic LOB locator values
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 131
Create a table
CREATE TABLE web_pages ( url VARCHAR2(512) PRIMARY KEY, htmlloc CLOB);
1 of 2
Must get a new LOB locator before writing into the LOB:
DECLARE the_url web_pages.url%TYPE := 'http://www.oodb.com'; the_loc CLOB; BEGIN INSERT INTO web_pages VALUES (the_url, EMPTY_CLOB()) RETURNING htmlloc INTO the_loc;
...
BEGIN This block retrieves INSERT INTO web_pages VALUES (the_url, EMPTY_CLOB()) and loads a web page RETURNING htmlloc INTO the_loc;
Syntax:
Example:
In a table...
CREATE TABLE web_graphics ( image_id INTEGER, image BFILE);
In an object type...
CREATE TYPE Business_card_t AS OBJECT ( name Name_t, addresses Address_tab_t, phones Phone_tab_t, scanned_card_image BFILE ); /
Example:
DECLARE picture BFILE := BFILENAME('WEB_PIX', 'prodicon.gif'); BEGIN INSERT INTO web_graphics VALUES (100015, picture); END; /
Notes:
No automatic file checking or synchronization No participation in transactions
ISOPEN
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 139
HOT
Some weaknesses...
LOB locator behavior slightly abstruse Inability to modify BFILEs directly from within PL/SQL.
COLD
Could it be...
The end of programming history as we know it? The easiest, fastest, slickest piece of software ever designed by human beings? Just the latest in a series of "silver bullets" promoted by software vendors in order to prop up quarterly sales? The first and only successful O-O language? None of the above?
While that scenario is certainly possible, it is very unlikely and totally unthinkable for years to come. PL/SQL will still be:
Faster and more productive than Java for database operations. A language in which hundreds of thousands of developers are trained. Ubiquitous in thousands of production applications and millions of lines of code. Supported and improved by Oracle -- and very aggressively, to boot.
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.
Compiling Classes
Before you can use a class, you must compile it with the javac command.
You must also either have set the CLASSPATH or include it in your javac call. This will convert the .java file to a .class file. It will also automatically recompile any classes used by your class that has not been compiled since last change.
Running a Class
Run a class? What does that mean? It means that if your class contains a method with this header:
public static void main (String[] args)
then you can "run" the main method with the java command:
d:\java> java Hello
You can also pass one or more arguments on the command line:
d:\java> java Hello mom Hello2.java
Using a Class
The main method is handy for providing a built-in test mechanism of your class. Usually, however, you will use a class by instantiating objects from the class and then invoking class methods on the object.
}
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 150
Types of Classes
There are several different kinds of classes in Java, besides the "regular" kind we just saw...
Abstract class
The class can contain members and methods, but at least one method remains unimplemented.
Interface class
The class only contains unimplemented methods.
Inner class
Classes that are defined inside other classes.
Inheritance
Subclasses inherit methods and variables from extended superclasses.
Polymorphism
An object of a class can have "multiple" forms, either as its own class or as any superclass. Dynamic polymorphism: form is determined at run-time. Static polymorphism: form is chosen at compile-time (PL/SQL overloading). supertype/"wider"
Citizen
Person Employee Hourly Worker
subtype/"narrower"
War Criminal
Person.java
A Class Hierarchy
Management NonManagement
Corporation
Salaried Worker
Language Basics
Comments
Primitive datatypes
So I lied; these are not objects instantiated from classes.
Strings
A String object is a read-only; if you assign a new value to it, you are actually allocating a new object.
for (initialize; expression; step) { lotsaStuff } while (expression) { lostsaStuff } do { lostsaStuff } while (expression);
Examples:
for (indx indx=0; indx < args.length; indx++) System.out.println (args[indx]);
static void processAll (Enumeration enum) { while (enum.hasMoreElements ()) { processIt (enum.NextElement()); System.out.println ( (String)enum.nextElement()) } }
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 154
Passing Parameters
Very similar to PL/SQL; you "throw" and "catch" exceptions, rather than raise and handle.
Throwing my own exception
public static int numBytes (String filename) { try { if (filename.equals("")) throw new Exception ("Filename is NULL"); Put inside try clause File myFile = new File (filename); return myFile.length();
}
catch (SecurityException e) { return -1; } catch (Exception e) { System.out.println (e.toString()); } }
7/10/2013 Copyright 2001 Steven Feuerstein
File-related exceptions
Exceptions are objects derived directly or indirectly from the Exception class.
So you can pass them as arguments and do anything and everything else you can do with objects.
You must inform users of your method of which exceptions may be thrown.
Use the throws clause in your specification, as in:
public static int numBytes (String filename) throws SecurityException, NoSuchFile { ... }
Now let's explore how you can put Java to work for you inside PL/SQL programs.
Oracle 8i server
PL/SQL cover for Java method Java virtual machine running Java method
Net8
extender
For example, better file I/O RMI callouts, network communication in/out
PL/SQL
replacement
More standard language Good performer for numeric processing tasks Beware database I/O & string manipulation performance
Scaleable
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
public static void main (String[] args) { // A very scary company Corporation TheGlobalMonster = new Corporation ( "Northrup-Ford-Mattel-Yahoo-ATT", 5000000, 50000000); System.out.println (TheGlobalMonster);
}} 7/10/2013 Copyright 2001 Steven Feuerstein
loadjava
Example: loadjava
options (abbreviated)
-oci8 -resolve
loadjava will connect using OCI driver Resolves external class references at compile time -resolver (shown later) Search path like CLASSPATH
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 163
Publish
CREATE OR REPLACE FUNCTION hello_emp (empno_in IN NUMBER) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'datacraft.bill.Hello.Emp(int) return java.lang.String'; /
Syntax (simplified)
CREATE [ OR REPLACE ] { PROCEDURE | FUNCTION } <name> [ RETURN <sqltype> ] [ ( <args> ) ] [ AUTHID { DEFINER | CURRENT_USER } ] AS LANGUAGE JAVA NAME '<method fullname> (<Java type fullname>, ...) [ return <Java type fullname> ]';
mapping
Java methods declared void become PL/SQL procedures Signature mismatches detected only at runtime
Type
mapping (typical)
VARCHAR2 DATE NUMBER
oracle.sql.STRUCT
<named type> oracle.sql.REF oracle.sql.ARRAY
Either in 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'); /
or in the body:
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; /
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 169
CREATE JAVA
Alternative to loadjava utility Creates or replaces an Oracle library unit from Java source, class, or resource Can read file designated with BFILE() function
ALTER JAVA: compiles Java source, resolves Java class references. DROP JAVA: drops a named Java library unit
Java object
JPublisher utility
Database server
Definition of type or REF in data dictionary
User-supplied JPublisher input file
What
does it do?
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)'; /
jspobj.sql
You can read/write files in PL/SQL with UTL_FILE, but that package is very limited.
Java offers many file-related classes with much greater capabilities. Let's see how we can make that great Java stuff available from within PL/SQL.
You won't generally access native Java methods in your PL/SQL wrapper.
Instead build a static method that instantiates a Java object from the class and then invokes the relevant method against that object.
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 xfile IS FUNCTION length (file IN VARCHAR2) RETURN NUMBER; END; / CREATE OR REPLACE PACKAGE BODY xfile IS FUNCTION length (file IN VARCHAR2) RETURN NUMBER AS LANGUAGE JAVA NAME 'JFile.length (java.lang.String) return long'; END; / xfile2.pkg
Both Java and PL/SQL support a native Boolean datatype, so you'd expect smooth sailing. Not so!
To pass a Boolean back from Java to PL/SQL, you will need to take these steps:
1. Convert the Java boolean to a String or number and return that value. 2. Write a "hidden" PL/SQL wrapper function that returns the string or number. 3. Write a "public" PL/SQL wrapper function to convert that number to a true PL/SQL Boolean.
JFile3.java
You can pass Oracle object information to Java without relying on JPub by using the STRUCT class.
public class UnionBuster { Obtain attributes of public static void wageStrategy (STRUCT e) the Oracle object. throws java.sql.SQLException { // Get the attributes of the labor_source object. Object[] attribs = e.getAttributes(); // Access individual attributes by array index, // starting with 0 String laborType = (String)(attribs[0]); BigDecimal hourly_rate = (BigDecimal)(attribs[1]); System.out.println ( "Pay " + laborType + " $" + hourly_rate + " per hour"); } }
You can pass Oracle object information to Java without relying on JPub by using the STRUCT class.
The Oracle object type definition
CREATE OR REPLACE PROCEDURE bust_em_with ( labor_source_in IN labor_source_t) AS LANGUAGE JAVA NAME 'UnionBuster.wageStrategy (oracle.sql.STRUCT)'; / BEGIN bust_em_with ( labor_source ('Workfare', 0)); bust_em_with ( labor_source ('Prisoners', '5')); END;
7/10/2013 Copyright 2001 Steven Feuerstein
}}}
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:
SET SERVEROUTPUT ON SIZE 1000000 CALL DBMS_JAVA.SET_OUTPUT (1000000);
HelloAll.java HelloAll.tst
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 8.1.5).
Currently, the entire Java stack is displayed on your screen, and you have to do some digging to extract the Oracle error information.
BEGIN dropany ('TABLE', 'blip'); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE (SQLCODE); DBMS_OUTPUT.PUT_LINE (SQLERRM); END; /
SQL> 2 3 4 5 6 7 8 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)
-29532 dropany.tst ORA-29532: Java call terminated by uncaught Java exception: java.sql.SQLException: getErrInfo.sp dropany2.tst ORA-00942: table or view does not exist 7/10/2013 Copyright 2001 Steven Feuerstein
DropAny.java
External Procedures
An external procedure is a 3GL routine that can serve as the body of a PL/SQL function, procedure, or method.
Must be a shared object library on Unix or a dynamically linked library (DLL) on Windows.
An Oracle8 feature that allowed relatively "native" callouts to C from PL/SQL for the first time.
Calls
extproc
returns results
Send email
email.sql
diskspace.sql
CREATE OR REPLACE PACKAGE 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;
END disk_util; /
section
Usage (4/4)
DECLARE lroot_path VARCHAR2(3) := 'C:\'; lsectors_per_cluster PLS_INTEGER; lbytes_per_sector PLS_INTEGER; lnumber_of_free_clusters PLS_INTEGER; ltotal_number_of_clusters PLS_INTEGER; return_code PLS_INTEGER; free_meg REAL; BEGIN return_code := disk_util.get_disk_free_space (lroot_path, lsectors_per_cluster, lbytes_per_sector, lnumber_of_free_clusters, ltotal_number_of_clusters);
Syntax:
Oracle8i offers a number of PL/SQL-specific features that give you tremendous additional flexibility and capability.
And the learning curve to take advantage of these features is generally not too steep.
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 Mechanism
Commonly developers lot to database tables, which can cause all sorts of complications: your log entry becomes a part of your transaction. Now you can avoid the complexities (need for ROLLBACK TO savepoints and so on).
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;
7/10/2013 Copyright 2001 Steven Feuerstein
retry.pkg retry.tst
The AT PRAGMA can be used only with individual programs and toplevel anonymous blocks.
You cannot define an entire package as an AT. You cannot define a nested anonymous block to be an AT.
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
Allows you to centralize access to and control of underlying data structures. Ignores roles and relies on directly-granted privileges. But it can be a source of confusion and architectural problems.
OE Code
Order_Mgt
Place
Sam_Sales
Close Old Orders
Cancel
OE Data
X
Cannot alter table directly.
Orders
Note: Oracle built-in packages have long had the capability of running under the invoker's authority.
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 204
Security
No declarative way to restrict privileges on certain modules in a package -- it's all or nothing, unless you write code in the package to essentially recreate roles programmatically. Can bypass 8is fine-grained access features (see the DBMS_RLS package) Difficult to audit privileges
For modules with separate spec and body, AUTHID goes only in spec, and must be at the package level. Synonyms may be necessary if modules use object names not qualified by schema.
In other words, do what it takes to get the code to compile. You could also create local, "dummy" objects. At run-time, the objects referenced may well be different from those against which it compiled.
With invoker rights, you can execute code owned by another schema, yet have all references to data structures "reflect back" into your own schema.
User/Data schema
PROCEDURE mng_account IS BEGIN ... code.acct_mgr.destroy(...); END;
modify destroy
accounts table
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.sql
oneversion.sql
You want to reuse the same code among many users, but you want their own directly-granted privileges to determine access.
National HQ Check City Statistics New York Schema stolenlives
Rely on invoker rights to allow centralized code to work with schema-specific data. Rely on definer rights to access centralized data from any schema.
HQ
Chicago
Check City Statistics
New York
stolenlives
Analyze Pattern
stolenlives
perpetrators
perp.sql
DBMS_RLS
Row-Level Security
Oracle8i offers a new package, DBMS_RLS, with which to implement automated row-level security (also referred to as "fine grained access control").
Row Level Security
The establishment of security policies on (restricted access to) individual rows of a table.
Prior to Oracle8i, you could achieve this only partially through the use of views.
The DBMS_RLS package (along with "system contexts") now allow you to do so in a foolproof manner.
PL/SQL Advanced Techniques - page 212
System Contexts
A new feature in Oracle8i, the system context is a named set of attributevalue pairs global to your session.
DBMS_RLS
Use programs in DBMS_RLS to associate your security policies with tables.
Let's step through a simple example to see how all these pieces tie together.
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.
fgac.sql
Define a context in the database, and create a procedure that will set the context attributes (type and ID) upon login.
CREATE CONTEXT patient_restriction USING nhc_pkg; PROCEDURE set_context IS This is a simplification. See CURSOR doc_cur IS fgac.sql for logic that identifies SELECT doctor_id FROM doctor different types of people and WHERE schema_name = USER; sets the context accordingly. doc_rec doc_cur%ROWTYPE; BEGIN OPEN doc_cur; FETCH doc_cur INTO doc_rec; DBMS_SESSION.SET_CONTEXT ( 'patient_restriction', c_person_type_attr, 'DOCTOR'); DBMS_SESSION.SET_CONTEXT ( 'patient_restriction', c_person_id_attr, doc_rec.doctor_id); END;
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 215
A predicate is a string that will be appended to the WHERE clause of the table to which this security policy is associated (see next page).
FUNCTION person_predicate ( schema_in VARCHAR2, name_in VARCHAR2) RETURN VARCHAR2 IS l_context VARCHAR2(100) := Extract the context SYS_CONTEXT (c_context, c_person_type_attr); information for this retval VARCHAR2(2000); connection. BEGIN IF l_context = 'DOCTOR' THEN retval := 'home_clinic_id IN (SELECT home_clinic_id FROM doctor We need a different string WHERE doctor_id = SYS_CONTEXT (''' || to modify the WHERE c_context || ''', ''' || clause for each type of c_person_id_attr || '''))'; person.
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 DATABASE BEGIN nhc_pkg.set_context; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE ( 'Error ' || SQLCODE || ' setting context for ' || USER'); END;
fgac.sql
Exception handling in trigger is critical! If you allow an exception to go unhandled, logon is disabled.
Context Information for "CSILVA": Type: PATIENT ID: Predicate: schema_name = 'CSILVA' Patients Visible to "CSILVA": CSILVA - Chris Silva - IL 7/10/2013 Copyright 2001 Steven Feuerstein
You learn what you need to get the job done, and you use only what you know.
Jump out of your rut - and play a new tune with PL/SQL!
Appendices
(a.k.a., If time permits)
UTL_FILE file IO
DBMS_JOB Job scheduling DBMS_PIPE Pipe-based communication DBMS_UTILITY the kitchen sink package
UTL_FILE
Server-side File I/O
Application FOPEN GET_LINE PUT_LINE ...
Physical Files
Allows you to read from and write to operating system files on the database server. A "version 1" (fairly primitive) utility...
Maximum of 1023 bytes per line (read or write) until Oracle 8.0.5, when it jumps to 32K. No higher-level file operations supported (change privileges, delete, copy, random access to contents). Limitations on files you can access (no mapped files, no use of environmental variables).
But you can read lines from a file and write lines to a file.
NEW_LINE
PUT PUT_LINE PUTF
Oracle requires you to list explicitly those directories you wish to be able to read/write with UTL_FILE.
You do this by adding lines to the instance parameter file.
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; /
utlfile.tst
Opening a File
DECLARE fid UTL_FILE.FILE_TYPE; BEGIN fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W'); END;
The FOPEN function returns a record ("file handle") based on the UTL_FILE.FILE_TYPE.
Currently contains a single ID field.
Maximum of 10 files may be opened in each user session. Test to see if file is open with the IS_OPEN function.
In actuality, this function simply returns TRUE if the file handle's id field is NOT NULL. Not much of a test...
Can only read from a file opened with the "R" mode.
The NO_DATA_FOUND exception is raised if you read past the end of the file.
You might want to build your own GET_LINE which handles the exception and returns an EOF Boolean status flag.
getnext.sp
Writing to a File
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 is so much fun that I never want to stop
Resulting Text
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.
PL/SQL Advanced Techniques - page 229
Closing a File
DECLARE fid UTL_FILE.FILE_TYPE; BEGIN fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'R'); UTL_FILE.GET_LINE (fid, myline); UTL_FILE.FCLOSE (fid); EXCEPTION WHEN UTL_FILE.READ_ERROR THEN UTL_FILE.FCLOSE (fid); END;
If you do not close the file, you will not see the data you have (supposedly) written to that file. You can close a single file with FCLOSE or all open files with FCLOSE_ALL. You should close files in exception handlers to make sure that files are not left "hanging" open.
PL/SQL Advanced Techniques - page 230
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.
utlflexc.sql
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.
PL/SQL Advanced Techniques - page 232
DBMS_JOB
Scheduled Execution of Stored Procedures
Application SUBMIT RUN Oracle Job Queue Subsystem
DBA_JOBS
REMOVE
...
Background Process
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.
CHANGE INTERVAL
ISUBMIT
NEXT_DATE REMOVE
RUN
SUBMIT USER_EXPORT
WHAT
Submitting a Job
DECLARE job# BINARY_INTEGER; BEGIN DBMS_JOB.SUBMIT (job#, 'calculate_totals;', SYSDATE, 'SYSDATE + 1'); END;
When you submit a job, you specify the date on which it should next execute, and then the jobs 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 for you.
PL/SQL Advanced Techniques - page 236
This block submits a job that uses a built-in procedure, DBMS_DDL.ANALYZE_OBJECT, to analyze a specific table every evening at midnight.
PL/SQL Advanced Techniques - page 237
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 automatically.
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.
Every hour Every Sunday at 2 AM First Monday of each quarter, at 9 AM Every Monday, Wednesday and Friday at 6 PM 'SYSDATE + 1/24' 'NEXT_DAY (TRUNC (SYSDATE), ''SATURDAY'') 2/24' 'NEXT_DAY ( ADD_MONTHS (TRUNC (SYSDATE, ''Q''), 3), ''MONDAY'') + 9/24' +
'TRUNC (LEAST ( NEXT_DAY (SYSDATE, ''MONDAY''), NEXT_DAY (SYSDATE, ''WEDNESDAY''), NEXT_DAY (SYSDATE, ''FRIDAY''))) + 18/24'
PL/SQL Advanced Techniques - page 239
You can also view the job queue by looking at the DBA_JOBS_RUNNING, DBA_JOBS and USER_JOBS views.
The following query blends job information with session information to display currently-executing jobs, who owns them and when they began.
SELECT jr.job ,username ,jr.this_date ,what job_id username start_date job_definition jr j s
showjobs.sql
FROM
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).
spacelog.sql showspc.sql
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.
You may find it useful to always wrap your stored procedure call (the what) inside a BEGIN-END block.
We've noticed some aberrant, difficult to reproduce behavior at times with "straight" procedure calls.
You will need to fully-qualify all database links (with user name and password) to get them to work properly.
If you find that you submit a job to run immediately and it does not start, perform a COMMIT after your submit.
When a job runs, it picks up the current execution environment for the user.
You can use the DBMS_IJOB to manage the jobs of other users.
DBMS_JOB only allows you to modify characteristics and behavior of jobs submitted by the current schema.
DBMS_PIPE
Inter-session Communication
DBMS_PIPE Overview
Allows communication between different Oracle sessions through a pipe in the RDBMS Shared Global Area.
Operates outside of database transaction limitations.
Architecture of DBMS_PIPE
Shared Global Area Sue
Bob
Message Buffer Message Buffer
Session A
Session B
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 with ORA$. They are reserved for Oracle use).
pipex1.sql pipex2.sql
Description
Creates a PUBLIC or PRIVATE pipe.
NEXT_ITEM_TYPE Returns the datatype of the next item in the piped message. PACK_MESSAGE Packs an item into the message buffer for your session. PURGE Empties the contents of a pipe into your local buffer freeing it for removal, making it a candidate for removal with a LRU algorithm.
RECEIVE_MESSAGE Receives a message from pipe and copies to local buffer. REMOVE_PIPE RESET_BUFFER Removes a pipe explicitly created via CREATE_PIPE. Clears your buffer so that PACK_MESSAGE and UNPACK_MESSAGE can work from the first item.
SEND_MESSAGE Sends contents of message buffer to the specified pipe. UNIQUE_SESSION_NAME Returns name that is unique among all sessions in the database.
UNPACK_MESSAGE Unpacks the next item from the local message buffer and deposits it into the specified local variable.
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 250
An explicit pipe can be private (accessible only to sessions with matching userID or SYSDBA privileges).
Specify TRUE for private argument of CREATE_PIPE.
Sending a Message
Provide pipe name, seconds you will wait, and new max pipe size (you can make it bigger, but not smaller). FUNCTION send_message (pipename IN VARCHAR2, timeout IN INTEGER DEFAULT maxwait, maxpipesize IN INTEGER DEFAULT 8192) RETURN INTEGER;
FOR month_num IN 1 .. 12 LOOP DBMS_PIPE.PACK_MESSAGE ( total_sales (month_num)); END LOOP; pipe_stat := DBMS_PIPE.SEND_MESSAGE ( monthly, 60, 10 * 4096);
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.
PROCEDURE unpack_message (item OUT VARCHAR2);
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.
Program wakes up every N seconds to read the data from the pipe and analyze results from assembly line (an intentional "infinite loop"!).
PROCEDURE analyze_assembly_data (every_n_secs IN INTEGER) IS pipe_status INTEGER; Wait up to N prod_total NUMBER; seconds for the BEGIN next report. LOOP pipe_status := DBMS_PIPE.RECEIVE_MESSAGE ( 'production', every_n_secs); If I got something, pass it on to the IF pipe_status = 0 computation THEN program. DBMS_PIPE.UNPACK_MESSAGE (prod_total); analyze_production (SYSDATE, prod_total); ELSE RAISE_APPLICATION_ERROR ( Stop process if -20000, 'Production data unavailable.'); data not received. END IF; END LOOP; END; PL/SQL Advanced Techniques - page 254
dbpipe.sql dbpipe.tst
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.
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.
Process A
Process B
Process C
Process A
Process B
Process C
End
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 257
The wait program waits till it receives a message from each program. Then net profits can be computed -- in a muchdecreased elapsed time.
PL/SQL Advanced Techniques - page 258
Receive the year, calculate sales, and send back the results.
Wait for all calculations to finish. The order in which you wait is insignificant.
parallel.sql
watch.pkg p_and_l.pkg
GET_HASH_VALUE
FORMAT_CALL_STACK ...
SCHEMA
COMMA_TO_TABLE
COMPILE_SCHEMA
DATA_BLOCK_ADDRESS_BLOCK DATA_BLOCK_ADDRESS_FILE
DB_VERSION
EXEC_DDL_STATEMENT FORMAT_CALL_STACK
FORMAT_ERROR_STACK
GET_HASH_VALUE
7/10/2013 Copyright 2001 Steven Feuerstein
dbver.pkg dbparm.pkg
GET_TIME returns the number of 100ths of seconds that have elapsed since an arbitrary point in time.
SYSDATE only reflects times down to the nearest second, so GET_TIME offers significant additional granularity. Useful when analyzing individual PL/SQL programs, especially those that run in sub-second time.
Compare results from consecutive calls to GET_TIME to determine the elapsed time of PL/SQL code execution.
Basic steps necessary to convert GET_TIME into a performance analysis tool.
DECLARE v_start BINARY_INTEGER; BEGIN v_start := DBMS_UTILITY.GET_TIME; calc_totals; DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.GET_TIME - v_start); END;
PL/SQL Advanced Techniques - page 265
Most recent program at beginning of "report". Does not show package elements, only the package name.
7/10/2013 Copyright 2001 Steven Feuerstein
PL/SQL Advanced Techniques - page 267
The call stack length can easily exceed 255 bytes, which means you cannot pass it to DBMS_OUTPUT directly. Instead, use a loop to read through the stack.
CREATE OR REPLACE PROCEDURE dispcs IS stk VARCHAR2(10000); next_newline INTEGER; next_line VARCHAR2(255); startpos INTEGER := 1; BEGIN stk := DBMS_UTILITY.FORMAT_CALL_STACK || CHR(10); LOOP next_newline := INSTR (stk, CHR(10), startpos, 1); EXIT WHEN next_newline = 0; next_line := SUBSTR ( stk, startpos, next_newline - startpos + 1);
DBMS_OUTPUT.PUT_LINE (next_line);
startpos := next_newline + 1; END LOOP; END;
PL/SQL Advanced Techniques - page 268
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);
What a chore! All those arguments...but don't see it as a problem, see it as an opportunity...for encapsulation!
showcomp.sp snc.pkg