Repository navigation
Conversation
sebjulliand
left a comment
There was a problem hiding this comment.
Thanks @bobcozzi . I've left comments to clean it all up.
Another question, from the devil's advocate: can CCSID other than 37 be a problem with that component? It gets compiled with the user job's CCSID, I guess. So could it lead to some issues regarding the variant characters?
|
|
||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // Copyright (c) 2026 by R. Cozzi, Jr. | ||
|
|
||
|
|
||
| // @author BobCozzi |
There was a problem hiding this comment.
No need for copyright here.
| // SPDX-License-Identifier: Apache-2.0 | |
| // Copyright (c) 2026 by R. Cozzi, Jr. | |
| // @author BobCozzi |
| -- SPDX-License-Identifier: Apache-2.0 | ||
| -- Copyright (c) 1996-2026 by R. Cozzi, Jr. | ||
|
|
||
| -- @author BobCozzi |
There was a problem hiding this comment.
No need for copyright here either.
| -- SPDX-License-Identifier: Apache-2.0 | |
| -- Copyright (c) 1996-2026 by R. Cozzi, Jr. | |
| -- @author BobCozzi |
|
|
||
| export function buildMbrAttrSqlSource(targetLibrary: string, version: number): string { | ||
| return MBR_ATTR_SQL_TEMPLATE | ||
| .replace(/\$\{version\}/g, String(version)) | ||
| .replace(/\bsqltools\b/gi, targetLibrary.trim()); | ||
| } |
There was a problem hiding this comment.
This could be remove since it's unused.
| export function buildMbrAttrSqlSource(targetLibrary: string, version: number): string { | |
| return MBR_ATTR_SQL_TEMPLATE | |
| .replace(/\$\{version\}/g, String(version)) | |
| .replace(/\bsqltools\b/gi, targetLibrary.trim()); | |
| } |
| -- @author BobCozzi | ||
|
|
||
|
|
||
| CREATE or REPLACE FUNCTION sqltools.mbr_attr( |
There was a problem hiding this comment.
No need to qualify the function here if DFTRDBCOL is specified when running RUNSQLSTM I think.
| CREATE or REPLACE FUNCTION sqltools.mbr_attr( | |
| CREATE or REPLACE FUNCTION mbr_attr( |
| SPECIFIC sqlTools.mbr_attr | ||
| CARDINALITY 1 | ||
| PARAMETER STYLE DB2SQL; | ||
|
|
||
|
|
||
| LABEL on specific routine sqltools.mbr_attr IS | ||
| '\${version} - Retrieve Source Member Description'; | ||
|
|
||
| COMMENT ON Specific FUNCTION sqltools.mbr_attr IS | ||
| '\${version} - Retrieve Source Member Description returns the basic or | ||
| advanced attributes for the specified Member name.'; | ||
|
|
||
| comment on parameter SPECIFIC FUNCTION sqltools.mbr_attr |
There was a problem hiding this comment.
No need to qualify the function here if DFTRDBCOL is specified when running RUNSQLSTM I think.
| SPECIFIC sqlTools.mbr_attr | |
| CARDINALITY 1 | |
| PARAMETER STYLE DB2SQL; | |
| LABEL on specific routine sqltools.mbr_attr IS | |
| '\${version} - Retrieve Source Member Description'; | |
| COMMENT ON Specific FUNCTION sqltools.mbr_attr IS | |
| '\${version} - Retrieve Source Member Description returns the basic or | |
| advanced attributes for the specified Member name.'; | |
| comment on parameter SPECIFIC FUNCTION sqltools.mbr_attr | |
| SPECIFIC mbr_attr | |
| CARDINALITY 1 | |
| PARAMETER STYLE DB2SQL; | |
| LABEL on specific routine mbr_attr IS | |
| '\${version} - Retrieve Source Member Description'; | |
| COMMENT ON Specific FUNCTION mbr_attr IS | |
| '\${version} - Retrieve Source Member Description returns the basic or | |
| advanced attributes for the specified Member name.'; | |
| comment on parameter SPECIFIC FUNCTION mbr_attr |
| `); | ||
|
|
||
| if (!row) { | ||
| return { status: "NotInstalled", remoteSignature: MbrAttr.SIGNATURE }; |
There was a problem hiding this comment.
Return a blank remoteSignature if it's not installed.
| return { status: "NotInstalled", remoteSignature: MbrAttr.SIGNATURE }; | |
| return { status: "NotInstalled", remoteSignature: '' }; |
| const remoteVersion = Number(longComment.match(/^(\d+)/)?.[1] || -1); | ||
| const status = remoteVersion >= MbrAttr.VERSION ? "Installed" : "NeedsUpdate"; | ||
|
|
||
| return { status, remoteSignature: MbrAttr.SIGNATURE }; |
There was a problem hiding this comment.
Use connection.getContent().getSQLRoutineSignature to get the function's signature. It will at least ensure that it's not been changed to call another program.
| } | ||
|
|
||
| const sqlResult = await connection.runCommand({ | ||
| command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)` |
There was a problem hiding this comment.
Since the SQL code should not be qualified, this will create the function in the right library.
| command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)` | |
| command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL) DFTRDBCOL(${library})` |
| const componentDir = path.posix.join(installDirectory, "mbr_attr"); | ||
| const rpglePath = path.posix.join(componentDir, `${MbrAttr.PGM_NAME}.RPGLE`); | ||
| const sqlPath = path.posix.join(componentDir, `${MbrAttr.PGM_NAME}.SQL`); | ||
|
|
||
| await connection.sendCommand({ command: `mkdir -p ${componentDir}` }); | ||
|
|
||
| await connection.getContent().writeStreamfileRaw(rpglePath, MBR_ATTR_RPGLE_SOURCE); | ||
| await connection.getContent().writeStreamfileRaw(sqlPath, buildMbrAttrSqlSource(library, MbrAttr.VERSION)); | ||
|
|
||
| const compileResult = await connection.runCommand({ | ||
| command: `QSYS/CRTBNDRPG PGM(${library}/${MbrAttr.PGM_NAME}) SRCSTMF('${rpglePath}') OPTION(*EVENTF) DBGVIEW(*NONE) TGTCCSID(*JOB)` | ||
| }); | ||
| if (compileResult.code !== 0) { | ||
| throw new Error(`Failed to compile ${MbrAttr.PGM_NAME}: ${compileResult.stderr || compileResult.stdout}`); | ||
| } | ||
|
|
||
| const sqlResult = await connection.runCommand({ | ||
| command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)` | ||
| }); | ||
| if (sqlResult.code !== 0) { | ||
| throw new Error(`Failed to install SQL routine ${library}.${MbrAttr.SPECIFIC_NAME}: ${sqlResult.stderr || sqlResult.stdout}`); | ||
| } |
There was a problem hiding this comment.
You can wrap that part and take advantage of the connection.withTempDirectory method here. It creates a temporary directory that gets deleted after it's done, regardless of its result. Since we don't need to keep the source files, that may be better.
| const componentDir = path.posix.join(installDirectory, "mbr_attr"); | |
| const rpglePath = path.posix.join(componentDir, `${MbrAttr.PGM_NAME}.RPGLE`); | |
| const sqlPath = path.posix.join(componentDir, `${MbrAttr.PGM_NAME}.SQL`); | |
| await connection.sendCommand({ command: `mkdir -p ${componentDir}` }); | |
| await connection.getContent().writeStreamfileRaw(rpglePath, MBR_ATTR_RPGLE_SOURCE); | |
| await connection.getContent().writeStreamfileRaw(sqlPath, buildMbrAttrSqlSource(library, MbrAttr.VERSION)); | |
| const compileResult = await connection.runCommand({ | |
| command: `QSYS/CRTBNDRPG PGM(${library}/${MbrAttr.PGM_NAME}) SRCSTMF('${rpglePath}') OPTION(*EVENTF) DBGVIEW(*NONE) TGTCCSID(*JOB)` | |
| }); | |
| if (compileResult.code !== 0) { | |
| throw new Error(`Failed to compile ${MbrAttr.PGM_NAME}: ${compileResult.stderr || compileResult.stdout}`); | |
| } | |
| const sqlResult = await connection.runCommand({ | |
| command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)` | |
| }); | |
| if (sqlResult.code !== 0) { | |
| throw new Error(`Failed to install SQL routine ${library}.${MbrAttr.SPECIFIC_NAME}: ${sqlResult.stderr || sqlResult.stdout}`); | |
| } | |
| await connection.withTempDirectory(async componentDir => { | |
| const rpglePath = path.posix.join(componentDir, `${MbrAttr.PGM_NAME}.RPGLE`); | |
| const sqlPath = path.posix.join(componentDir, `${MbrAttr.PGM_NAME}.SQL`); | |
| await connection.sendCommand({ command: `mkdir -p ${componentDir}` }); | |
| await connection.getContent().writeStreamfileRaw(rpglePath, MBR_ATTR_RPGLE_SOURCE); | |
| await connection.getContent().writeStreamfileRaw(sqlPath, buildMbrAttrSqlSource(library, MbrAttr.VERSION)); | |
| const compileResult = await connection.runCommand({ | |
| command: `QSYS/CRTBNDRPG PGM(${library}/${MbrAttr.PGM_NAME}) SRCSTMF('${rpglePath}') OPTION(*EVENTF) DBGVIEW(*NONE) TGTCCSID(*JOB)` | |
| }); | |
| if (compileResult.code !== 0) { | |
| throw new Error(`Failed to compile ${MbrAttr.PGM_NAME}: ${compileResult.stderr || compileResult.stdout}`); | |
| } | |
| const sqlResult = await connection.runCommand({ | |
| command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)` | |
| }); | |
| if (sqlResult.code !== 0) { | |
| throw new Error(`Failed to install SQL routine ${library}.${MbrAttr.SPECIFIC_NAME}: ${sqlResult.stderr || sqlResult.stdout}`); | |
| } | |
| }); |
|
I think we should have a Function Library (in addition to the Temp Library) so that one function can propagate to all codefori users on the system and we don't end up with 40 copies of it, nor does it get created in a library full of garbage. |
|
On the CCSID Issue, have a look at this other UDTF I created for just File Attributes. It accepts the file in both LIB, File, Mbr parameters or as a path name like /QSYS.LIB/COZTEST.LIB/Qrpglesrc.file/MbrName.mbr Have a look specifically at the way the PATH_NAME parameter is handled. |
|
Also, why do you want me to remove the copyright and author notice? |
Agreed, but it's not something we can force upon the users.
If you contribute something to an open-source project, it falls under the project's copyright, it's a part of the project and it's fully integrated to it. As for the author notice, is it really important to know who wrote this and that? The Git history and commits already tell it, if we need to know who's to blame 😄 |
|
Ok I see (re the (c) notice.) |
MBR_ATTR SQL UDTF
This PR adds a new SQL UDTF component named
MBR_ATTRthat exposes IBM i source member metadata through a standard table function. It is designed to surface the information available from the QUSRMBRD API in a SQL-friendly format so source member attributes can be queried directly without writing custom CL or API logic for each lookup.The function returns basic member metadata or a fuller set of source and usage details, depending on the
DETAILED_INFOparameter.Input parameters
LIBRARY_NAMEvarchar(10) default*LIBLFILE_NAMEvarchar(10)MBR_NAMEvarchar(10) default*FIRST*FIRST,*LAST, or a specific member name.OVR_MBRvarchar(10) defaultYESFIND_MBRvarchar(10) defaultYES*LIBL.DETAILED_INFOvarchar(10) defaultNOOutput columns
Basic columns
OBJLIBvarchar(10)OBJNAMEvarchar(10)MBRNAMEvarchar(10)SRCTYPEvarchar(10)FILEATTRvarchar(10)TEXTvarchar(50)MBR_CREATE_DATEtimestamp(0)MBR_CHG_DATEtimestamp(0)Detailed columns
Record_countbigintDeleted_Record_CountbigintEXPDATEdateLast_used_DaysintLast_used_DatedateLast_used_Reset_datedateOBJ_CHG_DATEtimestamp(0)Example usage
This UDTF makes it easy to query source member attributes using SQL in a consistent way. It tends to perform very fast (under 20ms per call)