Update Function GENERATE_SWAGGER_FROM_DBML

This commit is contained in:
2026-02-25 23:05:48 +00:00
parent be5b98986f
commit 2124fd979c

View File

@@ -1 +1,386 @@
@\@^@ 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 ;