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

◇◆丶佛笑我妖孽 提交于 2019-12-05 23:51:31

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;
/
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!