Seit Interactive Grids mit der Version 5.1 eingeführt wurden, erfreut sich dieses Feature berechtigterweise einer sehr großen Beliebtheit – es gibt sehr wahrscheinlich kaum eine produktive APEX-Anwendung, die auf die Verwendung von Interactive Grids verzichtet. Dabei lassen sich die Verhaltensweisen von Interactive Grids mit eigenem DML- und Locking-Code, durch Validierungen und auch Dynamic Actions in vielfältiger Weise mit eigenem PL/SQL-Code nachschärfen.
Dabei wird praktisch immer auf die Spaltenwerte des Interactive Grids mit Hilfe der Bindevariablen-Notation (Doppelpunkt) Bezug genommen. Ein typischer eigener DML-Code für ein Interactive Grids könnte so aussehen:
case :APEX$ROW_STATUS
when 'C' then
insert into emp ( empno, ename, deptno )
values ( :EMPNO, :ENAME, :DEPTNO )
returning empno into :EMPNO;
when 'U' then
update emp
set ename = :ENAME,
deptno = :DEPTNO
where empno = :EMPNO;
when 'D' then
delete emp
where empno = :EMPNO;
end case;
Es gibt zwar die Möglichkeit, im PL/SQL-Code-Editor im Page Designer eine Validierung des Codes durchzuführen. Diese überprüft jedoch nur, ob der Code syntaktisch und semantisch korrekt ist. Ob sich jedoch die Namen der verwendeten Bindevariablen sinnvoll auf Applikation- oder Page-Items bzw. auf Interactive Grids-Spalten beziehen, wird nicht überprüft.
Falls man sich somit bei der Code-Eingabe bei einem Item- bzw. Spaltennamen vertippt, so bleibt dieser Fehler zunächst unbemerkt. Es kommt aber noch schlimmer: Zur Ausführungszeit kommt es ebenfalls zu keinem harten Laufzeit-Fehler – es wird vielmehr nur in der Browser-Konsole und im Debug-Log ein Eintrag erzeugt, dass die ID des falsch getippten Items nicht gefunden werden konnte. Der Code selbst wird aber ausgeführt, und für die „falsche“ Bindevariable wird von APEX der Wert NULL gebunden! Dies kann lange unbemerkt bleiben und zu Datenkorruption führen. Darüber hinaus sind solche Fehler schwer zu analysieren.
Umso lohnender wäre deshalb die Ausführung des Advisors, um solche Fehler frühzeitig zu identifizieren. Leider erweist sich der Advisor jedoch bis APEX-Version 23.1 dafür als wenig hilfreich, da jede Referenz auf eine Interactive Grid-Spalte als ungültig gemeldet wird.
Dazu ein einfaches Beispiel:
Obwohl alle verwendeten Spalten-Referenzen im PL/SQL-Code existieren, meldet der Advisor fälschlicherweise alle Referenzen als ungültig. Die diesbezüglichen Prüfungen des Advisors sind also schlicht unbrauchbar. Bei einer größeren Applikation, die viele Seiten mit Interactive Grids mit eigenem Code verwendet, führen diese vielen Falschmeldungen des Advisor außerdem dazu, dass der gesamte Bericht unübersichtlich und schwerer auswertbar ist.
Ein großer Vorteil von APEX besteht darin, dass all die Metadaten, die unsere Applikationen ausmachen, mit Hilfe der APEX-Dictionary-Views durch SQL und PL/SQL ausgewertet werden können. Dies ermöglicht die Entwicklung eines PL/SQL-Skripts, das fehlerhafte Bindevariablen-Referenzen im Zusammenhang mit Interactive Grids identifizieren und melden kann – am Ende dieses Beitrags finden Sie das komplette Skript zur Ausführung im SQL*Plus oder SQL Developer.
Das Skript arbeitet folgendermaßen:
1. Am Anfang wird über den Wert der Konstante C_APP_ID festgelegt, welche Applikation geprüft werden soll.
2. Falls gewünscht ist, dass alle geprüften Referenzen ausgegeben werden (und nicht nur fehlerhafte), so kann die Konstante C_VERBOSE auf TRUE gesetzt werden.
3. Der Cursor GET_CODE holt aus APEX-Dictionary-Views folgende Codefragmente, die sich auf Interactive Grids beziehen:
Falls Code aus anderen Views zusätzlich geprüft werden soll, so kann die Abfrage des Cursors leicht um einen weiteren UNION ALL-Abschnitt erweitert werden.
4. Mit Hilfe der in APEX eingebauten Funktion WWV_FLOW_UTILITIES.GET_BINDS wird für jedes Code-Fragment ermittelt, welche Bindevariablen darin enthalten sind. Um die Vereinigungsmenge aus allen gefundenen Bindevariablen bilden zu können (mit MULTISET UNION), müssen die Funktionsergebnisse vorher noch mit Hilfe der Hilfsfunktion TO_TABLE in Nested Tables umgewandelt werden.
5. Für jede verwendete Bindevariable wird nun geprüft,
6. Falls keine der drei Möglichkeiten für eine gültige und sinnvolle Referenz vorliegt, wird per DBMS_OUTPUT.PUT_LINE eine entsprechende Meldung ausgegeben.
Die Ausführungszeit des Skripts beträgt im Regelfall auch bei größeren Applikationen nur wenige Sekunden.
Mit Hilfe des Skripts lassen sich Bindevariable-Referenzen von Interactive Grids – in Ergänzung zu den Prüfungen im Advisor – einfach überprüfen und Fehler leicht beheben. Das Skript lässt sich auch problemlos erweitern, um andere, zusätzliche Prüfungen und Analysen durchzuführen. Technisch ohne Weiteres machbar wäre z. B. die Integration des Skript-Codes in einer Pipelined Table-Funktion, die die Ergebnisse als Nested Table zurückgibt, und die Darstellung davon in einem APEX-Bericht.
Hier das komplette Skript für Copy/Paste
set serveroutput on
declare
c_app_id constant number := XXXXX /* Replace with your app id */;
--
c_verbose constant boolean := false;
--
l_binds apex_t_varchar2;
l_bind varchar2(4000 byte);
l_refs pls_integer := 0;
--
cursor get_code is
select application_id, page_id,
region_id, 'VALIDATION' comp_type,
validation_name comp_name,
validation_expression1 code1,
validation_expression2 code2,
condition_expression1 code3,
null code4
from apex_application_page_val
where application_id = c_app_id
and region_id is not null
union all
select application_id, page_id,
region_id, 'PROCESS' comp_type,
process_name comp_name,
cast(process_source as varchar2(4000 byte)) code1,
attribute_04 as code2,
condition_expression1 code3,
null code4
from apex_application_page_proc
where application_id = c_app_id
and region_id is not null
union all
select da.application_id, da.page_id,
da.when_region_id, 'DYNAMIC ACTION' comp_type,
da.dynamic_action_name comp_name,
act.attribute_01 code1,
act.attribute_03 code2,
act.attribute_04 code3,
act.attribute_06 code4
from apex_application_page_da da
join apex_application_page_da_acts act
on(act.dynamic_action_id = da.dynamic_action_id)
where da.application_id = c_app_id
and da.when_region_id is not null
order by application_id, page_id;
--
function to_table(p_array dbms_sql.varchar2_table) return apex_t_varchar2
as
l_tab apex_t_varchar2 := apex_t_varchar2();
begin
for i in 1 .. p_array.count loop
if p_array(i) is not null
then
l_tab.extend(1);
l_tab(l_tab.last) := p_array(i);
end if;
end loop;
return l_tab;
end;
--
function exists_item(p_application_id number, p_item varchar2)
return boolean
as
l_count pls_integer;
begin
select count(*)
into l_count
from (select 1 from apex_application_page_items
where application_id = p_application_id
and item_name = p_item
union all
select 1 from apex_application_items
where application_id = p_application_id
and item_name = p_item)
where rownum = 1;
--
return(l_count > 0);
end;
--
function exists_column(p_application_id number, p_region_id number,
p_column varchar2)
return boolean
as
l_count pls_integer;
begin
select count(*)
into l_count
from (select 1 from apex_appl_page_ig_columns
where application_id = p_application_id
and region_id = p_region_id
and name = p_column
union all
select 1 from apex_application_page_rpt_cols
where application_id = p_application_id
and region_id = p_region_id
and column_alias = p_column)
where rownum = 1;
--
return(l_count > 0);
end;
--
function is_builtin(p_bind varchar2) return boolean as
begin
return l_bind like 'APEX$ROW%' or l_bind like 'APP\_%' escape '\' or
l_bind like 'JET\_%' escape '\' or l_bind like 'THEME\_%' escape '\' or
l_bind like 'WORKSPACE\_%' escape '\' or
l_bind in('REQUEST', 'DEBUG', 'IMAGE_PREFIX', 'APEX_FILES');
end;
begin
for l_code in get_code
loop
l_binds := to_table(wwv_flow_utilities.get_binds(l_code.code1)) multiset union distinct
to_table(wwv_flow_utilities.get_binds(l_code.code2)) multiset union distinct
to_table(wwv_flow_utilities.get_binds(l_code.code3)) multiset union distinct
to_table(wwv_flow_utilities.get_binds(l_code.code4));
for i in 1 .. l_binds.count
loop
l_bind := trim(leading ':' from l_binds(i));
--
l_refs := l_refs + 1;
--
if not exists_item(l_code.application_id, l_bind) and
not exists_column(l_code.application_id, l_code.region_id, l_bind) and
not is_builtin(l_bind)
then
dbms_output.put_line(apex_string.format(
'PROBLEM: Page Id: %s, Region Id: %s, Type: %s, Object Name: %s, Bind Variable: %s',
l_code.page_id, l_code.region_id, l_code.comp_type, l_code.comp_name, l_bind));
elsif c_verbose
then
dbms_output.put_line(apex_string.format(
'OK: Page Id: %s, Region Id: %s, Type: %s, Object Name: %s, Bind Variable: %s',
l_code.page_id, l_code.region_id, l_code.comp_type, l_code.comp_name, l_bind));
end if;
end loop;
end loop;
dbms_output.put_line('Total references checked: ' || l_refs);
end;
/
In unserer Know-How Datenbank finden Sie mehr als 300 ausführliche Beiträge zu den
Oracle-Themen wie DBA, SQL, PL/SQL, APEX und vielem mehr.
Hier erhalten Sie Antworten auf Ihre Fragen.