600 likes | 618 Views
Scott Wesley Systems Consultant offers customised Oracle training workshops and consulting services to raise the bar in PL/SQL development. Improve your skills with creative conditional compilation and maximize performance and readability.
E N D
SAGE Computing Services Customised Oracle Training Workshops and Consulting Creative Conditional Compilation … and “raising the bar” with your PL/SQL Scott Wesley Systems Consultant
The example everybody’s seen… FUNCTION qty_booked(p_resource IN VARCHAR2 ,p_date IN DATE) RETURN NUMBER $IF dbms_db_version.ver_le_10 $THEN $ELSE RESULT_CACHE $END IS li_total PLS_INTEGER := 0; BEGIN SELECT SUM(b.qty) INTO li_total FROM bookings b, events e WHERE p_date BETWEEN e.start_date AND e.end_date AND b.resource = p_resource; RETURN li_total END qty_booked;
PL/SQL User’s Guide & Reference 10g Release 2 • Fundamentals of the PL/SQL Language • Conditional Compilation
Availability • 11g Release 1 • 10g Release 2 • Enabled out of the box • 10.1.0.4 – Once patched, enabled by default • Disable using “_parameter” • 9.2.0.6 – Once patched, disabled by default • Enable using “_parameter”
Catch 22 INDICES OF
Catch 22 INDICES OF Conditional Compilation Patch
Performance Readability Facilitates removal of unnecessary code at compile time It's cool! Testing Accuracy
Semantics Selection Directives $IF boolean_static_expression $THEN text [ $ELSIF boolean_static_expression $THEN text ] [ $ELSE text ] $END Inquiry Directives DBMS_OUTPUT.PUT_LINE($$PLSQL_LINE); ALTER SESSION SET PLSQL_CCFLAGS='max_sentence:100'; IF sentence > $$max_sentence THEN Error Directives $IF $$PLSQL_OPTIMIZE_LEVEL != 2 $THEN $ERROR 'intensive_program must be compiled with maximum optimisation' $END $END
<< anon >> BEGIN DBMS_OUTPUT.PUT_LINE('Unit:'||$$PLSQL_UNIT); DBMS_OUTPUT.PUT_LINE('Line:'||$$PLSQL_LINE); END anon; / Unit: Line:4
> CREATE OR REPLACE PROCEDURE sw_test IS BEGIN DBMS_OUTPUT.PUT_LINE('Unit:'||$$PLSQL_UNIT); DBMS_OUTPUT.PUT_LINE('Line:'||$$PLSQL_LINE); END sw_test; / Procedure created. > exec sw_test Unit:SW_TEST Line:4
ALTER SESSION SET PLSQL_CCFLAGS = 'max_sentence:100'; Session altered.
> BEGIN IF p_sentence < $$max_sentence THEN DBMS_OUTPUT.PUT_LINE('Parole Available'); ELSE DBMS_OUTPUT.PUT_LINE('Life'); END IF; END; / Life
ALTER SYSTEM SET PLSQL_CCFLAGS = 'VARCHAR2_SIZE:100, DEF_APP_ERR:-20001'; DECLARE lc_variable_chr VARCHAR2($$VARCHAR2_SIZE); e_def_app_err EXCEPTION; PRAGMA EXCEPTION_INIT (e_def_app_err, $$DEF_APP_ERR); BEGIN --> rest of your code END anon; /
First Demo: Post-processed Source Demo: cc1.sql cc2.sql
CREATE OR REPLACE PROCEDURE universe_alpha IS BEGIN DBMS_OUTPUT.PUT_LINE('Alpha pi = '||$$my_pi/100); END; CREATE OR REPLACE PROCEDURE universe_gamma IS BEGIN DBMS_OUTPUT.PUT_LINE('Gamma pi = '||$$my_pi/100); END; ALTER SYSTEM SET PLSQL_CCFLAGS = 'MY_PI:314'; CREATE OR REPLACE PROCEDURE universe_oz IS BEGIN DBMS_OUTPUT.PUT_LINE('Oz pi = '||$$my_pi/100); END; ALTER PROCEDURE universe_alpha COMPILE PLSQL_CCFLAGS = 'MY_PI:289' REUSESETTINGS; ALTER PROCEDURE universe_gamma COMPILE PLSQL_CCFLAGS = 'MY_PI:423' REUSESETTINGS; > BEGIN universe_alpha; universe_gamma; universe_oz; END; / Alpha pi = 2.89 Gamma pi = 4.23 Oz pi = 3.14
Second Demo: Directive Usage Demo: cc3.sql cc4.sql cc5.sql
Using new version code today $IF dbms_db_version.ver_le_10 $THEN -- version 10 and earlier code $ELSIF dbms_db_version.ver_le_11 $THEN -- version 11 code $ELSE -- version 12 and later code $END
CREATE OR REPLACE PROCEDURE sw_debug (p_text INVARCHAR2) IS $IF $$sw_debug_on $THEN l_text VARCHAR2(32767); $END BEGIN $IF $$sw_debug_on $THEN -- Let’s provide debugging info $IF dbms_db_version.ver_le_10_1 $THEN -- We have to truncate for <= 10.1 l_text := SUBSTR(p_text, 1 ,200); $ELSE l_text := p_text; $END DBMS_OUTPUT.PUT_LINE(p_text); $ELSE -- No debugging NULL; $END END sw_debug;
CREATE OR REPLACE PROCEDURE sw_debug (p_text IN VARCHAR2) IS $IF $$sw_debug_on $THEN l_text VARCHAR2(32767); $END BEGIN $IF $$sw_debug_on $THEN -- Let’s provide debugging info $IF dbms_db_version.ver_le_10_1 $THEN -- We have to truncate for <= 10.1 l_text := SUBSTR(p_text, 1 ,200); $ELSE l_text := p_text; $END DBMS_OUTPUT.PUT_LINE(p_text); $ELSE -- No debugging NULL; $END END sw_debug;
CREATE OR REPLACE PROCEDURE sw_debug (p_text IN VARCHAR2) IS $IF $$sw_debug_on $THEN l_text VARCHAR2(32767); $END BEGIN $IF $$sw_debug_on $THEN -- Let’s provide debugging info $IF dbms_db_version.ver_le_10_1 $THEN -- We have to truncate for <= 10.1 l_text := SUBSTR(p_text, 1 ,255); $ELSE l_text := p_text; $END DBMS_OUTPUT.PUT_LINE(p_text); $ELSE -- No debugging NULL; $END END sw_debug;
10g vs 11g result_cache FUNCTION quantity_ordered (p_item_id IN items.item_id%TYPE) RETURNNUMBER $IF dbms_version.ver_le_10$THEN -- nothing $ELSE RESULT_CACHE $END IS BEGIN ...
CREATE OR REPLACE PACKAGE BODY sw_bulk_insert IS PROCEDURE sw_insert (sw_tab IN t_sw_tab) IS BEGIN $IF dbms_db_version.ver_le_9 $THEN DECLARE l_dense t_sw_tab; ln_index PLS_INTEGER := sw_tab.FIRST; BEGIN << dense_loop >> WHILE (l_index IS NOT NULL) LOOP l_dense(l_dense.COUNT + 1) := sw_tab(l_index); l_index := sw_tab.NEXT(l_index); ENDLOOP dense_loop; FORALL i IN 1..l_dense.COUNT INSERTINTO sw_table VALUES l_dense(i); END; $ELSE FORALL i ININDICESOF sw_tab INSERTINTO sw_table VALUES sw_tab(i); $END END sw_insert; END sw_bulk_insert;
CREATE OR REPLACE PACKAGE BODY sw_bulk_insert IS PROCEDURE sw_insert (sw_tab IN t_sw_tab) IS BEGIN $IF dbms_db_version.ver_le_9 $THEN DECLARE l_dense t_sw_tab; ln_index PLS_INTEGER := sw_tab.FIRST; BEGIN << dense_loop >> WHILE (l_index IS NOT NULL) LOOP l_dense(l_dense.COUNT + 1) := sw_tab(l_index); l_index := sw_tab.NEXT(l_index); ENDLOOP dense_loop; FORALL i IN 1..l_dense.COUNT INSERTINTO sw_table VALUES l_dense(i); END; $ELSE FORALL i IN INDICES OF sw_tab INSERT INTO sw_table VALUES sw_tab(i); $END END sw_insert; END sw_bulk_insert;
CREATE OR REPLACE PACKAGE BODY sw_bulk_insert IS PROCEDURE sw_insert (sw_tab IN t_sw_tab) IS BEGIN $IF dbms_db_version.ver_le_9 $THEN DECLARE l_dense t_sw_tab; ln_index PLS_INTEGER := sw_tab.FIRST; BEGIN << dense_loop >> WHILE (l_index IS NOT NULL) LOOP l_dense(l_dense.COUNT + 1) := sw_tab(l_index); l_index := sw_tab.NEXT(l_index); END LOOP dense_loop; FORALL i IN 1..l_dense.COUNT INSERT INTO sw_table VALUES l_dense(i); END; $ELSE FORALL i ININDICESOF sw_tab INSERTINTO sw_table VALUES sw_tab(i); $END END sw_insert; END sw_bulk_insert;
CREATE OR REPLACE PACKAGE pkg_debug IS debug_flag CONSTANTBOOLEAN := FALSE; END pkg_debug; / CREATE OR REPLACE PROCEDURE sw_proc IS BEGIN $IF pkg_debug.debug_flag $THEN dbms_output.put_line ('Debugging Details'); $END END sw_proc; /
“Assertions should be used to document logically impossible situations — Testing Aid Development tool In-line Documentation if the ‘impossible’ occurs, then something fundamental is clearly wrong. This is distinct from error handling.” Run-time Cost
“The removal of assertions from production code is almost always done automatically.It usually is done via conditional compilation.” www.answers.com/topic/assert
$IF $$asserting OR CC_assertion.asserting $THEN IF p_param != c_pi*r*r THEN raise_application_error(.. ENDIF; $END -- individual program unit -- entire application
CREATEPACKAGEBODY universe IS -- Private PROCEDURE orbit IS .. END; -- Public PROCEDURE create_sun IS .. END; PROCEDURE create_planets IS .. END; -- Testers PROCEDURE test_orbit IS BEGIN $IF $$testing $THEN orbit; $ELSE RAISE program_error; $END END test_orbit; END universe; CREATEPACKAGE universe IS PROCEDURE create_sun; PROCEDURE create_planets; -- CC test procedure PROCEDURE test_orbit; END universe;
CREATE PACKAGE BODY universe IS -- Private PROCEDURE orbit IS .. END; -- Public PROCEDURE create_sun IS .. END; PROCEDURE create_planets IS .. END; -- Test sequence PROCEDURE test_run IS BEGIN $IF $$testing $THEN create_sun; create_planets; orbit; $ELSE RAISE program_error; $END END test_run; END universe; CREATEPACKAGE universe IS PROCEDURE create_sun; PROCEDURE create_planets; -- CC test sequence PROCEDURE test_run; END universe;
FUNCTION get_emp(p_emp_id IN emp.emp_id%TYPE) RETURN t_emp IS l_emp t_emp; BEGIN $IF $$mock_emp $THEN l_emp.emp_name := 'Scott'; .. RETURN l_emp; $ELSE SELECT * FROM emp INTO l_emp WHERE emp_id = p_emp_id; RETURN l_emp; $END END get_emp;
PROCEDURE xyz IS $IF $$alternative = 1 $THEN -- varray declaration $ELSIF $$alternative = 2 $THEN -- nested table declaration $END BEGIN $IF $$alternative = 1 $THEN -- simple varray solution $ELSIF $$alternative = 2 $THEN -- elegant nested table solution $END END xyz; PROCEDURE xyz IS $IF $$alternative = 1 $THEN -- varray declaration $ELSIF $$alternative = 2 $THEN -- nested table declaration $END BEGIN $IF $$alternative = 1 $THEN -- simple varray solution $ELSIF $$alternative = 2 $THEN -- elegant nested table solution $END END xyz; $IF $$alternative = 1 $THEN -- first verbose solution that came to mind $ELSIF $$alternative = 2 $THEN -- some crazy idea you came up with at 3am you need to try out $END
PACKAGE BODY core IS PROCEDURE execute_component(p_choice INVARCHAR2) IS BEGIN CASE p_choice -- Base is always installed. WHEN 'base' THEN base.main(); $IF CC_licence.cheap_installed $THEN WHEN 'cheap' THEN cheap.main(); $END ... $IF CC_licence.pricey_installed $THEN WHEN 'pricey' THEN pricey.main(); $END ENDCASE; EXCEPTIONWHEN case_not_found THEN dbms_output.put_line('Component '||p_choice||' is not installed.'); END execute_component; END core;
$IF $$PLSQL_OPTIMIZE_LEVEL != 2 $THEN $ERROR 'intensive_program must be compiled with maximum optimisation' $END $END
BEGIN ... /* * * Note to self: Must remember to finish this bit * */ ... END;