From 2124fd979c47cf991b08a45ee08dcf3fdbfa2c02 Mon Sep 17 00:00:00 2001 From: Alex Zaw Date: Wed, 25 Feb 2026 23:05:48 +0000 Subject: [PATCH] Update Function GENERATE_SWAGGER_FROM_DBML --- .../Functions/GENERATE_SWAGGER_FROM_DBML.sql | 387 +++++++++++++++++- 1 file changed, 386 insertions(+), 1 deletion(-) diff --git a/RESTAPI/Functions/GENERATE_SWAGGER_FROM_DBML.sql b/RESTAPI/Functions/GENERATE_SWAGGER_FROM_DBML.sql index a4aa364..5cb770c 100644 --- a/RESTAPI/Functions/GENERATE_SWAGGER_FROM_DBML.sql +++ b/RESTAPI/Functions/GENERATE_SWAGGER_FROM_DBML.sql @@ -1 +1,386 @@ -@@\@^@ % %@@@@Kmmm@M@ %@M]@k@ %@M]@k@ %@M]@k@ %@M]@]@ %@M]@@@ %@@ %@Kmmm@ %@@ %@@@ %@@@@ %@@@@~@\@k@ %@~@\@k@ %@~@\@k@ %@~@Mk@k@]@k@ %@~@\@k@ %@~@\@k@ %@~@\@@@ %@ %@m@@M@@@]@^@ %@m@@M@@@]@^@ %``@פ@@@ %@m@~@@K@mm@M@@k@@]@^@ %@@M@@]@@M@ %@@M@@m@]@ %]@k@ %``@י@M]@ %@@M@ %@@K@@@@k@@K@@@@k@ %@M@@K@@]@@@k@@K@@@@k@ %@K@@@@k@@K@@@@ %@@k@ %@M@}[aa}@@@K@@@@ %@@@M@@]@@}|}@k@@@M@ %@]@@}|䙉ׁㅔ}@k@ %@@M@@]@@}|ȣمԅ}@k@ %@@M@@]@@}|}@k@ %@@M@@]@@}|}@k@ %@@M@@]@@}|晁Ʉ}@]@ %@@ %]@k@ %``@ׁ@M@L@KKK@ɕ秧ׁ~KKKn]@ %@@M@ %@@K@@k@ %@M@@K@@k@@K@@k@@K@@]@ %@m@k@ %@ %@@K@@@@@@}}@ %@@K@@@@@@}ā}@ %@@K@@@@@@}}@ %@@m@ %@@k@ %@M@}[aa}@@@K@@@@ %@@@M@@]@@}|}@k@ %@@@}K}@]@@@ %@@@M@}a}@@@K@@ %@@@M@@]@@ %}|ɕׁׁ}@k@ %@@M@@]@@}|ɕƖׁ}@k@ %@@M@@]@@}|ɕؤׁ}@]@@ %@ %@@~@@ %@@M@@K@@k@@K@@k@@K@@]@ %@@@ %]@k@ %``@ׁ@@M`@]k@`@@@@ %@@M@ %@@K@@k@@K@m@@@k@ %@M@@K@@]@@m@ %@@k@@M@ %@K@@M@@K@@k@}a}@]@ %]@@@ %@@K@@Ln@}}@ %@@K@@@@}l}@ %@@M@@K@@]@Ln@@M@@]@@``@}@@@^@``@@@ %]@k@ %``@Ɖ@@@~@@@MKKk@䢅@`@]@ %@@M@ %@@k@m@k@m@M@]@@M@ %@@@ %@@@ %]@@@ %@@ %]@k@ %@ \ No newline at end of file +SET PATH *LIBL ; + +CREATE OR REPLACE FUNCTION RESTAPI.GENERATE_SWAGGER_FROM_DBML ( + INAPPVER VARCHAR(10) , + INAPPNAME VARCHAR(128) , + INHOST VARCHAR(256) , + INBASEPATH VARCHAR(256) ) + RETURNS CLOB(2147483647) + LANGUAGE SQL + SPECIFIC RESTAPI.GENERATE_SWAGGER_FROM_DBML + NOT DETERMINISTIC + READS SQL DATA + CALLED ON NULL INPUT + SET OPTION ALWBLK = *ALLREAD , + ALWCPYDTA = *OPTIMIZE , + COMMIT = *NONE , + DECRESULT = (31, 31, 00) , + DYNDFTCOL = *NO , + DYNUSRPRF = *USER , + SRTSEQ = *HEX + BEGIN +DECLARE V_DBML CLOB ( 2 G ) ; +DECLARE V_SWAGGER CLOB ( 2 G ) ; +-- Pull the DBML +SET V_DBML = RESTAPI . GENERATE_DBML_V2 ( INAPPVER , INAPPNAME ) ; +WITH D ( DOC ) AS ( +VALUES XMLPARSE ( DOCUMENT V_DBML ) +) , +-- Programs (endpoints) +P AS ( +SELECT X . NAME AS PGMNAME , X . PATH AS RELPATH , +UPPER ( X . METHOD ) AS HTTPMETHOD , X . OK AS HTTPSUCCESS , +X . FAIL AS HTTPFAIL , X . OUTWRAP AS OUTWRAP +FROM D , +XMLTABLE ( '$d/dbml/program' PASSING D . DOC AS "d" +COLUMNS NAME VARCHAR ( 128 ) PATH '@name' , PATH VARCHAR ( +512 ) PATH '@restUriPathTemplate' , +METHOD VARCHAR ( 10 ) PATH '@restHttpRequestMethod' , +OK VARCHAR ( 3 ) PATH '@httpstatusonsuccess' , +FAIL VARCHAR ( 3 ) PATH '@httpstatusonfailure' , +OUTWRAP VARCHAR ( 128 ) PATH '@outputWrapperIdentifier' ) +AS X +) , +-- Params (from ) +PRM AS ( +SELECT PX . PGMNAME , +COALESCE ( DT . PATHID , DT . FORMID , DT . QUERYID ) +AS PARAM_NAME , +CASE +WHEN DT . PATHID IS NOT NULL THEN 'path' +WHEN DT . FORMID IS NOT NULL THEN 'formData' +WHEN DT . QUERYID IS NOT NULL THEN 'query' +END AS PARAM_IN +FROM D , +XMLTABLE ( '$d/dbml/program' PASSING D . DOC AS "d" +COLUMNS PGMNAME VARCHAR ( 128 ) PATH '@name' , +PGMNODE XML PATH '.' ) AS PX +LEFT JOIN XMLTABLE ( 'sql/data' PASSING PX . PGMNODE +COLUMNS PATHID VARCHAR ( 128 ) PATH +'@restInPathParam' , +FORMID VARCHAR ( 128 ) PATH '@restInFormParam' , +QUERYID VARCHAR ( 128 ) PATH '@restInQueryParam' ) AS +DT +ON 1 = 1 +WHERE COALESCE ( DT . PATHID , DT . FORMID , DT . QUERYID ) +IS NOT NULL +) , +-- Path tokens (non-variable segments), auto-ignoring the app name +TOK AS ( +SELECT P . PGMNAME , S . ORDINAL_POSITION AS POS , +LOWER ( S . ELEMENT ) AS RAW_TOKEN +FROM P , TABLE ( +SYSTOOLS . SPLIT ( P . RELPATH , '/' ) +) AS S +WHERE S . ELEMENT <> '' +AND S . ELEMENT NOT LIKE '{%}' +AND LOWER ( S . ELEMENT ) <> LOWER ( INAPPNAME ) -- don't hardcode any app; use-- the param +) , +-- First two tokens = default tag (e.g., "Users - MAPICS") +TOP2 AS ( +SELECT PGMNAME , RAW_TOKEN , ROW_NUMBER ( ) OVER ( +PARTITION BY PGMNAME +ORDER BY POS +) AS RN +FROM TOK +) , +TAGRAW AS ( +SELECT T1 . PGMNAME , T1 . RAW_TOKEN AS RAW1 , T2 . RAW_TOKEN AS RAW2 +FROM TOP2 T1 +LEFT JOIN TOP2 T2 +ON T2 . PGMNAME = T1 . PGMNAME +AND T2 . RN = 2 +WHERE T1 . RN = 1 +) , +-- Pretty display for any raw token using NAME_MAP, else TitleCase +NAME_FMT AS ( +SELECT X . RAW_TOKEN , +COALESCE ( +NM . DISPLAY , UPPER ( SUBSTR ( X . RAW_TOKEN , 1 , 1 ) ) CONCAT +CASE +WHEN +LENGTH ( X . RAW_TOKEN ) > 1 +THEN +LOWER ( +REPLACE ( +REPLACE ( +SUBSTR ( X . RAW_TOKEN , 2 ) , '-' , ' ' +) , '_' , ' ' ) ) +ELSE '' +END ) AS DISPLAY +FROM ( +SELECT DISTINCT RAW_TOKEN +FROM TOK +) X +LEFT JOIN RESTAPI . NAME_MAP NM +ON NM . RAW_TOKEN = X . RAW_TOKEN +) , +-- Verb = last token if it's in VERB_MAP +LASTTOK AS ( +SELECT PGMNAME , MAX ( POS ) AS MAXPOS +FROM TOK +GROUP BY PGMNAME +) , +VERB AS ( +SELECT L . PGMNAME , T . RAW_TOKEN AS RAW_VERB , +VM . DISPLAY AS VERB_DISPLAY , +CASE +WHEN VM . RAW_VERB IS NULL THEN 0 +ELSE 1 +END AS IS_VERB , L . MAXPOS +FROM LASTTOK L +JOIN TOK T +ON T . PGMNAME = L . PGMNAME +AND T . POS = L . MAXPOS +LEFT JOIN RESTAPI . VERB_MAP VM +ON VM . RAW_VERB = T . RAW_TOKEN +) , +-- Object phrase = last one or two nouns *before* the verb (if verb recognized),-- +-- else last one or two overall +OBJ_SRC AS ( +SELECT T . PGMNAME , T . POS , T . RAW_TOKEN , V . IS_VERB , V . MAXPOS +FROM TOK T +LEFT JOIN VERB V +ON V . PGMNAME = T . PGMNAME +) , +OBJ_NOUNS AS ( +SELECT PGMNAME , RAW_TOKEN , ROW_NUMBER ( ) OVER ( +PARTITION BY PGMNAME +ORDER BY POS DESC +) AS RN_ALLDESC , ROW_NUMBER ( ) OVER ( +PARTITION BY PGMNAME +ORDER BY +CASE +WHEN IS_VERB = 1 THEN +CASE +WHEN POS < MAXPOS THEN POS +ELSE - 999999 +END +ELSE POS +END DESC +) AS RN_BEFORE_VERB_DESC +FROM OBJ_SRC +) , +OBJ_PICK AS ( +-- Prefer nouns before verb (RN_BEFORE_VERB_DESC), fallback to overall (RN_ALLDE--SC) +SELECT O1 . PGMNAME , +COALESCE ( +NF1 . DISPLAY , +UPPER ( SUBSTR ( O1 . RAW_TOKEN , 1 , 1 ) ) CONCAT +LOWER ( SUBSTR ( O1 . RAW_TOKEN , 2 ) ) ) AS OBJ1 , +COALESCE ( +NF2 . DISPLAY , +UPPER ( SUBSTR ( O2 . RAW_TOKEN , 1 , 1 ) ) CONCAT +LOWER ( SUBSTR ( O2 . RAW_TOKEN , 2 ) ) ) AS OBJ2 +FROM ( +SELECT * +FROM OBJ_NOUNS +WHERE RN_BEFORE_VERB_DESC = 1 +) O1 +LEFT JOIN ( +SELECT * +FROM OBJ_NOUNS +WHERE RN_BEFORE_VERB_DESC = 2 +) O2 +ON O2 . PGMNAME = O1 . PGMNAME +LEFT JOIN NAME_FMT NF1 +ON NF1 . RAW_TOKEN = O1 . RAW_TOKEN +LEFT JOIN NAME_FMT NF2 +ON NF2 . RAW_TOKEN = COALESCE ( O2 . RAW_TOKEN , '' ) +UNION ALL +SELECT O1 . PGMNAME , +COALESCE ( +NF1 . DISPLAY , +UPPER ( SUBSTR ( O1 . RAW_TOKEN , 1 , 1 ) ) CONCAT +LOWER ( SUBSTR ( O1 . RAW_TOKEN , 2 ) ) ) AS OBJ1 , +COALESCE ( +NF2 . DISPLAY , +UPPER ( SUBSTR ( O2 . RAW_TOKEN , 1 , 1 ) ) CONCAT +LOWER ( SUBSTR ( O2 . RAW_TOKEN , 2 ) ) ) AS OBJ2 +FROM ( +SELECT * +FROM OBJ_NOUNS +WHERE RN_ALLDESC = 1 +) O1 +LEFT JOIN ( +SELECT * +FROM OBJ_NOUNS +WHERE RN_ALLDESC = 2 +) O2 +ON O2 . PGMNAME = O1 . PGMNAME +LEFT JOIN NAME_FMT NF1 +ON NF1 . RAW_TOKEN = O1 . RAW_TOKEN +LEFT JOIN NAME_FMT NF2 +ON NF2 . RAW_TOKEN = COALESCE ( O2 . RAW_TOKEN , '' ) +WHERE NOT EXISTS ( +SELECT 1 +FROM OBJ_NOUNS Z +WHERE Z . PGMNAME = O1 . PGMNAME +AND Z . RN_BEFORE_VERB_DESC = 1 ) +) , +-- Derived tag ("Seg1 - Seg2") using display names +TAG_DERIVED AS ( +SELECT TR . PGMNAME , +CASE +WHEN +NF2 . DISPLAY IS NOT NULL +THEN NF1 . DISPLAY CONCAT ' - ' CONCAT NF2 . DISPLAY +ELSE NF1 . DISPLAY +END AS TAG_NAME +FROM TAGRAW TR +LEFT JOIN NAME_FMT NF1 +ON NF1 . RAW_TOKEN = TR . RAW1 +LEFT JOIN NAME_FMT NF2 +ON NF2 . RAW_TOKEN = TR . RAW2 +) , +-- Summaries: "{VerbDisplay or 'Execute'} {OBJ1 [OBJ2]} [for param1, param2 ...]--" +PATH_PARAMS AS ( +SELECT PGMNAME , +LISTAGG ( +PARAM_NAME , ', ' ) WITHIN GROUP ( ORDER BY PARAM_NAME ) +AS PATH_PARAM_LIST +FROM PRM +WHERE PARAM_IN = 'path' +GROUP BY PGMNAME +) , +SUMMARY_DERIVED AS ( +SELECT P . PGMNAME , +RTRIM ( +COALESCE ( V . VERB_DISPLAY , 'Execute' ) CONCAT ' ' +CONCAT +CASE +WHEN +OP . OBJ2 IS NOT NULL +THEN OP . OBJ2 CONCAT ' ' CONCAT OP . OBJ1 +ELSE OP . OBJ1 +END CONCAT +COALESCE ( ' for ' CONCAT PP . PATH_PARAM_LIST , '' ) ) +AS SUMMARY +FROM P +LEFT JOIN VERB V +ON V . PGMNAME = P . PGMNAME +LEFT JOIN OBJ_PICK OP +ON OP . PGMNAME = P . PGMNAME +LEFT JOIN PATH_PARAMS PP +ON PP . PGMNAME = P . PGMNAME +) , +-- Apply optional per-program overrides +META AS ( +SELECT P . PGMNAME , +COALESCE ( O . TAG_NAME , TD . TAG_NAME ) AS TAG_NAME , +COALESCE ( O . SUMMARY , SD . SUMMARY ) AS SUMMARY +FROM P +LEFT JOIN TAG_DERIVED TD +ON TD . PGMNAME = P . PGMNAME +LEFT JOIN SUMMARY_DERIVED SD +ON SD . PGMNAME = P . PGMNAME +LEFT JOIN RESTAPI . SWAGGER_OVERRIDES O +ON O . APPVER = INAPPVER +AND O . APPNAME = INAPPNAME +AND O . PGMNAME = P . PGMNAME +) , +-- Parameters JSON per program +PARMS AS ( +SELECT PGMNAME , +'[' CONCAT +COALESCE ( +LISTAGG ( +'{"name":"' CONCAT PARAM_NAME CONCAT +'","in":"' CONCAT PARAM_IN CONCAT +'","required":true,"type":"string"}' , ',' ) , +'' ) CONCAT ']' AS PARAMS_JSON +FROM PRM +GROUP BY PGMNAME +) , +-- Path method JSON per program +PATHS AS ( +SELECT '/v1' CONCAT RELPATH AS FULLPATH , +JSON_OBJECT ( +LOWER ( HTTPMETHOD ) VALUE JSON_OBJECT ( +'tags' VALUE JSON_ARRAY ( M . TAG_NAME ) , +'summary' VALUE M . SUMMARY , +'operationId' VALUE M . PGMNAME , +'consumes' VALUE JSON_ARRAY ( '*/*' ) , +'produces' VALUE JSON_ARRAY ( 'application/json' ) , +'parameters' VALUE JSON_ARRAY ( PR . PARAMS_JSON ) , +'responses' VALUE JSON_OBJECT ( +HTTPSUCCESS VALUE JSON_OBJECT ( +'description' VALUE 'Successful operation' +) , HTTPFAIL VALUE JSON_OBJECT ( +'description' VALUE 'Operation failure' , +'schema' VALUE JSON_OBJECT ( +'$ref' VALUE '#/definitions/' || +OUTWRAP +) +) +) +) +) AS METHOD_JSON +FROM P +LEFT JOIN META M +ON M . PGMNAME = P . PGMNAME +LEFT JOIN PARMS PR +ON PR . PGMNAME = P . PGMNAME +) , +PATHS_JSON AS ( +SELECT +'{' CONCAT +LISTAGG ( +'"' CONCAT FULLPATH CONCAT '": ' CONCAT METHOD_JSON , +',' ) CONCAT '}' AS J +FROM PATHS +) , +TAGS_JSON AS ( +SELECT +'[' CONCAT +LISTAGG ( +'{"name":"' CONCAT TAG_NAME CONCAT +'","description":"Operations for ' CONCAT TAG_NAME +CONCAT '"}' , ',' ) CONCAT ']' AS J +FROM ( +SELECT DISTINCT TAG_NAME +FROM META +) +) , +DEFS_JSON AS ( +SELECT +'{' CONCAT +'"SQLException":{"type":"object","properties":{"SQLState":{"type":"string","maxLength":5},"errorCode":{"type":"integer"},"message":{"type":"string"}}},' +CONCAT +'"SQLStateInfo":{"type":"object","properties":{"rowsAffectedCounts":{"type":"string"},"SQLError":{"$ref":"#/definitions/SQLException"},"SQLWarnings":{"type":"array","items":{"$ref":"#/definitions/SQLException"}}}},' +CONCAT +LISTAGG ( +'"' CONCAT OUTWRAP CONCAT +'":{"type":"object","properties":{"SQLStateInfo":{"$ref":"#/definitions/SQLStateInfo"}}}' +, ',' ) CONCAT '}' AS J +FROM ( +SELECT DISTINCT OUTWRAP +FROM P +) +) +SELECT '{' CONCAT '"swagger":"2.0",' CONCAT '"info":{"title":"' +CONCAT UPPER ( SUBSTR ( INAPPNAME , 1 , 1 ) ) CONCAT +LOWER ( SUBSTR ( INAPPNAME , 2 ) ) CONCAT ' APIs",' CONCAT +'"description":"APIs available for ' CONCAT INAPPNAME CONCAT +'",' CONCAT '"version":"1.0.0"},' CONCAT '"host":"' CONCAT +INHOST CONCAT '",' CONCAT '"schemes":["http"],' CONCAT +'"basePath":"' CONCAT INBASEPATH CONCAT '",' CONCAT '"tags":' +CONCAT COALESCE ( ( SELECT J +FROM TAGS_JSON ) , '[]' ) CONCAT ',' CONCAT +'"definitions":' CONCAT ( SELECT J +FROM DEFS_JSON ) CONCAT ',' CONCAT '"paths":' CONCAT +( SELECT J +FROM PATHS_JSON ) CONCAT '}' +INTO V_SWAGGER +FROM SYSIBM . SYSDUMMY1 ; +RETURN V_SWAGGER ; +END ; + +GRANT ALTER , EXECUTE +ON SPECIFIC FUNCTION RESTAPI.GENERATE_SWAGGER_FROM_DBML +TO AMAPICS WITH GRANT OPTION ; + +GRANT EXECUTE +ON SPECIFIC FUNCTION RESTAPI.GENERATE_SWAGGER_FROM_DBML +TO PUBLIC ; +