Programming

PL/SQL Session State for HTML Forms with HTP & DBMS_SESSION

Elegant PL/SQL solution using Oracle session contexts and DBMS_SESSION for multi-step HTML web forms with HTP. Reduce parameter passing, modularize code, handle arrays in mod_plsql apps for scalable state management.

1 answer 1 view

Elegant PL/SQL Solution for Handling Multiple User Inputs in HTML Web Forms with HTP

I have a PL/SQL web form using the HTP package to display data and capture user input into the database. As the project scales, it requires passing numerous parameters (e.g., choice flags, usernames, and arrays for IDs, colors, ratings) between procedures, making the code cumbersome.

ChatGPT suggested using context variables via OWA_UTIL, but I couldn’t implement it successfully. Is there an elegant way to modularize this, store state across procedure calls, and reduce parameter passing?

Current Implementation Example

Here’s a simplified version of my test_page procedure:

plsql
procedure test_page(
 p_choice in VARCHAR2 default null,
 p_user_name in VARCHAR2 default null,
 a_id in T_ARR default cast(null as T_ARR),
 a_colour in T_ARR default cast(null as T_ARR),
 a_rating in T_ARR default cast(null as T_ARR)
)
as
 refcur SYS_REFCURSOR;
 g_page_link VARCHAR2(4000) default 'my_site/my_schema/my_package';
 l_strsql VARCHAR2(4000);
 l_cnt INT;
 cur_id VARCHAR2(30);
 cur_colour VARCHAR2(300);
begin
 htp.htmlopen;
 htp.headopen;
 htp.title('Test page');
 htp.headclose;
 htp.bodyopen;
 
 htp.print('<h1 align="center">Test page</h1>');
 
 if p_choice is null then
 htp.print('<form action="' || g_page_link || '.test_page" method="post">');
 -- Form for filter selection (user name dropdown, submit button)
 htp.print('<div>');
 htp.print('<table>');
 htp.print('<tr> <th colspan="2">Filter</th></tr>');
 htp.print('<tr> <td>User</td>' ||
 '<td> <select name="p_user_name" size="1"> ' ||
 '<option selected value="Peter">Peter</option>' ||
 '<option value="Tom">Tom</option>' ||
 '</td></tr>');
 htp.print('<tr><td colspan="2"><button type="submit" name="p_choice" value="rate"> Use Filter </button> </td></tr>');
 htp.print('</table>');
 htp.print('</div>');
 htp.print('</form>');
 end if;
 
 if p_choice = 'rate' then
 htp.print('<form action="' || g_page_link || '.test_page" method="post">');
 htp.print('<input type="hidden" name="p_user_name" value="' || p_user_name || '">');
 htp.print('<table><thead><tr>');
 htp.print('<th id="col">Colour</th>');
 htp.print('<th id="rat">Rating</th>');
 htp.print('</tr></thead><tbody>');
 l_strsql :=
 'select 1 as id, ''blue'' as colour from dual union all ' ||
 'select 2 as id, ''red'' as colour from dual union all ' ||
 'select 3 as id, ''green'' as colour from dual';
 l_cnt := 0;
 open refcur for l_strsql;
 loop
 fetch refcur into cur_id, cur_colour;
 exit when refcur%notfound or l_cnt >= 1000;
 l_cnt := l_cnt + 1;
 htp.print('<input type="hidden" name="a_id" value="' || cur_id || '">');
 htp.print('<input type="hidden" name="a_colour" value="' || cur_colour || '">');
 htp.print('<tr>' ||
 '<td axis="sstring" headers="col" >' || cur_colour || '</td>' ||
 '<td axis="number" headers="rat"><input type="text" name="a_rating" value="' || 0 || '"></td>' ||
 '</tr>');
 end loop;
 close refcur;
 htp.print('</tbody></table>');
 htp.print(' <p> <button type="submit" name="p_choice" value="done">Rate now </button></p>');
 htp.print('</form>');
 end if;
 
 if p_choice = 'done' then
 htp.print('<form action="' || g_page_link || '.test_page" method="post">');
 htp.print('<div>');
 htp.print('<table>');
 htp.print('<tr> <th colspan="3">Result</th></tr>');
 htp.print('<tr> <td>User</td> <td value="' || p_user_name || '">' || p_user_name || '</td> <td> &nbsp;</td> </tr>');
 
 for i in a_id.first .. a_id.last loop
 htp.print('<tr> <td value="' || a_id(i) || '">' || a_id(i) || '</td><td value="' || a_colour(i) || '">' || a_colour(i) || '</td> <td value="' || a_rating(i) || '">' || a_rating(i) || '</td></tr> ');
 end loop;
 htp.print('<tr><td colspan="2"><button type="submit" name="p_choice" value=""> Finish rating </button> </td></tr>');
 htp.print('</table>');
 htp.print('</div>');
 htp.print('</form>');
 end if;
 
 htp.bodyclose;
 htp.htmlclose;
exception
 when others then
 htp.print('<h1 align="center">Error</h1>');
 htp.print('<p align="center"> ' || sqlerrm || '</p>');
end;

Key issues:

  • Multi-step form (filter → rate → results) requires passing all data via parameters.
  • Arrays grow large with more data.
  • Hard to maintain as parameters increase.

What are best practices for state management in PL/SQL web apps (e.g., using contexts, sessions, or packages)? Provide a modular example.

Use DBMS_SESSION.SET_CONTEXT and SYS_CONTEXT to elegantly store state like usernames, choice flags, and even serialized arrays in an Oracle session context during multi-step HTML forms with HTP/HTF. This eliminates bulky parameter passing across procedure calls in PL/SQL web apps, keeping your code modular while data persists for the user’s session. Package globals handle transient request data if needed, but session contexts shine for stateless HTTP flows.


Contents


State Management Challenges in PL/SQL Web Forms

Ever built a PL/SQL web form that starts simple but balloons into a parameter nightmare? Your test_page procedure nails it—filter selection leads to ratings, then results, all shuttled via POST params and arrays. HTP spits out the HTML, but HTTP’s stateless nature means every submit reposts everything. Arrays like a_id, a_colour, a_rating? They explode signatures and maintenance.

Why does this hurt? One more field, and you’re refactoring every call. ChatGPT’s OWA_UTIL nudge was close—it’s for CGI env vars—but for true session persistence, Oracle’s got better: oracle session contexts via DBMS_SESSION. No cookies, no temp tables (yet). Just lightweight, secure storage tied to the database session mod_plsql creates per user.

Picture this: user picks “Peter,” hits submit. Instead of passing p_user_name everywhere, stash it once. Next form reads it via SYS_CONTEXT. Ratings? Serialize the array as JSON or delimited string. Boom—modular procedures like show_filter, show_ratings, process_results. No more monolith.


Leveraging Oracle Session Contexts with DBMS_SESSION

Here’s where oracle session magic happens. DBMS_SESSION lets PL/SQL apps store key-value pairs in a namespace that survives across calls in the same oracle session. Perfect for web forms since mod_plsql reuses sessions for the same browser.

Core API from Oracle’s PL/SQL Packages Reference:

  • DBMS_SESSION.SET_CONTEXT('MYAPP', 'USER_NAME', 'Peter');
  • Read anywhere: SYS_CONTEXT('MYAPP', 'USER_NAME');
  • Clear: DBMS_SESSION.CLEAR_CONTEXT('MYAPP', 'USER_NAME'); or CLEAR_ALL_CONTEXT('MYAPP');

Setup’s a one-time admin thing: grant CTXAPP role or create an ACL. But for dev? Just use it. Namespaces like ‘MYAPP’ isolate your app.

In your flow:

  1. Filter submit → set context for p_choice='rate', user_name.
  2. Ratings form → fetch from context, generate table.
  3. Done → process arrays from context, show results.

No params beyond p_choice. Why contexts over globals? They survive commits/rollbacks, secure (VPD integration), and scale to pooled connections. Stack Overflow examples show packages wrapping this for cleanliness.

But arrays? SYS_CONTEXT stores VARCHAR2(4000) max. Serialize 'em.


Package-Level State for Quick Request Handling

Contexts handle long-lived state. For single-request crunching—like parsing POSTed ratings—use package globals. They live for the session too, but reset easier.

From Oracle Web Apps docs, package vars persist as users navigate via mod_plsql. Your T_ARR collections? Stash in a package spec:

plsql
PACKAGE state_pkg AS
 g_user_name VARCHAR2(100);
 g_choice VARCHAR2(20);
 g_ids T_ARR := T_ARR();
 g_colours T_ARR := T_ARR();
 g_ratings T_ARR := T_ARR();
END state_pkg;

On POST: populate from owa_util.get_value or params. Procedures read state_pkg.g_user_name. Clear at end: g_ids.delete;.

Pros? Handles complex types (collections, refs) contexts can’t. Cons? Not sharable across sessions; leaks if not cleared. Hybrid wins: contexts for user ID/choice, packages for arrays during rating.

Oracle-Base notes this shines in connection pools—reset identifiers ensure isolation.

Quick gotcha: declare package in schema owning the procedure. Test with state_pkg.g_user_name := 'Peter';—visible next call.


Modular Refactoring: Step-by-Step Example

Ditch the god-procedure. Split into handle_request(p_choice VARCHAR2). Use one entrypoint, dispatch by choice.

First, init context (call once per session):

plsql
PROCEDURE init_context IS
BEGIN
 DBMS_SESSION.SET_CONTEXT('TESTAPP', 'SESSION_ID', USERENV('SESSIONID'));
 -- Trust level if needed
END;

Main dispatcher:

plsql
PROCEDURE test_page(p_choice VARCHAR2 DEFAULT NULL) IS
BEGIN
 htp.htmlopen; -- Boilerplate...
 OWA_UTIL.mime_header('text/html', TRUE);
 
 IF p_choice IS NULL THEN
 show_filter;
 ELSIF p_choice = 'rate' THEN
 store_filter(owa_util.get_value('p_user_name'));
 show_ratings;
 ELSIF p_choice = 'done' THEN
 process_ratings;
 show_results;
 END IF;
 
 htp.htmlclose;
END;

store_filter:

plsql
PROCEDURE store_filter(p_user VARCHAR2) IS
BEGIN
 DBMS_SESSION.SET_CONTEXT('TESTAPP', 'USER_NAME', p_user);
 DBMS_SESSION.SET_CONTEXT('TESTAPP', 'CHOICE', 'rate');
END;

show_ratings: fetch user, build table like yours, but names match arrays: name="rating_1". No hiddens for IDs/colors—store in context serialized.

Form action stays test_page?p_choice=done.

Processing: loop owa_util.get_value('rating_' || i) into g_ratings.

Elegant? Dead yes. Scales to 10+ steps.


Serializing Arrays and Complex Data

Arrays kill params. Solution: JSON via JSON_ARRAYAGG (12c+), or delimited: '1:blue:5|2:red:3'.

Store:

plsql
DECLARE
 l_data CLOB;
BEGIN
 SELECT LISTAGG(id || ':' || colour || ':' || rating, '|') WITHIN GROUP (ORDER BY id)
 INTO l_data FROM ratings_table; -- Or from POST
 DBMS_SESSION.SET_CONTEXT('TESTAPP', 'RATINGS_DATA', SUBSTR(l_data, 1, 4000));
END;

Parse later:

plsql
FUNCTION parse_ratings RETURN ratings_tab PIPELINED IS
 l_data VARCHAR2(4000) := SYS_CONTEXT('TESTAPP', 'RATINGS_DATA');
 -- Split by |, then : via REGEXP_SUBSTR
END;

For huge data? GLOBAL TEMP TABLE: CREATE GLOBAL TEMPORARY TABLE temp_ratings ON COMMIT DELETE ROWS;. Load on POST, query anywhere in session. Auto-cleans.

PL/SQL Web Toolkit docs endorse this for stateful apps.

Tradeoff: contexts fast for scalars, temp tables for bulk.


Best Practices and Performance Tips

  • Security: Use DBMS_SESSION.SET_CONTEXT(..., username => USER); for VPD.
  • Cleanup: Always CLEAR_ALL_CONTEXT('TESTAPP') on logout/finish.
  • Debug: DBMS_SESSION.LIST_CONTEXT dumps all.
  • Scale: For high traffic, contexts beat packages (no pinning).
  • HTP/HTF: Use htf.formopen for cleaner HTML forms.
  • Params still? Minimal: just p_action. owa_util.get_value for rest.

Avoid URL state (O’Reilly tip)—exposes data. No raw packages without clear (memory hogs).

Test edge: concurrent users? Contexts isolate per oracle session.

Production? Wrap in ACL for 19c+.


Sources

  1. Developing PL/SQL Web Applications
  2. DBMS_SESSION PL/SQL Reference
  3. PL/SQL Session Variables on Stack Overflow
  4. DBMS_SESSION on ORACLE-BASE
  5. PL/SQL Web Toolkit Docs
  6. Maintaining State in Oracle Web Apps
  7. Developing Web Apps with PL/SQL

Conclusion

DBMS_SESSION contexts transform clunky PL/SQL HTML forms into sleek, modular flows—store once, read anywhere in the oracle session. Blend with package state for arrays, temp tables for bulk, and watch param hell vanish. Your refactored test_page now dispatches cleanly, scales effortlessly. Start small: wrap your filter in context, test the ratings loop. You’ll wonder how you coded without it.

Authors
Verified by moderation
Moderation
PL/SQL Session State for HTML Forms with HTP & DBMS_SESSION