How to call a stored function whose name is returned by another stored function in Oracle plsql? - oracle

I tried to make some version management for the salary computing in our payroll application.
I made a stored function for each formula version according to the legislation that applies at any given time, and then I stored the names of these functions in a table, together with the starting validity date of each function.
Then I made a function that retrieves the name (and returns this name as varchar) of the function applicable for a formula at a given time.
In the view that makes all the computing, I tried to call this function to retrieve the name of the function and use this returned name to call the formula function. All this is made in a select instruction.
I'm trying to call a function whose name is returned by the other function, like:
(function1(param1, param2, ...))(paramx, paramy, ...)
but this doesn't work.
Is there any way to use the name returned by function1 to call the function with that name and with input (parameters paramx, paramy, ...) ?

Functions are not first-class objects which can be returned from other functions, and the name of the function contained in a variable is not the actual code which can be executed. However, you can probably do what you're trying to do by using dynamic SQL:
DECLARE
param1 NUMBER;
param2 VARCHAR2(2000);
function_name VARCHAR2(2000);
paramx NUMBER;
paramy VARCHAR2(2000);
plsql_block VARCHAR2(2000);
result NUMBER; -- assumes the function returns a NUMBER
BEGIN
param1 := 123; -- or whatever is appropriate
param2 := 'abc'; -- or whatever is appropriate
function_name := function1(param1, param2);
-- assume that function_name now contains 'some_function'
paramx := 456; -- or whatever is appropriate
paramy := 'def'; -- or whatever is appropriate
plsql_block:= 'BEGIN :r := ' || function_name || '(:px, :py); END;';
-- plsql_block should now contain 'BEGIN :r := some_function(:px, :py); END;'
EXECUTE IMMEDIATE plsql_block USING IN OUT result, paramx, paramy;
DBMS_OUTPUT.PUT_LINE('result = ' || result);
END;
Best of luck.

If all the functions you might have to call are known and exist when the master function is compiled, then you can use the variable you have to decide which to call. As a very rough outline:
create function master_salary(p_date date)
return number as
l_function_name all_objects.object_name%type;
begin
l_function_name := choose_function(p_date);
case l_function
when 'function_a' then
return function_a;
when 'function_b' then
return function_b(some_arg);
when 'function_c' then
return function_c(some_arg, another_arg);
else
raise_application_error(-20001, 'Unknown function ' || l_function_name);
end case;
end;
/
This avoids dynamic SQL and lets you have functions with different numbers and/or types of arguments, and it's easier to follow what is happening.
If you're adding more functions on the fly then you probably shouldn't be - at least not outside some kind of source control and release mechanism, which would allow you to maintain the master function in step. You could, as a fall back, have the default case try to execute whatever function name you have dynamically (as Bob Jarvis shows) if it isn't one you're expecting; but you'd need consistent argument numbers and data types, and it potentially opens up a vulnerability if the table you get the function name from could be modified.

Related

how to call an oracle procedure and a function inside another procedure

I have a situation, where I need to do the following.
Step1: Call a procedure with the given input values and get the 2 output values.
step2: Call the function with input parameters along with one of the output value from Step1(procedure call)
Step3: Extract the output value from the return value of Step2.
Please help, how to handle this situation.
Thanks
A very basic example, with made up variable names, data types (all numbers for simplicity) and procedure/function names and signatures:
create or replace procedure wrapper_proc as
-- define local variables; use appropriate data types!
l_input_1 number;
l_input_2 number;
l_output_1 number;
l_output_2 number;
l_result number;
begin
-- Step1: Call a procedure with the given input values and get the 2 output values.
l_input_1 := 42;
l_input_2 := 128;
your_proc (l_input_1, l_input_2, l_output_1, l_output_2);
-- l_output_1 and l_output_2 are not set by that first procedire
-- step2: Call the function with input parameters along with one of the output value from Step1(procedure call)
-- assuming same two original inuts, and one of the procedure outputs...
l_result := your_func (l_input_1, l_input_2, l_output_2);
--Step3: Extract the output value from the return value of Step2.
-- do something unspecified with l_result
dbms_output.put_line('Final result was: ' || l_result);
end;
/
Or if you want to pass the input values into that wrapper procedure:
create or replace procedure wrapper_proc (
-- arguments; use appropriate data types!
p_input_1 number,
p_input_2 number
) as
-- define local variables; use appropriate data types!
l_output_1 number;
l_output_2 number;
l_result number;
begin
-- Step1: Call a procedure with the given input values and get the 2 output values.
your_proc (p_input_1, p_input_2, l_output_1, l_output_2);
-- l_output_1 and l_output_2 are not set by that first procedire
-- step2: Call the function with input parameters along with one of the output value from Step1(procedure call)
-- assuming same two original inuts, and one of the procedure outputs...
l_result := your_func (p_input_1, p_input_2, l_output_2);
--Step3: Extract the output value from the return value of Step2.
-- do something unspecified with l_result
dbms_output.put_line('Final result was: ' || l_result);
end;
/

How to call list of package methods dynamically in PL/SQL

I have a scenario that I want to execute dynamically fetched methods from a cursor with different arguments. Those argument values are replaced (using Get_Parameter_Value___(head_rec_.objkey,parameter_); ) with values in a loop as you can see in the following example.
PROCEDURE Dynamic_exe(
keyvalue_ IN VARCHAR2)
IS
param_str_ VARCHAR2(2000);
temp_param_str_ VARCHAR2(2000);
method_stmt_ VARCHAR2(100);
CURSOR get_method IS
SELECT exe_method
FROM method_tab
BEGIN
param_str_ := Substr(rec_.exe_method,Instr(rec_.exe_method,'(')+1,(Instr(rec_.exe_method,')')-1)-Instr(rec_.exe_method,'('));
temp_param_str_ := param_str_;
method_stmt_ := rec_.exe_method;
WHILE temp_param_str_ IS NOT NULL LOOP
IF (Instr(temp_param_str_,',') > 0 )THEN
parameter_ := trim(Substr(temp_param_str_,1,Instr(temp_param_str_,',')-1));
temp_param_str_ := Substr(temp_param_str_,Instr(temp_param_str_,',')+1);
ELSE
parameter_ := trim(temp_param_str_);
temp_param_str_ := NULL;
END IF;
parameter_value_ := Get_Parameter_Value___(head_rec_.objkey,parameter_);
method_stmt_ := REPLACE(method_stmt_,parameter_,''''||parameter_value_||'''');
END LOOP;
FOR rec_ IN get_method LOOP
EXECUTE IMMEDIATE 'BEGIN '||method_stmt_||'; END;';
END LOOP;
END Dynamic_exe;
This is not safe, SQL injection can be done for this, I need a solution, associated with bind variables, Does anyone have a solution for this?
You can eliminate the possibility of SQL injection by using DBMS_ASSERT.SQL_OBJECT_NAME to protect the method name, and use DBMS_SQL and bind variables to protect the arguments.
DBMS_ASSERT.SQL_OBJECT_NAME throws an error if the value is not the same as an existing object. (Although for packages it only checks that the package name exists, not the procedure name. But the procedure name must still be a realistic name.)
For example, if the package name exists, the function will simply return the name:
SQL> select dbms_assert.SQL_OBJECT_NAME('test_package.test_procedure') name from dual;
NAME
--------------------------------------------------------------------------------
test_package.test_procedure
But any SQL injection shenanigans will raise an exception (which you can catch and handle if necessary):
SQL> select dbms_assert.sql_object_name('; delete from employees;') from dual;
select dbms_assert.sql_object_name('; delete from employees;') from dual
*
ERROR at line 1:
ORA-44002: invalid object name
ORA-06512: at "SYS.DBMS_ASSERT", line 401
Instead of building the entire statement as a string, add :bind_variable_n and DBMS_SQL to run it.
So the final string will look something like this (add the bind variable numbers in the loop):
method_stmt_ := 'begin '||method_name||'(:1, :2); end;';
Executing a dynamic number of bind variables requires DBMS_SQL.BIND_VARIABLE. Switching from native dynamic SQL to DBMS_SQL is going to be annoying, but it will let you pass in the bind variables without any injection concerns.

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;
/

RESULT_CACHE RELIES_ON (NLS_SESSION_PARAMETERS)

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;

oracle stored procedure OUT parameter vs. postgresql pl/pgsql function OUT parameter

I'm trying to port oracle stored procedures (plsql) to a postgresql function (pl/pgsql).
In oracle I can define a stored procedure with IN and OUT parameters.
CREATE OR REPLACE PROCEDURE MY_TEST(foo IN NUMBER,
bar OUT NUMBER)
IS
BEGIN
bar := 1
END
This will store a value of 1 in the variable that is passed to the stored procedure.
I can call it as follows:
DECLARE
outValue NUMBER ;
BEGIN
Exec MY_TEST(10, outValue);
DBMS_OUTPUT.PUT_LINE('Value Returned Is : '||outValue) ;
END ;
In Postgresql (pl/pgsql) I can define a function like this one:
CREATE OR REPLACE FUNCTION MY_TEST(foo IN NUMBER,
bar OUT NUMBER)
BEGIN
bar := 1
END;
$body$
LANGUAGE PLPGSQL;
However I can not use the out parameter the same way as I could in oracle.
In postgresql the OUT parameter defines the return value.
In oracle stored procedures don't have return values, but instead write the output into the variable that is passed in the call
Is there something I overlooked, that would permit me to use pl/pgsql functions in a similar way as the stored procedure is used in the example above?
Any hints are greatly appreciated.
In PostgreSQL, PL/pgSQL or SQL functions take parameter values and return values.
They do not take pointers or references - so that the value of the referenced address could be manipulated.
You could do something like that in theory with a C-language function, where you can pass values by reference. However, de facto, you cannot. The manual warns:
Warning
Never modify the contents of a pass-by-reference input value. If you
do so you are likely to corrupt on-disk data, since the pointer you
are given might point directly into a disk buffer. The sole exception to this rule is explained in Section 35.10.
In short: what you are trying to do is impossible in PostgreSQL.
You don't need an OUT parameter for Postgres:
CREATE OR REPLACE FUNCTION MY_TEST(foo IN integer)
returns integer
as
$body$
BEGIN
return 1;
END;
$body$
LANGUAGE PLPGSQL;
Then use it like this:
DO $body$
DECLARE
result integer;
begin
result := my_test(42);
raise notice 'Return value is: %', result;
end;
$body$
Take a read of this.
http://www.postgresonline.com/journal/archives/129-Use-of-OUT-and-INOUT-Parameters.html
In summary given the following stored function and the anonymous block to test this is one approach that the article will show.
CREATE OR REPLACE FUNCTION fn_plpgsqltestout(param_subject text,
OUT subject_scramble text, OUT subject_char text)
AS
$$
BEGIN
subject_scramble := substring($1, 1,CAST(random()*length($1) As integer));
subject_char := substring($1, 1,1);
END;
$$
LANGUAGE 'plpgsql' VOLATILE;
DO $body$
DECLARE
o_subject_scramble TEXT;
o_subject_char TEXT;
begin
--Option1
SELECT (fn_plpgsqltestout('This is a test subject')).* INTO o_subject_scramble,o_subject_char;
--Option2
SELECT (fn_plpgsqltestout('This is a test subject')).subject_scramble INTO o_subject_scramble;
SELECT (fn_plpgsqltestout('This is a test subject')).subject_char INTO o_subject_char;
--Option3
o_subject_scramble := (fn_plpgsqltestout('This is a test subject')).subject_scramble;
o_subject_char := (fn_plpgsqltestout('This is a test subject')).subject_char;
raise notice 'Return value is: %', o_subject_scramble;
raise notice 'Return value is: %', o_subject_char;
end;
$body$

Resources