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 <data ... restInXxxParam="...">) 
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 ; 

