RESULT_CACHE RELIES_ON (NLS_SESSION_PARAMETERS) - caching

Why below function is not returning fresh param value every time I am altering session to set new NLS_DATE_FORMAT
FUNCTION get_param(p_parameter IN VARCHAR2)
RETURN VARCHAR2 RESULT_CACHE relies_on(nls_session_parameters) IS
l_value nls_session_parameters.value%TYPE;
BEGIN
dbg('Entered Fn_Get_nls_session_Parameter_frc to cache details for .. ' || p_parameter);
SELECT SYS_CONTEXT('USERENV', p_parameter) INTO l_value FROM dual;
RETURN l_value;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbg('In NDF : Gng to return value as null.. ');
l_value := NULL;
RETURN l_value;
END get_param;

Well... I would say the answer stands in the question! If you carefully read Oracle documentation about Cross Session Functions, then you'd know.
The cross-session PL/SQL function result cache provides a simple way to boost the performance of PL/SQL functions by saving the results of function calls for specific combinations of input parameters in the SGA. These results can be reused by any session calling the same function with the same parameters.
This is exactly what you're using when creating your function:
FUNCTION get_param(p_parameter IN VARCHAR2)
RETURN VARCHAR2
RESULT_CACHE relies_on(nls_session_parameters)
IS
Indeed nls_session_parameters View doesn't change between your calls! it is a fixed system view. What changes it what your user sees from it.
So you have solutions:
simpler and inefficient(sorry): remove RESULT_CACHE statement from your function declaration or find a way to refresh the cache between the calls
add a parameter that will change between your calls:
FUNCTION get_param(p_parameter IN VARCHAR2, p_dummy_session_id IN NUMBER)
RETURN VARCHAR2 RESULT_CACHE relies_on(nls_session_parameters) IS
...
(you might need to actually do something with the "dummy" parameter for it to be taken into account)

1) With Oracle Database 11gR2, the RELIES ON clause is deprecated, which means that you don’t even have to list the dependencies: Oracle will figure everything out for you.
2) Moreover Oracle has V$RESULT_CACHE_OBJECTS. There are information about cached object.
3) You can also force the oracle to refresh 'result_cache'
declare
n number;
begin
n := DBMS_RESULT_CACHE.INVALIDATE (user,'GET_PARAM');
end;

Related

A syntax for custom lazy-evaluation/short-circuiting of function parameters

Oracle defines several structures that make use of what looks like lazy evaluation but what's actually short-circuiting.
For example:
x := case when 1 = 2 then count_all_prime_numbers_below(100000000)
else 2*2
end;
The function count_all(...) will never be called.
However, what I'm more interested in is the syntax that looks like regular function call:
x := coalesce(null, 42, hundreth_digit_of_pi());
Hundreth_digit_of_pi() will not be called since coalesce is not a regular function, but a syntax sugar that looks like one - for regular ones parameters get evaluated when the function is called.
The question is:
is it possible do define in plsql a custom procedure/function that would behave in the same way?
If you're not convinced I'll give an example when that could be useful:
We use ''framework'' for logging.
To trace something you call a procedure:
trace_something('A text to be saved somewhere');
Trace_something is 'pragma autonomous transaction' procedure that does the following steps:
1. See, if any tracing methods (for example file / db table) are enabled
2. For every enabled method use that method to save parameter somewhere.
3. If it was saved to DB, commit.
A problem occurs when building the actual string to be traced might take noticable amount of time, and we wouldn't want to have to spend it, if tracing isn't even enabled in the first place.
The objective would be, in pseudocode:
procedure lazily_trace_something(some_text lazily_eval_type) {
if do_i_have_to_trace() = TRUE then
trace_something(evaluate(some_text));
else
NULL; -- in which case, some_text doesn't get evaluated
end if;
}
/*
*/
lazily_trace_something(first_50_paragraphs_of_lorem_ipsum(a_rowtype_variable));
Is it possible to be done in plsql?
Lazy evaluation can be (partially) implemented using ref cursors, conditional compilation, or execute immediate. The ANYDATA type can be used to pass generic data.
Ref Cursor
Ref cursors can be opened with a static SQL statement, passed as arguments, and will not execute until needed.
While this literally answers your question about lazy evaluation I'm not sure if it's truly practical. This isn't the intended use of ref cursors. And it may not be convenient to have to add SQL to everything.
First, to prove that the slow function is running, create a function that simply sleeps for a few seconds:
grant execute on sys.dbms_lock to <your_user>;
create or replace function sleep(seconds number) return number is
begin
dbms_lock.sleep(seconds);
return 1;
end;
/
Create a function to determine whether evaltuation is necessary:
create or replace function do_i_have_to_trace return boolean is
begin
return true;
end;
/
This function may perform the work by executing the SQL statement. The SQL statement must return something, even though you may not want a return value.
create or replace procedure trace_something(p_cursor sys_refcursor) is
v_dummy varchar2(1);
begin
if do_i_have_to_trace then
fetch p_cursor into v_dummy;
end if;
end;
/
Now create the procedure that will always call trace but will not necessarily spend time evaluating the arguments.
create or replace procedure lazily_trace_something(some_number in number) is
v_cursor sys_refcursor;
begin
open v_cursor for select sleep(some_number) from dual;
trace_something(v_cursor);
end;
/
By default it's doing the work and is slow:
--Takes 2 seconds to run:
begin
lazily_trace_something(2);
end;
/
But when you change DO_I_HAVE_TO_TRACE to return false the procedure is fast, even though it's passing a slow argument.
create or replace function do_i_have_to_trace return boolean is
begin
return false;
end;
/
--Runs in 0 seconds.
begin
lazily_trace_something(2);
end;
/
Other Options
Conditional compilation is more traditionally used to enable or disable instrumentation. For example:
create or replace package constants is
c_is_trace_enabled constant boolean := false;
end;
/
declare
v_dummy number;
begin
$if constants.c_is_trace_enabled $then
v_dummy := sleep(1);
This line of code does not even need to be valid!
(Until you change the constant anyway)
$else
null;
$end
end;
/
You may also want to re-consider dynamic SQL. Programming style and some syntactic sugar can make a big difference here. In short, the alternative quote syntax and simple templates can make dynamic SQL much more readable. For more details see my post here.
Passing Generic Data
The ANY types can be use to store and pass any imaginable data type. Unfortunately there's no native data type for each row type. You'll need to create a TYPE for each table. Those custom types are very simple so that step can be automated if necessary.
create table some_table(a number, b number);
create or replace type some_table_type is object(a number, b number);
declare
a_rowtype_variable some_table_type;
v_anydata anydata;
v_cursor sys_refcursor;
begin
a_rowtype_variable := some_table_type(1,2);
v_anydata := anydata.ConvertObject(a_rowtype_variable);
open v_cursor for select v_anydata from dual;
trace_something(v_cursor);
end;
/

Oracle equivalent of stored procedure that returns an inline table?

Example in T-SQL (SQL Server - taken from here):
CREATE PROC proc_authors
#au_lname VARCHAR(40)
AS
SELECT
au_id, au_fname, au_lname, city, state
FROM authors
WHERE au_lname = #au_lname
go
Is it possible in Oracle to create a stored procedure that returns an inline table (without declaring a type - like the above)? If not, what would be the closest alternative? i.e. declare inline type, then use it. The idea is to minimize number of DB permissions that are granted.
Please include sample code as part of your answer.
Reasoning behind using stored procedure vs function - we have legacy software that can only execute stored procedures, or raw queries. It appears that only stored procedures in there have support for parameterized execution, which is what we are after.
try this with ref cursor
PROCEDURE proc_get_tada(ip_user IN VARCHAR2,
op_error_code OUT NUMBER,
op_cursor OUT SYS_REFCURSOR,) AS
BEGIN
OPEN op_cursor FOR
SELECT * FROM your_table yt where yt.user = ip_user;
EXCEPTION
WHEN OTHERS THEN
op_error_code := -1;
END proc_get_tada;
you will get collection of all data from you table you can iterate in java or calling program.
Maybe you are searching for something like this:
create table author
(
au_id number,
au_name varchar2(100)
);
insert into author (au_id, au_name) values(1, 'ME');
create or replace function getAuthor(auName varchar2)
return author%rowtype
is
retval author%rowtype;
begin
select * into retval from author where au_name=auName;
return retval;
end;
declare
auth author%rowtype;
begin
auth := getAuthor('ME');
dbms_output.put_line(auth.au_id);
end;

How to execute an Oracle function that returns a sys_refcursor using NHibernate?

This is the function:
FUNCTION GET_ALL(P_USER_ID IN VARCHAR2) RETURN SYS_REFCURSOR IS
C SYS_REFCURSOR;
BEGIN
OPEN C
FOR 'SELECT * FROM XYZ WHERE USER_ID = :P_USER_ID'
USING P_USER_ID;
RETURN C;
END;
I'm trying to call this function using NHibernate, like this:
Session
.CreateSQLQuery("BEGIN ? = PKG.GET_ALL(:P_USER_ID); END;")
.SetString("P_USER_ID", "SOMEONE")
.List<XYZ>();
Any code, tips or smoke signs are welcome.
PS: I'm using NHibernate 3.3.0.GA
From the official docs:
For Oracle the following rules apply:
A function must return a result set. The first parameter of a
procedure must be an OUT that returns a result set. This is done by
using a SYS_REFCURSOR type in Oracle 9 or 10. In Oracle you need to
define a REF CURSOR type, see Oracle literature.
There are working tests with full mapping and stored procedure code at https://github.com/nhibernate/nhibernate-core/tree/master/src/NHibernate.Test/SqlTest/Custom/Oracle

Empty RELIES_ON for RESULT_CACHE

I have a query inside the function with RESULT_CACHE.
So when the table is changed - my cache is invalidated and function is executed again.
What I want is to implement the function that depends only on input parameters, and doesn't depend on any implicit dependencies (like tables, etc).
Is it possible (without dynamic sql)?
a function that depends only on its parameters can be declared DETERMINISTIC. The results of this function will be cached in some cases. This thread on the OTN forums shows how deterministic function results get cached inside SQL statements.
As of 10gR2, the function results don't get cached across SQL statements nor do they get cached in PL/SQL. Still, this cache feature can be useful if you call a function in a SELECT where it might get called lots of time.
I don't have a 11gR2 instance available right now, so I can't test the RESULT_CACHE feature, but have you considered delaring your function relying on a fixed dummy table (a table that never gets updated for instance)?
The correct answer is NO.
A solution in cases where things like result caches and materialized views won't work because of invalidations or too much overhead is the Oracle In-Memory Database Cache option. See result caches ..... what about heavily modified data It's a real smart option, not cheap.
If you use a database link it is possible to create a function result cache that will read from a table when a parameter changes but will not be invalidated when the table changes.
Obviously there are some issues with this approach; performance (even for a self-link), maintenance, the function may return the wrong result, everybody hates database links, etc.
Note that RELIES_ON is deprecated in 11gR2. Dependencies are automatically determined at run-time, even dynamic SQL wouldn't help you here. But apparently this dependency tracking doesn't work over database links.
The script below demonstrates how this works. Remove "#myself" from the function to see how it normally works. Some of the code is based on this great article.
--For testing, create a package that will hold a counter.
create or replace package counter is
procedure reset;
procedure increment;
function get_counter return number;
end;
/
create or replace package body counter as
v_counter number := 0;
procedure reset is begin v_counter := 0; end;
procedure increment is begin v_counter := v_counter + 1; end;
function get_counter return number is begin return v_counter; end;
end;
/
--Create database link
create database link myself connect to <username> identified by "<password>"
using '<connect string>';
drop table test purge;
create table test(a number primary key, b varchar2(100));
insert into test values(1, 'old value1');
insert into test values(2, 'old value2');
commit;
--Cached function that references a table and keeps track of the number of executions.
drop function test_cache;
create or replace function test_cache(p_a number) return varchar2 result_cache is
v_result varchar2(100);
begin
counter.increment;
select b into v_result from test#myself where a = p_a;
return v_result;
end;
/
--Reset
begin
counter.reset;
end;
/
--Start with 0 calls
select counter.get_counter from dual;
--First result is "value 1", is only called once no matter how many times it runs.
select test_cache(1) from dual;
select test_cache(1) from dual;
select test_cache(1) from dual;
select counter.get_counter from dual;
--Call for another parameter, counter only increments by 1.
select test_cache(2) from dual;
select test_cache(2) from dual;
select test_cache(2) from dual;
select counter.get_counter from dual;
--Now change the table. This normally would invalidate the cache.
update test set b = 'new value1' where a = 1;
update test set b = 'new value2' where a = 2;
commit;
--Table was changed, but old values are still used. Counter was not incremented.
select test_cache(1) from dual;
select test_cache(2) from dual;
select counter.get_counter from dual;
--The function is not dependent on the table.
SELECT ro.id AS result_cache_id
, ro.name AS result_name
, do.object_name
FROM v$result_cache_objects ro
, v$result_cache_dependency rd
, dba_objects do
WHERE ro.id = rd.result_id
AND rd.object_no = do.object_id;
Two options:
Don't query any table.
Implement your own cache - wrap the function in a package, and store the query results in a PL/SQL table in memory. The downside to this approach, however, is that the cache only works within a single session. Each session will maintain its own cache.

Cursor design and refactoring question

I have many cursors that all return rows with the same fields: a numeric ID field and an XMLType field. Every time I access one of these cursors (each cursor has now got its own function for access), I go through the same pattern:
--query behind cursor is designed to no more than one row.
for rec in c_someCursor(in_searchKey => local_search_key_value) loop
v_id := rec.ID
v_someXMLVar := rec.XMLDataField
end loop;
if v_someXMLVar is null then
/* A bunch of mostly-standard error handling and logging goes here */
end if;
exception
/* all cursor access functions have the same error-handling */
end;
As the pattern became more obvious, it made sense to centralize it in a single function:
function fn_standardCursorAccess(in_cursor in t_xmlCursorType, in_alt in XMLType) return XMLType is
v_XMLData XMLType;
begin
dbms_application_info.set_module(module_name => $$PLSQL_UNIT, action_name => 'fn_standardCursorAccess');
loop
fetch in_cursor
into v_XMLData;
exit when in_cursor%notfound;
end loop;
/*some additional standard processing goes here*/
return v_XML;
exception
/*standard exception handling happens here*/
end;
The problem I've run into is in calling this function. I now have to call it like this:
open v_curs for select /*blah blah blah*/ where key_field = x and /*...*/;
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
What I'd like to do is call it like this:
open v_curs for c_getSomeData(x);
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
...reason being to minimize the amount of changes to my code (I don't want to have to cut/paste all these cursors to the functions that depend on them, and in the case where multiple functions depend on the same cursor, I'll have to wrap that in a new function).
Unfortunately, this does not work, Oracle returns an error saying
Error: PLS-00222: no function with name 'C_GETSOMEDATA' exists in this scope
Is what I'm trying to do even possible?
(Oracle version is 10.2)
EDIT:
I think a better way to describe what I'm doing is pass a reference to an explicit cursor to a function that will perform some common routines on the data returned by the cursor.
It appears that I cannot use an open-for statement with an explcit cursor, is there any other way to get a reference to an explicit cursor so I can pass that reference to a function? Maybe there is some other way I could approach this problem?
EDIT:
Copying and pasting from my earlier reply to R Van Rijn's reply:
I tried declaring the cursor in the package specification, and referencing it with the package name: open v_curs for PKG.c_getSomeData(x);... This gives me a new error, saying that PKG.c_getSomeData must be a function or array to be used that way.
UPDATE:
I spoke to our DBA here, he says it is not possible to have a ref cursor point to an explicit cursor. It looks like I can't do this after all. Bummer. :(
concerning the Error PLS-00222:
An identifier being referenced as a function 'c_getSomeData' was not declared or actually represents another object (for example, it might have been declared as a procedure).
Check the spelling and declaration of the identifier. Also confirm that the declaration is placed correctly in the block structure
This means you must create a function that actually returns some value(s).
Does this test script and output represent what you are trying to do? Instead of open v_curs for c_getSomeData(x); I'm setting the cursor variable = to the output from the function.
Our Test Data:
set serveroutput on
--create demo table
drop table company;
create table company
(
id number not null,
name varchar2(40)
);
insert into company (id, name) values (1, 'Test 1 Company');
insert into company (id, name) values (2, 'Test 2 Company');
insert into company (id, name) values (3, 'Test 3 Company');
commit;
Create Packages
create or replace package test_pkg as
type cursor_type is ref cursor;
function c_getSomeData(v_companyID number) return cursor_type;
end test_pkg;
/
create or replace package body test_pkg as
function c_getSomeData(v_companyID number) return cursor_type
is
v_cursor cursor_type;
begin
open v_cursor for
select id,
name
from company
where id = v_companyID;
return v_cursor;
end c_getSomeData;
end test_pkg;
/
Run Our Procedure
declare
c test_pkg.cursor_type;
v_id company.id%type;
v_name company.name%type;
begin
c := test_pkg.c_getSomeData(1);
loop
fetch c
into v_id, v_name;
exit when c%notfound;
dbms_output.put_line(v_id || ' | ' || v_name);
end loop;
close c;
end;
/
1 | Test 1 Company
PL/SQL procedure successfully completed.
I confess to finding your requirements a trifle hard to divine. You have posted a lot of code, but as I suggested in my comment, not the parts which would illuminate the problem. So possibly the following is way off-beam. But it is an interesting issue.
The following code shows how we can define a common, geneneric REF CURSOR, populate it with specific data from different queries, and then process them in a standardised fashion. Again, I apologise if this does not fit your business logic; if such is the case, please edit your question to explain where I have made a bloomer..
Here is the generic ref cursor. ...
create or replace package type_def is
type xml_rec is record (id number, payload xmltype);
type xml_cur is ref cursor return xml_rec;
end type_def;
/
and here is the standatd processor
create or replace procedure print_xml_cur
( p_cur in type_def.xml_cur )
is
lrec type_def.xml_rec;
begin
loop
fetch p_cur into lrec;
exit when p_cur%notfound;
dbms_output.put_line('ID='||lrec.id);
dbms_output.put_line('xml='||lrec.payload.getClobVal());
end loop;
close p_cur;
end print_xml_cur;
/
Two procedures which return the standard cursor with different data....
create or replace function get_emp_xml
( p_id in emp.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(ename))
from emp
where deptno = p_id
group by deptno;
return return_value;
end get_emp_xml;
/
create or replace function get_dept_xml
( p_id in dept.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(dname))
from dept
where deptno = p_id
group by deptno;
return return_value;
end get_dept_xml;
/
Now let's put it all together ....
SQL> set serveroutput on size unlimited
SQL>
SQL> exec print_xml_cur(get_emp_xml(40))
ID=40
xml=<?xml
version="1.0"?>
<ROWSET>
<ENAME>GADGET</ENAME>
<ENAME>KISHORE</ENAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL> exec print_xml_cur(get_dept_xml(20))
ID=20
xml=<?xml version="1.0"?>
<ROWSET>
<DNAME>RESEARCH</DNAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL>
OK, so the short answer from Oracle is: "can't be done!"
The short answer from me is: "Yeah - like Oracle is gonna stop me! So yes you can....but you need to be sneaky ... oh yes, and there is a 'but' or two....in fact...ugh!"
So, how can you pass your explicit cursor by reference? By nesting it into another cursor using the CURSOR() construct!
e.g.)
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor
is
test_Cur sys_refcursor;
cursor gettest is
select CURSOR( -pass our actual query back as a nested CURSOR type
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID,
ELECTORAL_DISTRICT_ID,
ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = ed_id)
from dual;
begin
open gettest;
fetch gettest into test_Cur;
return test_Cur;
end;
end;
/
So what is the problem with this solution? It has a leak! The outer gettest cursor is never closed, because we don't close it and the client will only close the reference to the nested cursor that was selected for them - not the main cursor. And we can't close it automatically because closign the parent would force closing the nested cursor that you have returned by reference - and it is entirely likely that the client has not done using it.
So we have to leave a cursor open in order to return the nested cursor.
And if the user tried calling get_Cursor again with a new value of ed_id, they would discover that the session persistence in the package meant that the cursor handle is still in use and an error would be raised.
Now, we could fix that by first checking and closing the explicit cursor:
if gettest%isopen then
close gettest;
end if;
open gettest;
fetch gettest into test_Cur;
return test_Cur;
But still - what if the user never calls this again? How long 'til Oracle garbage-collects the cursor? And how many users running how many sessions calling how many functions that use this construct will be leaving cursors open after they are done with them? Better count on a huuuuuge overhead to leave all those open cursors layin' about!
No, you would need to have the users do a callback to explicitely close it or you would be clogging the database. But doing this would require changing the scope of the explicit cursor so that both functions can access it: So we need to make it at package scope, not function scope
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
function close_cursor return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
cursor l_gettest(p_ed_id in number) is
select CURSOR(
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID, ELECTORAL_DISTRICT_ID, ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = p_ed_id)
from dual;
function get_cursor(ed_id number) return sys_refcursor
is
l_get_Cursor sys_refcursor;
begin
open l_gettest (ed_id);
fetch l_gettest into l_get_Cursor;
return l_get_cursor;
end;
function close_cursor return sys_refcursor
is
begin
if l_gettest%isopen then
close l_gettest;
end if;
return pkg_common.generic_success_cursor;
end;
end;
/
OK, plugged the leak. Except it cost us a network round trip instead of the hard parse,...oh wait - and also except embedding a bind variable into an explicit cursor declared at this level is probably going to cause scoping issues of its own which was the reason we wanted to do this in the first place!
Oh, and in a session-pooling environment can two users step on each other's cursors? IF they aren't very carefull about doing an open-fetch-close before returning the session to the pool - we could wind up with some really interesting (and impossible to debug) results!
And how much do you trust the maintainers of the client code to be extra-diligent on this? YEah - me too.
So the short answer is: Yes, with a bit of sneakiness it could be done despite Oracle saying it can't.
The better answer is: But please don't! The extra round trip and potential for memory leaks and client code errors causing data problems makes this a very scary proposition.
It appears that what I wanted to do (have an open-for statement reference an existing explicit cursor) is simply not allowed in Oracle. :(

Resources