Skip to content

Instantly share code, notes, and snippets.

@forstie
Last active February 11, 2025 14:41
Show Gist options
  • Save forstie/6fa66b0f635b75e3187c2a77b55e55d5 to your computer and use it in GitHub Desktop.
Save forstie/6fa66b0f635b75e3187c2a77b55e55d5 to your computer and use it in GitHub Desktop.
The request... fill an IBM i (SQL) Services gap in Work Management via the creation of a CLASS_INFO service.
--
-- Subject: Finding IBM i class objects and return the class attributes
-- Author: Scott Forstie
-- Date : February, 2025
--
-- This service overcomes the lack of OUTFILE support on the Display Class (DSPCLS) CL command
-- by copying the spooled file into a Db2 for i table in the SESSION schema.
--
-- Features Used : This Gist uses object_statistics, output_queue_entries_basic, SQL PL, Pipe
--
-- Resources:
-- https://www.ibm.com/docs/api/v1/content/ssw_ibm_i_75/cl/dspcls.htm
-- https://www.ibm.com/docs/api/v1/content/ssw_ibm_i_75/cl/cpysplf.htm
-- https://www.ibm.com/docs/api/v1/content/ssw_ibm_i_75/cl/dltsplf.htm
--
drop function if exists coolstuff.class_info;
--
-- UDTF... find class information
--
CREATE OR REPLACE FUNCTION coolstuff.class_info ()
RETURNS TABLE (
class_library VARCHAR(10) FOR SBCS DATA,
class_name VARCHAR(10) FOR SBCS DATA,
text_description VARCHAR(50) FOR SBCS DATA,
last_used date,
use_count INTEGER,
run_priority INTEGER,
time_slice INTEGER,
default_wait VARCHAR(11) FOR SBCS DATA,
maximum_cpu_time VARCHAR(11) FOR SBCS DATA,
maximum_temporary_storage_allowed VARCHAR(11) FOR SBCS DATA,
maximum_active_threads VARCHAR(11) FOR SBCS DATA
)
NOT DETERMINISTIC
EXTERNAL ACTION
MODIFIES SQL DATA
NOT FENCED
SET OPTION commit = *none, usrprf = *user, dynusrprf = *user
BEGIN
DECLARE v_print_line CHAR(133);
DECLARE local_sqlcode INTEGER;
DECLARE local_sqlstate CHAR(5);
DECLARE v_message_text VARCHAR(70);
DECLARE v_dspcls VARCHAR(1000);
--
-- DSPCLS detail
--
DECLARE v_class CHAR(10);
DECLARE v_class_library CHAR(10);
DECLARE v_class_run_priority INTEGER;
DECLARE v_class_time_slice INTEGER;
DECLARE v_class_dft_wait VARCHAR(11);
DECLARE v_class_max_cpu VARCHAR(11) FOR SBCS DATA;
DECLARE v_class_max_tmp_stg VARCHAR(11) FOR SBCS DATA;
DECLARE v_class_max_threads VARCHAR(11) FOR SBCS DATA;
--
-- OBJECT_STATISTICS detail
--
DECLARE find_classes_query_text VARCHAR(5000);
DECLARE v_cls_text VARCHAR(50);
DECLARE v_job_name VARCHAR(28);
DECLARE v_cls_last_use DATE;
DECLARE v_cls_use_count INTEGER;
DECLARE c_find_classes CURSOR FOR find_classes_query;
DECLARE c_find_dspcls_output CURSOR FOR
SELECT job_name
FROM qsys2.output_queue_entries_basic
WHERE user_name = session_user
AND spooled_file_name = 'QPDSPCLS'
ORDER BY create_timestamp DESC
LIMIT 1;
DECLARE c_dspcls_output CURSOR FOR
SELECT c1
FROM session.splf x
WHERE RRN(x) > 4
ORDER BY RRN(x);
DECLARE CONTINUE HANDLER FOR sqlexception
BEGIN
GET DIAGNOSTICS CONDITION 1
local_sqlcode = db2_returned_sqlcode,
local_sqlstate = returned_sqlstate;
SET v_message_text = 'systools.class_info() failed with: ' CONCAT
local_sqlcode CONCAT ' AND ' CONCAT local_sqlstate;
SIGNAL SQLSTATE 'QPC01' SET MESSAGE_TEXT = v_message_text;
END;
DECLARE GLOBAL TEMPORARY TABLE splf (c1 CHAR(133)) WITH replace;
SET find_classes_query_text =
'select libs.objname, objs.OBJNAME, objs.OBJTEXT, objs.LAST_USED_TIMESTAMP, objs.DAYS_USED_COUNT from table (
qsys2.object_statistics(''QSYS '', ''*LIB'')
) libs, lateral ( select * FROM TABLE (qsys2.OBJECT_STATISTICS(libs.objname,''CLS '')) AS a ) objs'
;
PREPARE find_classes_query FROM find_classes_query_text;
OPEN c_find_classes;
l1: LOOP
FETCH FROM c_find_classes
INTO v_class_library, v_class, v_cls_text, v_cls_last_use,
v_cls_use_count;
GET DIAGNOSTICS CONDITION 1
local_sqlcode = db2_returned_sqlcode,
local_sqlstate = returned_sqlstate;
IF (local_sqlstate = '02000') THEN
CLOSE c_find_classes;
RETURN;
END IF;
SET v_dspcls = 'QSYS/DSPCLS CLS(' CONCAT RTRIM(v_class_library)
CONCAT '/' CONCAT RTRIM(v_class) CONCAT
') OUTPUT(*PRINT)';
CALL qsys2.qcmdexc(v_dspcls);
OPEN c_find_dspcls_output;
FETCH FROM c_find_dspcls_output
INTO v_job_name;
CLOSE c_find_dspcls_output;
CALL qsys2.qcmdexc(
'QSYS/CPYSPLF FILE(QPDSPCLS) TOFILE(QTEMP/SPLF) SPLNBR(*LAST) JOB('
CONCAT v_job_name CONCAT ') ');
OPEN c_dspcls_output;
FETCH FROM c_dspcls_output
INTO v_print_line;
SET v_class_run_priority = INT(SUBSTR(v_print_line, 56, 10));
FETCH FROM c_dspcls_output
INTO v_print_line;
SET v_class_time_slice = INT(SUBSTR(v_print_line, 56, 10));
FETCH FROM c_dspcls_output
INTO v_print_line;
FETCH FROM c_dspcls_output
INTO v_print_line;
SET v_class_dft_wait = SUBSTR(v_print_line, 56, 10);
FETCH FROM c_dspcls_output
INTO v_print_line;
SET v_class_max_cpu = SUBSTR(v_print_line, 56, 10);
FETCH FROM c_dspcls_output
INTO v_print_line;
SET v_class_max_tmp_stg = SUBSTR(v_print_line, 56, 10);
FETCH FROM c_dspcls_output
INTO v_print_line;
SET v_class_max_threads = SUBSTR(v_print_line, 56, 10);
CLOSE c_dspcls_output;
CALL qsys2.qcmdexc('QSYS/DLTSPLF FILE(QPDSPCLS) SPLNBR(*LAST) JOB('
CONCAT v_job_name CONCAT ') ');
PIPE (
v_class_library,
v_class, v_cls_text, v_cls_last_use, v_cls_use_count,
v_class_run_priority, v_class_time_slice, v_class_dft_wait,
v_class_max_cpu, v_class_max_tmp_stg, v_class_max_threads);
END LOOP; /* L1 */
CLOSE c_find_classes;
END;
stop;
select *
from table (
coolstuff.class_info()
);
stop;
--
-- Create a view to expose the class information
--
CREATE OR REPLACE VIEW coolstuff.class_info (
class_library FOR COLUMN class_lib, class_name FOR COLUMN class,
TEXT_DESCRIPTION FOR COLUMN text,
LAST_USED_TIMESTAMP FOR COLUMN last_used, use_count,
run_priority FOR COLUMN priority, time_slice,
default_wait FOR COLUMN DFTWAIT, maximum_cpu_time FOR COLUMN cpu_time,
maximum_temporary_storage_allowed FOR COLUMN max_stg,
maximum_active_threads FOR COLUMN max_thread) AS
SELECT *
FROM TABLE (
coolstuff.class_info()
);
stop;
--
-- Review the CLASS information
--
SELECT *
FROM coolstuff.class_info;
stop;
@chrjorgensen
Copy link

Hi Scott.

Nice work as usual - this could be a candidate for a new Work Management service in a future release? πŸ™

Allow me to offer a version of this with a twist: Instead of interpreting the printed output from the command DSPCLS, call the QSYS/QWCRCLSI API directly from SQL! πŸ’₯

My version is found here: https://gist.github.com/chrjorgensen/4da3562f834abee41b87ab065a2e09f6
It also includes the ELIGIBLE FOR PURGE value in the class object.

Maybe we should spread the word about using API's in SQL? Not many are aware of this possibility, I think. πŸ€”

Two different techniques achieving the same goal - offering the programmer a choice... πŸ˜ƒ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment