Skip to content

Add managed MBR_ATTR SQL UDTF component - #3476

Open
bobcozzi wants to merge 16 commits into
codefori:masterfrom
bobcozzi:feat/mbr-attr-udtf-component
Open

bobcozzi wants to merge 16 commits into
codefori:masterfrom
bobcozzi:feat/mbr-attr-udtf-component

Conversation

@bobcozzi

@bobcozzi bobcozzi commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

MBR_ATTR SQL UDTF

This PR adds a new SQL UDTF component named MBR_ATTR that 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_INFO parameter.

Input parameters

  • LIBRARY_NAME varchar(10) default *LIBL
    • Library containing the source file.
  • FILE_NAME varchar(10)
    • Source file containing the member.
  • MBR_NAME varchar(10) default *FIRST
    • Member name to inspect. Accepts *FIRST, *LAST, or a specific member name.
  • OVR_MBR varchar(10) default YES
    • Whether to respect override member processing.
  • FIND_MBR varchar(10) default YES
    • Whether to locate the member when using *LIBL.
  • DETAILED_INFO varchar(10) default NO
    • Specifies whether to include the extended detail columns.

Output columns

Basic columns

  • OBJLIB varchar(10)
    • Library containing the object.
  • OBJNAME varchar(10)
    • Name of the source file.
  • MBRNAME varchar(10)
    • Member name.
  • SRCTYPE varchar(10)
    • Source member type.
  • FILEATTR varchar(10)
    • File attribute such as PF, LF, or DDMF.
  • TEXT varchar(50)
    • Member text description.
  • MBR_CREATE_DATE timestamp(0)
    • Date and time the member was created.
  • MBR_CHG_DATE timestamp(0)
    • Date and time the member was last changed.

Detailed columns

  • Record_count bigint
    • Current record count.
  • Deleted_Record_Count bigint
    • Number of deleted records.
  • EXPDATE date
    • Expiration date for the member.
  • Last_used_Days int
    • Number of days since the member was last used.
  • Last_used_Date date
    • Last used date.
  • Last_used_Reset_date date
    • Last reset date for usage tracking.
  • OBJ_CHG_DATE timestamp(0)
    • Last object change timestamp.

Example usage

SELECT *
FROM TABLE(sqltools.mbr_attr(
  LIBRARY_NAME => '*LIBL',
  FILE_NAME    => 'QCLSRC',
  MBR_NAME     => 'MYPGM',
  DETAILED_INFO => 'YES'
)) AS x;

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)

@bobcozzi
bobcozzi deployed to testing_environment October 6, 2026 00:49 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 6, 2026 10:52 — with GitHub Actions Active
@sebjulliand sebjulliand self-assigned this Oct 6, 2026
@sebjulliand sebjulliand added the enhancement New feature or request label Oct 6, 2026
@sebjulliand
sebjulliand self-requested a review October 6, 2026 11:02

@sebjulliand sebjulliand left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread src/api/components/mbrAttr/source.ts Outdated
Comment on lines +2 to +7

// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 by R. Cozzi, Jr.


// @author BobCozzi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for copyright here.

Suggested change
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 by R. Cozzi, Jr.
// @author BobCozzi

Comment thread src/api/components/mbrAttr/source.ts Outdated
Comment on lines +380 to +383
-- SPDX-License-Identifier: Apache-2.0
-- Copyright (c) 1996-2026 by R. Cozzi, Jr.

-- @author BobCozzi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for copyright here either.

Suggested change
-- SPDX-License-Identifier: Apache-2.0
-- Copyright (c) 1996-2026 by R. Cozzi, Jr.
-- @author BobCozzi

Comment thread src/api/components/mbrAttr/source.ts Outdated
Comment on lines +461 to +466

export function buildMbrAttrSqlSource(targetLibrary: string, version: number): string {
return MBR_ATTR_SQL_TEMPLATE
.replace(/\$\{version\}/g, String(version))
.replace(/\bsqltools\b/gi, targetLibrary.trim());
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be remove since it's unused.

Suggested change
export function buildMbrAttrSqlSource(targetLibrary: string, version: number): string {
return MBR_ATTR_SQL_TEMPLATE
.replace(/\$\{version\}/g, String(version))
.replace(/\bsqltools\b/gi, targetLibrary.trim());
}

Comment thread src/api/components/mbrAttr/source.ts Outdated
-- @author BobCozzi


CREATE or REPLACE FUNCTION sqltools.mbr_attr(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to qualify the function here if DFTRDBCOL is specified when running RUNSQLSTM I think.

Suggested change
CREATE or REPLACE FUNCTION sqltools.mbr_attr(
CREATE or REPLACE FUNCTION mbr_attr(

Comment thread src/api/components/mbrAttr/source.ts Outdated
Comment on lines +419 to +431
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to qualify the function here if DFTRDBCOL is specified when running RUNSQLSTM I think.

Suggested change
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

Comment thread src/api/components/mbrAttr/index.ts Outdated
`);

if (!row) {
return { status: "NotInstalled", remoteSignature: MbrAttr.SIGNATURE };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return a blank remoteSignature if it's not installed.

Suggested change
return { status: "NotInstalled", remoteSignature: MbrAttr.SIGNATURE };
return { status: "NotInstalled", remoteSignature: '' };

Comment thread src/api/components/mbrAttr/index.ts Outdated
const remoteVersion = Number(longComment.match(/^(\d+)/)?.[1] || -1);
const status = remoteVersion >= MbrAttr.VERSION ? "Installed" : "NeedsUpdate";

return { status, remoteSignature: MbrAttr.SIGNATURE };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/api/components/mbrAttr/index.ts Outdated
}

const sqlResult = await connection.runCommand({
command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the SQL code should not be qualified, this will create the function in the right library.

Suggested change
command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL)`
command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SQL) DFTRDBCOL(${library})`

Comment thread src/api/components/mbrAttr/index.ts Outdated
Comment on lines +45 to +66
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}`);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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}`);
}
});

@bobcozzi

bobcozzi commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

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.

@bobcozzi

bobcozzi commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

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.
I could do something like that on all the parameters for this UDTF.
FILE_NAME VARCHAR(10) CCSID(1208);

@bobcozzi

bobcozzi commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Also, why do you want me to remove the copyright and author notice?

@sebjulliand

Copy link
Copy Markdown
Member

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.

Agreed, but it's not something we can force upon the users.
The system-wide settings allow sysadmins to do that by specifying a temporary library that will be used by anyone that connects.

Also, why do you want me to remove the copyright and author notice?

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 😄

@bobcozzi

bobcozzi commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Ok I see (re the (c) notice.)
As for the function library; what I did in my CLPrompter Ext is added a function library that defaults to *TEMPLIB so it defaults and falls back to the temp lib. That way we keep temp lib as potential distinctive per user while only keeping perm objects in the function library. Of course I did not document it. I just did it as a thought experiment but it works fine.

@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 15:55 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 16:00 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 17:26 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 20:40 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 20:43 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 20:53 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 20:55 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:02 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:11 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:13 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:16 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:16 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:31 — with GitHub Actions Active
@bobcozzi
bobcozzi deployed to testing_environment October 9, 2026 21:32 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
testing_environment — 6d06a5eb Deployed Oct 9, 2026 by bobcozzi via Test runner #723
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants