IBM i: The SQL Way · #19

Run IBM i CL Commands from SQL and Control Job-Log Output

Use the QSYS2.QCMDEXC scalar function to execute a CL command from SQL, check its return value, and control whether the command text is written to the job log.

Related native optionQCMDEXC API or CALL QSYS2.QCMDEXC
IBM iSQLCLQCMDEXCJob LogAutomationSecurity

QSYS2.QCMDEXC allows an SQL statement to execute an IBM i CL command. The current scalar-function enhancement adds control over whether the command text is written to the job log, which improves diagnostics but also creates an important security decision.

IBM provides QCMDEXC in two SQL forms:

QSYS2.QCMDEXC procedure
QSYS2.QCMDEXC scalar function

The procedure is useful for a direct command call.

The scalar function is useful when the command result must participate in an SQL expression, query, conditional operation, or generated report.

Basic scalar-function example

VALUES QSYS2.QCMDEXC(
    'ADDLIBLE LIB(MYLIB)'
);

The scalar function returns:

1    Command completed successfully
-1   Command execution failed

The command can be up to 32,000 characters.

New PRINT parameter

The current enhancement adds:

PRINT

to the scalar function.

Supported values are:

NONE
ERROR
VERBOSE

NONE

VALUES QSYS2.QCMDEXC(
    COMMAND => 'ADDLIBLE LIB(MYLIB)',
    PRINT   => 'NONE'
);

The command text is not written to the job log.

This is the default.

ERROR

VALUES QSYS2.QCMDEXC(
    COMMAND => 'ADDLIBLE LIB(MYLIB)',
    PRINT   => 'ERROR'
);

The command text is written to the job log only when command execution fails.

This can provide useful diagnostic context without logging every successful command.

VERBOSE

VALUES QSYS2.QCMDEXC(
    COMMAND => 'ADDLIBLE LIB(MYLIB)',
    PRINT   => 'VERBOSE'
);

The command text is always written to the job log.

Use this only when the command contains no confidential information.

Do not log secrets.

IBM specifically warns against using ERROR or VERBOSE when the CL command contains sensitive information such as a password. Job logs can be viewed, copied, archived, forwarded to monitoring systems, and retained longer than expected.

Check the return value

SELECT
    CASE QSYS2.QCMDEXC(
        COMMAND => 'ADDLIBLE LIB(MYLIB)',
        PRINT   => 'ERROR'
    )
        WHEN 1 THEN 'COMMAND COMPLETED'
        ELSE 'COMMAND FAILED'
    END AS COMMAND_STATUS
FROM SYSIBM.SYSDUMMY1;

The integer return value is useful when an SQL-driven process must record success or failure.

Run a command for selected rows

IBM’s documentation demonstrates using QCMDEXC with active-job information.

A simplified administrative example is:

SELECT
    JOB_NAME,
    CASE
        WHEN QSYS2.QCMDEXC(
                 COMMAND =>
                     'QSYS/HLDJOB JOB('
                     CONCAT JOB_NAME
                     CONCAT ')',
                 PRINT => 'ERROR'
             ) = 1
          THEN 'JOB HELD'
        ELSE 'JOB NOT HELD'
    END AS COMMAND_RESULT
FROM TABLE(
    QSYS2.ACTIVE_JOB_INFO(
        DETAILED_INFO => 'ALL'
    )
)
WHERE SQL_STATEMENT_START_TIMESTAMP
      < CURRENT TIMESTAMP - 2 HOURS;

This is powerful—and potentially disruptive.

Do not execute a command against a result set until the selection query has been reviewed independently.

First run:

SELECT
    JOB_NAME,
    SQL_STATEMENT_START_TIMESTAMP,
    SQL_STATEMENT_TEXT
FROM TABLE(
    QSYS2.ACTIVE_JOB_INFO(
        DETAILED_INFO => 'ALL'
    )
)
WHERE SQL_STATEMENT_START_TIMESTAMP
      < CURRENT TIMESTAMP - 2 HOURS;

Only after validating the target rows should the command-execution expression be considered.

Build a command from table data

SELECT
    OBJECT_LIBRARY,
    OBJECT_NAME,
    QSYS2.QCMDEXC(
        COMMAND =>
            'QSYS/CHKOBJ OBJ('
            CONCAT TRIM(OBJECT_LIBRARY)
            CONCAT '/'
            CONCAT TRIM(OBJECT_NAME)
            CONCAT ') OBJTYPE(*FILE)',
        PRINT => 'ERROR'
    ) AS COMMAND_RESULT
FROM MYTOOLS.OBJECT_CHECK_QUEUE
WHERE STATUS = 'READY';

Dynamic command construction must validate every value.

Even when the values come from a database table, the table may contain:

Validate syntax first

Combine CHECK_COMMAND_SYNTAX with QCMDEXC:

WITH COMMAND_TO_RUN (COMMAND_TEXT) AS
(
    VALUES
      'ADDLIBLE LIB(MYLIB)'
)
SELECT
    COMMAND_TEXT,
    CASE
        WHEN SYSTOOLS.CHECK_COMMAND_SYNTAX(
                 COMMAND_TEXT
             ) IS NOT TRUE
          THEN -2
        ELSE QSYS2.QCMDEXC(
                 COMMAND => COMMAND_TEXT,
                 PRINT   => 'ERROR'
             )
    END AS COMMAND_RESULT
FROM COMMAND_TO_RUN;

Possible application meanings:

 1   Command succeeded
-1   Command execution failed
-2   Command syntax was rejected before execution

Syntax validation does not prove that a command is authorized, safe, or appropriate.

Use an allowlist

A secure command service should not accept arbitrary user-supplied CL.

A simple table-driven allowlist could contain:

COMMAND_NAME
ALLOWED_ENVIRONMENT
REQUIRES_APPROVAL
LOGGING_POLICY

Before calling QCMDEXC, validate:

Procedure form

For a direct call where no scalar return value is needed:

CALL QSYS2.QCMDEXC(
    'ADDLIBLE LIB(MYLIB)'
);

Use the procedure when:

Use the scalar function when:

Authority

The user invoking QCMDEXC must have the authority required by the CL command being executed.

QCMDEXC does not bypass IBM i object authority.

For example, this may be syntactically valid:

VALUES QSYS2.QCMDEXC(
    'DLTLIB LIB(MYLIB)'
);

but it should fail unless the executing profile has the required authority.

That failure is a protection—not an inconvenience to work around with excessive authority.

Adopted authority and execution context

When QCMDEXC is called from:

review the complete authority model.

Questions include:

The same command can behave differently under another job context.

Avoid relying on the library list

For automation, qualify command and object names where practical:

VALUES QSYS2.QCMDEXC(
    COMMAND =>
      'QSYS/CHKOBJ OBJ(MYLIB/MYFILE) OBJTYPE(*FILE)',
    PRINT => 'ERROR'
);

Qualification improves repeatability and reduces the chance that an unexpected library-list entry changes the target.

Store command results

CREATE TABLE MYTOOLS.COMMAND_AUDIT
(
    AUDIT_ID          BIGINT
                      GENERATED ALWAYS AS IDENTITY,
    REQUEST_TIMESTAMP TIMESTAMP NOT NULL,
    REQUESTED_BY      VARCHAR(128) NOT NULL,
    COMMAND_TEXT      VARCHAR(32000) NOT NULL,
    COMMAND_RESULT    INTEGER,
    COMPLETED_TIMESTAMP TIMESTAMP
);

A production audit design should avoid storing credentials or sensitive parameter values.

Consider storing:

Do not run destructive commands from a SELECT casually

A scalar function can be invoked once for every qualifying row.

A query that unexpectedly returns 500 rows may execute 500 commands.

Before adding QCMDEXC:

1. Run the selection by itself.
2. Confirm the exact row count.
3. Review duplicate rows.
4. Confirm idempotency.
5. Add a narrow environment filter.
6. Consider a transaction-independent work queue.
7. Test with a non-destructive command.

Job-log guidance

Use:

PRINT => 'NONE'

when:

Use:

PRINT => 'ERROR'

when:

Use:

PRINT => 'VERBOSE'

when:

Release requirement

The scalar function itself is available on earlier supported releases.

The PRINT parameter is enhanced at:

IBM i 7.6 — Db2 Group PTF SF99960 Level 3
IBM i 7.5 — Db2 Group PTF SF99950 Level 12

On an earlier PTF level, a call using PRINT may fail because the parameter is not recognized.

A safe workflow

1. Build the target SELECT without QCMDEXC.
2. Validate the row count and exact objects or jobs.
3. Validate the CL command syntax.
4. Apply a command and parameter allowlist.
5. Confirm the executing profile and authority model.
6. Choose a PRINT policy.
7. Execute the smallest possible batch.
8. Capture the return result and SQL diagnostics.
9. Review the job log and audit journal.
10. Confirm the intended system change.

Final takeaway

QSYS2.QCMDEXC creates a powerful bridge between SQL selection logic and IBM i CL commands.

The new PRINT parameter improves control over diagnostics, but it also makes command-content protection part of the design.

Use the function for controlled automation—not as an unrestricted command shell hidden inside SQL.

References

IBM documentation and support references used for this entry.

Comments

Share your thoughts, questions, or real-world IBM i experiences related to this article.