Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e82ae23
Better naming for fetching user ASP
worksofliam May 1, 2025
c53e896
Use method to get configured ASP
worksofliam May 1, 2025
8bc2b3a
Ability to configure iASP
worksofliam May 1, 2025
ac11af2
test cases to prove ASPs work as expected
worksofliam May 1, 2025
88fa413
Add check in afterAll
worksofliam May 1, 2025
2cb9afa
Fix to library list order when bash is used
worksofliam May 1, 2025
08fefb6
Minor changes to default ASP config
worksofliam May 2, 2025
a48945a
Fix ASP library API to support multiple ASPs
worksofliam May 2, 2025
035f40c
Remove useless call to getjobid
worksofliam May 2, 2025
82c85e6
Add additional check to make sure the configured ASP is usable
worksofliam May 2, 2025
4f3c248
Show loading indicator when opening connection settings
worksofliam May 2, 2025
efc457c
Merge branch 'master' into feature/asp_selection
worksofliam May 5, 2025
1b4009f
Test case for getLibraryList
worksofliam May 8, 2025
bcec68e
Fix to validateLibraryList to support ASP
worksofliam May 8, 2025
a9c0af9
Merge branch 'master' into feature/asp_selection
worksofliam May 8, 2025
5dd27bf
Add large timeout for search tests
worksofliam May 8, 2025
b31f288
Increase timeouts
worksofliam May 8, 2025
fd6280c
Improvements to connection times
worksofliam May 9, 2025
ae6c3f8
Update text on ASP setting
worksofliam May 9, 2025
4bf6a98
When no libl is used, fallback to system
worksofliam May 12, 2025
7c94d5c
Rollback getLibraryList lookup
worksofliam May 15, 2025
b046014
Add additional environment type for backwards compatability
worksofliam May 15, 2025
cb5a653
Remove use of CL
worksofliam May 15, 2025
cbdab01
Additional comment for lookupLibraryIAsp
worksofliam May 15, 2025
959b1e6
Fix up test logging
worksofliam May 15, 2025
bd46718
Use cl for running SQL statements under the hood
worksofliam May 21, 2025
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
44 changes: 33 additions & 11 deletions src/api/CompileTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,39 @@ export namespace CompileTools {

case `ile`:
default:
// escape $ and # in commands
commandResult = await connection.sendQsh({
command: [
...options.noLibList ? [] : buildLiblistCommands(connection, ileSetup),
...commands.map(command =>
`${`system "${IBMi.escapeForShell(command)}"`}`,
)
].join(` && `),
directory: cwd,
...callbacks
});
if (connection.usingBash()) {
const chosenAsp = connection.getConfiguredIAsp();
const aspName = chosenAsp ? chosenAsp.name : `*NONE`;

const setAspGrp = connection.getContent().toCl(`SETASPGRP`, {
ASPGRP: aspName,
CURLIB: ileSetup.currentLibrary,
USRLIBL: ileSetup.libraryList.join(` `),
});

commandResult = await connection.sendCommand({
command: [
`cl "${setAspGrp}"`,
...commands.map(command =>
`${`cl "${IBMi.escapeForShell(command)}"`}`,
)
].join(` && `),
directory: cwd,
...callbacks
});
} else {
commandResult = await connection.sendQsh({
command: [
...options.noLibList? [] : buildLiblistCommands(connection, ileSetup),
...commands.map(command =>
`${`system "${IBMi.escapeForShell(command)}"`}`,
)
].join(` && `),
directory: cwd,
...callbacks
});
}

break;
}

Expand Down
131 changes: 102 additions & 29 deletions src/api/IBMi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ export default class IBMi {
* the root of the IFS, thus why we store it.
*/
private iAspInfo: AspInfo[] = [];
private currentAsp: string|undefined;
private libraryAsps = new Map<string, number>();
private userProfileIAsp: string|undefined;
private libraryAsps = new Map<string, number[]>();

/**
* @deprecated Will be replaced with {@link IBMi.getAllIAsps} in v3.0.0
Expand Down Expand Up @@ -620,7 +620,7 @@ export default class IBMi {
//check users default shell

if (!commandShellResult.stderr) {
let usesBash = this.shell === IBMi.bashShellPath;
const usesBash = this.usingBash();
if (!usesBash) {
// make sure chsh is installed
if (this.remoteFeatures[`chsh`]) {
Expand Down Expand Up @@ -757,7 +757,7 @@ export default class IBMi {
//This is mostly a nice to have. We grab the ASP info so user's do
//not have to provide the ASP in the settings.
try {
const resultSet = await this.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
const resultSet = await this.runSQL(`select * from qsys2.asp_info where asp_state in ('ACTIVE', 'AVAILABLE', 'VARIED ON')`);
resultSet.forEach(row => {
// Does not ever include SYSBAS/SYSTEM, only iASPs
if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
Expand All @@ -781,7 +781,27 @@ export default class IBMi {
message: `Fetching current iASP information.`
});

this.currentAsp = await this.getUserProfileAsp();
this.userProfileIAsp = await this.getUserProfileAsp();

if (this.usingBash()) {
// Set the default ASP to the current ASP if it is not set.
const chosenAspIsConfigured = this.getConfiguredIAsp();
if (chosenAspIsConfigured) {
// Double check that the configured ASP is usable.
const configuredIsUsable = await this.canUseConfiguredAsp();

if (!configuredIsUsable) {
this.config.chosenAsp = `*SYSBAS`;
}

} else {
this.config.chosenAsp = this.userProfileIAsp || `*SYSBAS`;
}
} else {
// If the user is not using bash, then we set the chosen ASP to the user profile iASP.
// since we can't change the ASP without using bash (since we use 'cl')
this.config.chosenAsp = this.userProfileIAsp || `*SYSBAS`;
}

// Fetch conversion values?
if (quickConnect() && cachedServerSettings?.jobCcsid !== null && cachedServerSettings?.userDefaultCCSID && cachedServerSettings?.qccsid) {
Expand Down Expand Up @@ -1329,21 +1349,25 @@ export default class IBMi {
* @param statements
* @returns a Result set
*/
async runSQL(statements: string, options: { fakeBindings?: (string | number)[], forceSafe?: boolean } = {}): Promise<Tools.DB2Row[]> {
async runSQL(statements: string, options: { fakeBindings?: (string | number)[], forceSafe?: boolean, ignoreDatabase?: boolean } = {}): Promise<Tools.DB2Row[]> {
const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.remoteFeatures;
const possibleChangeCommand = (this.userCcsidInvalid ? `@CHGJOB CCSID(${this.getCcsid()});\n` : '');

if (QZDFMDB2) {
// CHGJOB not required here. It will use the job CCSID, or the runtime CCSID.
let input = Tools.fixSQL(`${possibleChangeCommand}${statements}`, true);
let returningAsCsv: WrapResult | undefined;
let command = `${IBMi.locale} system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`

const chosenAsp = this.getConfiguredIAsp();
const rdbParameter = chosenAsp && options.ignoreDatabase !== true ? `'-r' '${chosenAsp.rdbName}'` : ``;

let command = `${IBMi.locale} system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t' ${rdbParameter})"`
let useCsv = options.forceSafe;

// Use custom QSH if available
if (this.canUseCqsh) {
const customQsh = this.getComponent<CustomQSh>(CustomQSh.ID)!;
command = `${IBMi.locale} ${customQsh.installPath} -c "system \\"call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')\\""`;
command = `${IBMi.locale} ${customQsh.installPath} -c "system \\"call QSYS/QZDFMDB2 PARM('-d' '-i' '-t' ${rdbParameter})\\""`;
}

if (this.requiresTranslation) {
Expand Down Expand Up @@ -1466,7 +1490,7 @@ export default class IBMi {
}

private async getUserProfileAsp(): Promise<string|undefined> {
const [currentRdb] = await this.runSQL(`values current_server`);
const [currentRdb] = await this.runSQL(`values current_server`, {ignoreDatabase: true});

if (currentRdb) {
const key = Object.keys(currentRdb)[0];
Expand All @@ -1479,6 +1503,17 @@ export default class IBMi {
}
}

private async canUseConfiguredAsp(): Promise<boolean> {
try {
const [row] = await this.runSQL(`values current_server`);
return true;
} catch (e) {
// If we can't run the SQL, then we can't use the configured ASP.
console.log(e);
return false;
}
}

getAllIAsps() {
return this.iAspInfo;
}
Expand All @@ -1491,40 +1526,78 @@ export default class IBMi {
asp = this.iAspInfo.find(asp => asp.id === by);
}

if (asp) {
return asp;
}
return asp;
}

getIAspName(by: string|number): string|undefined {
return this.getIAspDetail(by)?.name;
}

getCurrentIAspName() {
return this.currentAsp;
getCurrentUserIAspName() {
return this.userProfileIAsp;
}

getConfiguredIAsp(): AspInfo|undefined {
const selected = this.config?.chosenAsp;

return selected ? this.getIAspDetail(selected) : undefined;
}

/**
* Will return the name of the iASP in which the given library resides.
* If the library is not in an iASP, it will return undefined.
* If multiple iASPS contain a library with this name, then either:
* - If the user has configured an iASP for this library, it will return that iASP name.
* - If the user has not configured an iASP for this library, it will return undefined.
*/
async lookupLibraryIAsp(library: string): Promise<string|undefined> {
let foundNumber = this.libraryAsps.get(library);

if (!foundNumber) {
const [row] = await this.runSQL(`SELECT IASP_NUMBER FROM TABLE(QSYS2.LIBRARY_INFO('${this.sysNameInAmerican(library)}'))`);
const iaspNumber = Number(row?.IASP_NUMBER);
if (iaspNumber >= 0) {
this.libraryAsps.set(library, iaspNumber);
foundNumber = iaspNumber;
let foundAsps = this.libraryAsps.get(library) || [];

if (foundAsps.length === 0) {
const rows = await this.runSQL(`SELECT IASP_NUMBER from table(qsys2.object_statistics('*ALLAVL', '*LIB')) x where x.objname = '${this.sysNameInAmerican(library)}'`);

for (const row of rows) {
if (row?.IASP_NUMBER !== undefined && row?.IASP_NUMBER !== null) {
const aspNumber = Number(row.IASP_NUMBER);

if (aspNumber > 0) {
foundAsps.push(aspNumber);
}
break;
}
}
}

if (foundNumber) {
return this.getIAspName(foundNumber);
if (foundAsps.length > 0) {
// Cache the found ASPs for this library
this.libraryAsps.set(library, foundAsps);
}
}

return this.getLibraryIAsp(library);
}

getLibraryIAsp(library: string) {
const found = this.libraryAsps.get(library);
if (found && found >= 0) {
return this.getIAspName(found);
getLibraryIAsp(library: string): string|undefined {
const foundAsps = this.libraryAsps.get(library) || [];

if (foundAsps.length === 1) {
// If there is only one ASP, return it
return this.getIAspName(foundAsps[0]);
}
else if (foundAsps.length > 1) {
const userConfigured = this.getConfiguredIAsp();
if (userConfigured && foundAsps.includes(userConfigured.id)) {
// If the user has configured an iASP for this library, return it
return userConfigured.name;
} else {
return undefined; // Multiple ASPs found, but no user configured iASP
}
}
}

getLibraryIAsps(library: string): string[] {
const foundAsps = this.libraryAsps.get(library) || [];

return foundAsps.map(asp => this.getIAspName(asp)).filter(name => name !== undefined);
}

/**
Expand Down
16 changes: 14 additions & 2 deletions src/api/IBMiContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,19 @@ export default class IBMiContent {

if (localLibrary !== `QSYS`) {
if (!await this.checkObject({ library: "QSYS", name: localLibrary, type: "*LIB" })) {
throw new Error(`Library ${localLibrary} does not exist.`);
const configuredAsp = this.ibmi.getConfig().chosenAsp;
const existsInLibrary = await this.ibmi.lookupLibraryIAsp(localLibrary);
const existsInMultiple = this.ibmi.getLibraryIAsps(localLibrary);

if (existsInMultiple.length >= 2) {
throw new Error(`Library ${localLibrary} exists in multiple IASPs: ${existsInMultiple.join(', ')}. Please specify the IASP in the connection settings.`);
}
else if (existsInLibrary && existsInLibrary !== configuredAsp) {
throw new Error(`Library ${localLibrary} is in IASP ${existsInLibrary}, but the current connection is not configured to use that IASP.`);

} else {
throw new Error(`Library ${localLibrary} does not exist.`);
}
}
}

Expand Down Expand Up @@ -877,7 +889,7 @@ export default class IBMiContent {
// Escape names for shell
const pathList = files
.map(file => {
const asp = file.asp || this.ibmi.getCurrentIAspName();
const asp = file.asp || this.ibmi.getConfiguredIAsp()?.name;
if (asp && asp.length > 0) {
return [
Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), asp, true),
Expand Down
1 change: 1 addition & 0 deletions src/api/Tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export namespace Tools {
const data = output.split(`\n`).filter(line => {
const trimmed = line.trim();
return trimmed !== `DB2>` &&
!trimmed.startsWith('WARNING') && // TODO: Ignore warnings (TODO: specifically for multi-threaded, be careful because of locale changes)
!trimmed.startsWith(`DB20`) && // Notice messages
!/COMMAND .+ COMPLETED WITH EXIT STATUS \d+/.test(trimmed) && // @CL command execution output
trimmed !== `?>`;
Expand Down
1 change: 1 addition & 0 deletions src/api/configuration/config/ConnectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function initialize(parameters: Partial<ConnectionConfig>): ConnectionConfig {
ifsShortcuts: parameters.ifsShortcuts || [],
/** Default auto sorting of shortcuts to off */
autoSortIFSShortcuts: parameters.autoSortIFSShortcuts || false,
chosenAsp: parameters.chosenAsp || `*SYSBAS`,
homeDirectory: parameters.homeDirectory || `.`,
/** Undefined means not created, so default to on */
tempLibrary: parameters.tempLibrary || `ILEDITOR`,
Expand Down
1 change: 1 addition & 0 deletions src/api/configuration/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ConnectionConfig extends ConnectionProfile {
connectionProfiles: ConnectionProfile[];
commandProfiles: CommandProfile[];
autoSortIFSShortcuts: boolean;
chosenAsp: string;
tempLibrary: string;
tempDir: string;
sourceFileCCSID: string;
Expand Down
Loading
Loading