Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions PR_DESCRIPTION_OBJ_ATTR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# OBJ_ATTR SQL UDTF

This PR adds a new managed SQL UDTF component named `OBJ_ATTR` that exposes IBM i object and member metadata through a standard table function. It is designed to surface information available from `Qp0lGetAttr` and related APIs in a SQL-friendly format so object-level and member-level attributes can be queried directly without custom API logic for each lookup.

The function supports both direct object-name/library access and UTF-8 `/QSYS.LIB/...` path-based object names. It can return metadata for a file, library object, or member when supplied as a path or as explicit parameters.

## Input parameters

- `LIBRARY_NAME` varchar(10) default `*LIBL`
- Library containing the object.
- `OBJECT_NAME` varchar(10) default `NULL`
- Object name to inspect. May be blank or omitted when `PATH_NAME` is supplied.
- `OBJTYPE` varchar(10) default `*FILE`
- Object type, such as `*FILE`, `*PGM`, `*SRVPGM`, and so on.
- `MBR_NAME` varchar(10) default `NULL`
- Member name. When set for a file, metadata for the member is returned.
- `PATH_NAME` varchar(4096) CCSID 1208 default `NULL`
- Full UTF-8 `/QSYS.LIB/...` object path or IFS path. When supplied, it takes precedence over the direct-name parameters.
- `CVTOPTION` varchar(10) default `NO`
- Controls whether the path is converted to upper or lower case before lookup. The default is `NO`, so the path is used as-is unless a conversion is explicitly requested.

## Output columns

- `created_Date` timestamp(0)
- Object creation date/time.
- `access_Date` timestamp(0)
- Last access time.
- `changed_Date` timestamp(0)
- Last modified/changed time.
- `data_changed_date` timestamp(0)
- Last data-change time.
- `last_used` timestamp(0)
- Last-used time from usage information.
- `last_used_days` int
- Days since last use.
- `last_used_reset` timestamp(0)
- Last reset time for use tracking.
- `Allocate_size` bigint
- Allocated storage size.
- `data_size` bigint
- Data size.
- `object_CCSID` int
- CCSID of the object.
- `ASP_Nbr` smallint
- ASP number.
- `OBJTEXT` varchar(50) CCSID 1208
- Object text description.
- `LIBNAME` varchar(10)
- Library name.
- `OBJNAME` varchar(10)
- Object name.
- `OBJTYPE` varchar(10)
- Object type.
- `MBRNAME` varchar(10)
- Member name, if requested.
- `SRCTYPE` varchar(10)
- Member source type, when applicable.
- `ASPNAME` varchar(10)
- ASP name.

## Example usage

```sql
SELECT *
FROM TABLE(sqltools.OBJ_ATTR(
LIBRARY_NAME => '*LIBL',
OBJECT_NAME => 'QRPGLESRC',
OBJTYPE => '*FILE',
MBR_NAME => 'MYPGM'
)) AS x;
```

```sql
SELECT *
FROM TABLE(sqltools.OBJ_ATTR(
PATH_NAME => '/QSYS.LIB/MYLIB.LIB/QRPGLESRC.FILE/MYPGM.MBR',
CVTOPTION => 'NO'
)) AS x;
```

## Implementation note

The SQL template intentionally keeps the `sqltools` schema/library name as a placeholder in source form. During installation, the component writes the generated script to the target temp library and replaces the placeholder with the active library name via the install-time builder function. This preserves the source template while ensuring the routine is created in the correct library and schema for the connected IBM i environment.

## Why this matters

This UDTF makes it easy to query object and source-member metadata from SQL in a consistent, reusable way. It is especially useful for IDE tooling, metadata inspection, and diagnostics where the caller needs object-level or member-level attributes without hand-rolling API calls or parsing raw IBM i metadata structures.
21 changes: 21 additions & 0 deletions PR_REVIEW_MEMORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# OBJ_ATTR component PR review note (2026-10-05)

- Branch: feat/obj-attr-udtf-component
- Commit: fc043426
- PR URL: https://github.com/bobcozzi/vscode-ibmi/pull/new/feat/obj-attr-udtf-component
- Worktree: /Users/cozzi/Downloads/projects/vscode-ibmi-pr-mbr-attr

## Files changed
- src/extension.ts
- src/api/components/objAttr/index.ts
- src/api/components/objAttr/source.ts

## Quick review commands
- git show --stat fc043426
- git show fc043426 -- src/extension.ts src/api/components/mbrAttr/index.ts src/api/components/mbrAttr/source.ts
- git status -sb

## Change summary
- Added managed component class for OBJ_ATTR SQL UDTF deployment.
- Added embedded RPGLE source and SQL template with version token replacement.
- Registered the new component in extension activation so lifecycle check/update runs with component manager.
80 changes: 80 additions & 0 deletions src/api/components/objAttr/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import path from "path";
import IBMi from "../../IBMi";
import { ComponentIdentification, IBMiComponent, SecureComponentState } from "../component";
import { getSource_objattr, OBJ_ATTR_RPGLE_SOURCE } from "./source";

export class ObjAttr implements IBMiComponent {
static readonly ID = "obj_attr";
static readonly VERSION = 1;
static readonly SIGNATURE = `OBJ_ATTR:${ObjAttr.VERSION}`;
static readonly PGM_NAME = "OBJ_ATTR";
static readonly SPECIFIC_NAME = "OBJ_ATTR";

getIdentification(): ComponentIdentification {
return {
name: ObjAttr.ID,
version: ObjAttr.VERSION,
signature: ObjAttr.SIGNATURE
};
}

async getRemoteState(connection: IBMi): Promise<SecureComponentState> {
const library = connection.getConfig().tempLibrary.toUpperCase();
const [row] = await connection.runSQL(/* sql */`
select cast(long_comment as varchar(200)) as LONG_COMMENT
from qsys2.sysroutines
where routine_type = 'FUNCTION'
and routine_schema = '${library}'
and specific_name = '${ObjAttr.SPECIFIC_NAME}'
fetch first row only
`);

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

const longComment = String(row.LONG_COMMENT || "").trim();
const remoteVersion = Number(longComment.match(/^(\d+)/)?.[1] || -1);
const status = remoteVersion >= ObjAttr.VERSION ? "Installed" : "NeedsUpdate";

return { status, remoteSignature: ObjAttr.SIGNATURE };
}

async update(connection: IBMi, installDirectory: string): Promise<SecureComponentState> {
const library = connection.getConfig().tempLibrary.toUpperCase();
const currentState = await this.getRemoteState(connection);
const action = currentState.status === "NotInstalled" ? "Installing" : "Updating";
connection.appendOutput(`${action} component ${ObjAttr.ID} (${ObjAttr.VERSION}) in ${library}\n`);

return connection.withTempDirectory(async componentDir => {
const rpglePath = path.posix.join(componentDir, `${ObjAttr.PGM_NAME}.RPGLE`);
const sqlPath = path.posix.join(componentDir, `${ObjAttr.PGM_NAME}.SQL`);

connection.appendOutput(`\t${action} ${ObjAttr.PGM_NAME} sources in ${componentDir}\n`);
await connection.getContent().writeStreamfileRaw(rpglePath, OBJ_ATTR_RPGLE_SOURCE);
await connection.getContent().writeStreamfileRaw(sqlPath, getSource_objattr(library, ObjAttr.VERSION));

connection.appendOutput(`\tCompiling program ${library}/${ObjAttr.PGM_NAME} (CRTBNDRPG)\n`);
const compileResult = await connection.runCommand({
command: `QSYS/CRTBNDRPG PGM(${library}/${ObjAttr.PGM_NAME}) SRCSTMF('${rpglePath}') OPTION(*EVENTF) DBGVIEW(*NONE) TGTCCSID(*JOB)`
});
if (compileResult.code !== 0) {
throw new Error(`Failed to compile ${ObjAttr.PGM_NAME}: ${compileResult.stderr || compileResult.stdout}`);
}

connection.appendOutput(`\t${action} routine ${library}.${ObjAttr.SPECIFIC_NAME} (RUNSQLSTM)\n`);
const sqlResult = await connection.runCommand({
command: `QSYS/RUNSQLSTM SRCSTMF('${sqlPath}') COMMIT(*NONE) NAMING(*SYS) OPTION(*ERRLIST)`
});

if (sqlResult.code !== 0) {
throw new Error(`Failed to install SQL routine ${library}.${ObjAttr.SPECIFIC_NAME}: ${sqlResult.stderr || sqlResult.stdout}`);
}

const completedAction = action === "Installing" ? "Installed" : "Updated";
connection.appendOutput(`\t${completedAction} component ${ObjAttr.ID} (${ObjAttr.SIGNATURE})\n`);

return this.getRemoteState(connection);
});
}
}
Loading
Loading