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 ;