From e047c0b6f1d984428bef357a42650a6b4cf17286 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 15:26:42 -0400 Subject: [PATCH 01/16] Move runSQL API and improved CCSID logic Signed-off-by: worksofliam --- src/api/IBMi.ts | 71 +++++++++++++++++++++++++++++----- src/api/IBMiContent.ts | 39 +++++-------------- src/filesystems/qsys/QSysFs.ts | 5 ++- src/views/helpView.ts | 6 ++- 4 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 1a3eb47e3..c641c7ce3 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -45,6 +45,9 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures ]; export default class IBMi { + private qccsid: number; + private userDefaultCCSID: number; + client: node_ssh.NodeSSH; currentHost: string; currentPort: number; @@ -54,8 +57,6 @@ export default class IBMi { defaultUserLibraries: string[]; outputChannel?: vscode.OutputChannel; aspInfo: { [id: number]: string }; - qccsid: number; - defaultCCSID: number; remoteFeatures: { [name: string]: string | undefined }; variantChars: { american: string, local: string }; lastErrors: object[]; @@ -83,7 +84,7 @@ export default class IBMi { */ this.aspInfo = {}; this.qccsid = CCSID_SYSVAL; - this.defaultCCSID = 0; + this.userDefaultCCSID = 0; this.remoteFeatures = { git: undefined, @@ -619,7 +620,7 @@ export default class IBMi { if (quickConnect === true && cachedServerSettings?.qccsid !== null && cachedServerSettings?.variantChars && cachedServerSettings?.defaultCCSID) { this.qccsid = cachedServerSettings.qccsid; this.variantChars = cachedServerSettings.variantChars; - this.defaultCCSID = cachedServerSettings.defaultCCSID; + this.userDefaultCCSID = cachedServerSettings.defaultCCSID; } else { progress.report({ message: `Fetching conversion values.` @@ -642,7 +643,7 @@ export default class IBMi { try { const [activeJob] = await runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`); - this.defaultCCSID = Number(activeJob.DEFAULT_CCSID); + this.userDefaultCCSID = Number(activeJob.DEFAULT_CCSID); } catch (error) { const [defaultCCSID] = (await this.runCommand({ command: "DSPJOB OPTION(*DFNA)" })) @@ -652,13 +653,14 @@ export default class IBMi { const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim()); if (defaultCCSCID && !isNaN(defaultCCSCID)) { - this.defaultCCSID = defaultCCSCID; + this.userDefaultCCSID = defaultCCSCID; } } - if (this.config.enableSQL && this.qccsid === 65535) { + const ccsidDetail = this.getEncoding() + if (this.config.enableSQL && ccsidDetail.fallback) { this.config.enableSQL = false; - vscode.window.showErrorMessage(`QCCSID is set to 65535. Using fallback methods to access the IBM i file systems.`); + vscode.window.showErrorMessage(`CCSID is set to ${ccsidDetail.ccsid}. Using fallback methods to access the IBM i file systems.`); } progress.report({ @@ -691,7 +693,7 @@ export default class IBMi { } if ((this.qccsid < 1 || this.qccsid === 65535)) { - this.outputChannel?.appendLine(`\nUser CCSID is ${this.qccsid}; falling back to using default CCSID ${this.defaultCCSID}\n`); + this.outputChannel?.appendLine(`\nUser CCSID is ${this.qccsid}; falling back to using default CCSID ${this.userDefaultCCSID}\n`); } // give user option to set bash as default shell. @@ -882,7 +884,7 @@ export default class IBMi { badDataAreasChecked: true, libraryListValidated: true, pathChecked: true, - defaultCCSID: this.defaultCCSID + defaultCCSID: this.userDefaultCCSID }); //Keep track of variant characters that can be uppercased @@ -1231,4 +1233,53 @@ export default class IBMi { return name.toLocaleUpperCase(); } } + + + /** + * Run SQL statements. + * Each statement must be separated by a semi-colon and a new line (i.e. ;\n). + * If a statement starts with @, it will be run as a CL command. + * + * @param statements + * @returns a Result set + */ + async runSQL(statements: string): Promise { + const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.remoteFeatures; + + if (QZDFMDB2) { + const ccsidDetail = this.getEncoding(); + const possibleChangeCommand = ccsidDetail.fallback ? `@CHGJOB CCSID(${ccsidDetail.fallback});\n` : ''; + + const output = await this.sendCommand({ + command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`, + stdin: Tools.fixSQL(`${possibleChangeCommand}${statements}`) + }) + + if (output.stdout) { + return Tools.db2Parse(output.stdout); + } else { + throw new Error(`There was an error running the SQL statement.`); + } + + } else { + throw new Error(`There is no way to run SQL on this system.`); + } + } + + getEncoding(): { invalid: boolean, fallback: boolean, ccsid: number } { + const fallback = (this.qccsid && this.userDefaultCCSID && (this.qccsid < 1 || this.qccsid === 65535) && this.userDefaultCCSID > 0) ? true : false; + const ccsid = fallback ? this.userDefaultCCSID : this.qccsid; + return { + fallback, + ccsid, + invalid: (ccsid === 65535 || ccsid < 1) + }; + } + + getCcsids() { + return { + qccsid: this.qccsid, + userDefaultCCSID: this.userDefaultCCSID + }; + } } \ No newline at end of file diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index 6c148e887..84396e8da 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -156,7 +156,7 @@ export default class IBMiContent { if (this.ibmi.dangerousVariants && new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { copyResult = { code: 0, stdout: '', stderr: '' }; try { - await this.runSQL([ + await this.ibmi.runSQL([ `@QSYS/CPYF FROMFILE(${library}/${sourceFile}) TOFILE(QTEMP/QTEMPSRC) FROMMBR(${member}) TOMBR(TEMPMEMBER) MBROPT(*REPLACE) CRTFILE(*YES);`, `@QSYS/CPYTOSTMF FROMMBR('${Tools.qualifyPath("QTEMP", "QTEMPSRC", "TEMPMEMBER", undefined)}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID});` ].join("\n")); @@ -231,7 +231,7 @@ export default class IBMiContent { if (this.ibmi.dangerousVariants && new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { copyResult = { code: 0, stdout: '', stderr: '' }; try { - await this.runSQL([ + await this.ibmi.runSQL([ `@QSYS/CPYF FROMFILE(${library}/${sourceFile}) FROMMBR(${member}) TOFILE(QTEMP/QTEMPSRC) TOMBR(TEMPMEMBER) MBROPT(*REPLACE) CRTFILE(*YES);`, `@QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${Tools.qualifyPath("QTEMP", "QTEMPSRC", "TEMPMEMBER", undefined)}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`, `@QSYS/CPYF FROMFILE(QTEMP/QTEMPSRC) FROMMBR(TEMPMEMBER) TOFILE(${library}/${sourceFile}) TOMBR(${member}) MBROPT(*REPLACE);` @@ -281,7 +281,7 @@ export default class IBMiContent { * @returns result set */ runStatements(...statements: string[]): Promise { - return this.runSQL(statements.map(s => s.trimEnd().endsWith(`;`) ? s : `${s};`).join(`\n`)); + return this.ibmi.runSQL(statements.map(s => s.trimEnd().endsWith(`;`) ? s : `${s};`).join(`\n`)); } /** @@ -291,29 +291,10 @@ export default class IBMiContent { * * @param statements * @returns a Result set + * @deprecated Use `IBMi#runSQL` instead */ - async runSQL(statements: string): Promise { - const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.ibmi.remoteFeatures; - - if (QZDFMDB2) { - if (this.chgJobCCSID === undefined) { - this.chgJobCCSID = (this.ibmi.qccsid < 1 || this.ibmi.qccsid === 65535) && this.ibmi.defaultCCSID > 0 ? `@CHGJOB CCSID(${this.ibmi.defaultCCSID});\n` : ''; - } - - const output = await this.ibmi.sendCommand({ - command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`, - stdin: Tools.fixSQL(`${this.chgJobCCSID}${statements}`) - }) - - if (output.stdout) { - return Tools.db2Parse(output.stdout); - } else { - throw new Error(`There was an error running the SQL statement.`); - } - - } else { - throw new Error(`There is no way to run SQL on this system.`); - } + runSQL(statements: string) { + return this.ibmi.runSQL(statements); } /** @@ -322,7 +303,7 @@ export default class IBMiContent { async getLibraryListFromCommand(ileCommand: string): Promise<{ currentLibrary: string; libraryList: string[]; } | undefined> { if (this.ibmi.remoteFeatures[`GETNEWLIBL.PGM`]) { const tempLib = this.config.tempLibrary; - const resultSet = await this.runSQL(`CALL ${tempLib}.GETNEWLIBL('${ileCommand.replace(new RegExp(`'`, 'g'), `''`)}')`); + const resultSet = await this.ibmi.runSQL(`CALL ${tempLib}.GETNEWLIBL('${ileCommand.replace(new RegExp(`'`, 'g'), `''`)}')`); let result = { currentLibrary: `QGPL`, @@ -358,7 +339,7 @@ export default class IBMiContent { if (!member) member = file; //Incase mbr is the same file if (file === member && this.config.enableSQL) { - const data = await this.runSQL(`SELECT * FROM ${library}.${file}`); + const data = await this.ibmi.runSQL(`SELECT * FROM ${library}.${file}`); if (deleteTable && this.config.autoClearTempData) { await this.ibmi.runCommand({ @@ -433,7 +414,7 @@ export default class IBMiContent { from table( SYSTOOLS.SPLIT( INPUT_LIST => '${libraries.toString()}', DELIMITER => ',' ) ) libs , table( QSYS2.OBJECT_STATISTICS( OBJECT_SCHEMA => 'QSYS', OBJTYPELIST => '*LIB', OBJECT_NAME => libs.ELEMENT ) ) os `; - results = await this.runSQL(statement); + results = await this.ibmi.runSQL(statement); } else { results = await this.getQTempTable(libraries.map(library => `@DSPOBJD OBJ(QSYS/${library}) OBJTYPE(*LIB) DETAIL(*TEXTATR) OUTPUT(*OUTFILE) OUTFILE(QTEMP/LIBLIST) OUTMBR(*FIRST *ADD)`), "LIBLIST"); if (results.length === 1 && !results[0].ODOBNM?.toString().trim()) { @@ -672,7 +653,7 @@ export default class IBMiContent { ${singleMemberExtension ? `And TYPE Like '${singleMemberExtension}'` : ''} Order By ${sort.order === 'name' ? 'NAME' : 'CHANGED'} ${!sort.ascending ? 'DESC' : 'ASC'}`; - const results = await this.runSQL(statement); + const results = await this.ibmi.runSQL(statement); if (results.length) { const asp = this.ibmi.aspInfo[Number(results[0].ASP)]; return results.map(result => ({ diff --git a/src/filesystems/qsys/QSysFs.ts b/src/filesystems/qsys/QSysFs.ts index 124f83ccb..fd621ca9b 100644 --- a/src/filesystems/qsys/QSysFs.ts +++ b/src/filesystems/qsys/QSysFs.ts @@ -65,8 +65,9 @@ export class QSysFS implements vscode.FileSystemProvider { if (connection.remoteFeatures[`QZDFMDB2.PGM`]) { this.extendedMemberSupport = true; this.sourceDateHandler.changeSourceDateMode(config.sourceDateMode); - if (connection.qccsid === 65535) { - vscode.window.showWarningMessage(`Source date support is enabled, but QCCSID is 65535. If you encounter problems with source date support, please disable it in the settings.`); + const ccsidDetail = connection.getEncoding(); + if (ccsidDetail.invalid) { + vscode.window.showWarningMessage(`Source date support is enabled, but CCSID is 65535. If you encounter problems with source date support, please disable it in the settings.`); } } else { vscode.window.showErrorMessage(`Source date support is enabled, but the remote system does not support SQL. Source date support will be disabled.`); diff --git a/src/views/helpView.ts b/src/views/helpView.ts index c4ce77c29..341c5d317 100644 --- a/src/views/helpView.ts +++ b/src/views/helpView.ts @@ -146,14 +146,16 @@ async function getRemoteSection() { } } + const ccsids = connection.getCcsids(); + return [ createSection(`Remote system`, '|Setting|Value|', '|-|-|', `|IBM i OS|${osVersion?.OS || '?'}|`, `|Tech Refresh|${osVersion?.TR || '?'}|`, - `|CCSID|${connection.qccsid || '?'}|`, - `|Default CCSID|${connection.defaultCCSID || '?'}|`, + `|QCCSID|${ccsids.qccsid || '?'}|`, + `|Default CCSID|${ccsids.userDefaultCCSID || '?'}|`, `|SQL|${config.enableSQL ? 'Enabled' : 'Disabled'}`, `|Source dates|${config.enableSourceDates ? 'Enabled' : 'Disabled'}`, '', From e80800111b6d43f5a1150e4f1fa2e05e150c8239 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 17:21:23 -0400 Subject: [PATCH 02/16] APIs for getting a valid CCSID and SQL support Signed-off-by: worksofliam --- src/api/IBMi.ts | 102 ++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index c641c7ce3..1735855cf 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -45,21 +45,32 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures ]; export default class IBMi { - private qccsid: number; - private userDefaultCCSID: number; + private qccsid: number = 65535; + private userDefaultCCSID: number = CCSID_SYSVAL; client: node_ssh.NodeSSH; - currentHost: string; - currentPort: number; - currentUser: string; - currentConnectionName: string; - tempRemoteFiles: { [name: string]: string }; - defaultUserLibraries: string[]; + currentHost: string = ``; + currentPort: number = 22; + currentUser: string = ``; + currentConnectionName: string = ``; + tempRemoteFiles: { [name: string]: string } = {}; + defaultUserLibraries: string[] = []; outputChannel?: vscode.OutputChannel; - aspInfo: { [id: number]: string }; + + /** + * Used to store ASP numbers and their names + * Their names usually maps up to a directory in + * the root of the IFS, thus why we store it. + */ + aspInfo: { [id: number]: string } = {}; remoteFeatures: { [name: string]: string | undefined }; variantChars: { american: string, local: string }; - lastErrors: object[]; + + /** + * Strictly for storing errors from sendCommand. + * Used when creating issues on GitHub. + * */ + lastErrors: object[] = []; config?: ConnectionConfiguration.Parameters; shell?: string; @@ -69,22 +80,6 @@ export default class IBMi { constructor() { this.client = new node_ssh.NodeSSH; - this.currentHost = ``; - this.currentPort = 22; - this.currentUser = ``; - this.currentConnectionName = ``; - - this.tempRemoteFiles = {}; - this.defaultUserLibraries = []; - - /** - * Used to store ASP numbers and their names - * THeir names usually maps up to a directory in - * the root of the IFS, thus why we store it. - */ - this.aspInfo = {}; - this.qccsid = CCSID_SYSVAL; - this.userDefaultCCSID = 0; this.remoteFeatures = { git: undefined, @@ -109,13 +104,6 @@ export default class IBMi { american: `#@$`, local: `#@$` }; - - /** - * Strictly for storing errors from sendCommand. - * Used when creating issues on GitHub. - * */ - this.lastErrors = []; - } /** @@ -575,8 +563,10 @@ export default class IBMi { } } - if (this.remoteFeatures[`QZDFMDB2.PGM`]) { + if (this.sqlRunnerAvailable()) { //Temporary function to run SQL + + // TODO: stop using this runSQL function and this.runSql const runSQL = async (statement: string) => { const output = await this.sendCommand({ command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, @@ -657,12 +647,6 @@ export default class IBMi { } } - const ccsidDetail = this.getEncoding() - if (this.config.enableSQL && ccsidDetail.fallback) { - this.config.enableSQL = false; - vscode.window.showErrorMessage(`CCSID is set to ${ccsidDetail.ccsid}. Using fallback methods to access the IBM i file systems.`); - } - progress.report({ message: `Fetching local encoding values.` }); @@ -684,14 +668,18 @@ export default class IBMi { } } else { // Disable it if it's not found - if (this.config.enableSQL) { + if (this.enableSQL) { progress.report({ message: `SQL program not installed. Disabling SQL.` }); - this.config.enableSQL = false; } } + if (!this.enableSQL) { + const ccsidDetail = this.getEncoding(); + vscode.window.showErrorMessage(`CCSID is set to ${ccsidDetail.ccsid}. Using fallback methods to access the IBM i file systems.`); + } + if ((this.qccsid < 1 || this.qccsid === 65535)) { this.outputChannel?.appendLine(`\nUser CCSID is ${this.qccsid}; falling back to using default CCSID ${this.userDefaultCCSID}\n`); } @@ -1044,6 +1032,28 @@ export default class IBMi { vscode.window.showInformationMessage(`Disconnected from ${this.currentHost}.`); } + /** + * SQL only available when runner is installed and CCSID is valid. + */ + get enableSQL(): boolean { + const sqlRunner = this.sqlRunnerAvailable(); + const encodings = this.getEncoding(); + return sqlRunner && encodings.invalid === false; + } + + /** + * Do not use this API directly. + * It exists to support some backwards compatability. + * @deprecated + */ + set enableSQL(value: boolean) { + this.remoteFeatures[`QZDFMDB2.PGM`] = value ? `/QSYS.LIB/QZDFMDB2.PGM` : undefined; + } + + public sqlRunnerAvailable() { + return this.remoteFeatures[`QZDFMDB2.PGM`] !== undefined; + } + /** * Generates path to a temp file on the IBM i * @param {string} key Key to the temp file to be re-used @@ -1248,7 +1258,7 @@ export default class IBMi { if (QZDFMDB2) { const ccsidDetail = this.getEncoding(); - const possibleChangeCommand = ccsidDetail.fallback ? `@CHGJOB CCSID(${ccsidDetail.fallback});\n` : ''; + const possibleChangeCommand = (ccsidDetail.fallback && !ccsidDetail.invalid ? `@CHGJOB CCSID(${ccsidDetail.ccsid});\n` : ''); const output = await this.sendCommand({ command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`, @@ -1266,13 +1276,13 @@ export default class IBMi { } } - getEncoding(): { invalid: boolean, fallback: boolean, ccsid: number } { - const fallback = (this.qccsid && this.userDefaultCCSID && (this.qccsid < 1 || this.qccsid === 65535) && this.userDefaultCCSID > 0) ? true : false; + getEncoding() { + const fallback = ((this.qccsid < 1 || this.qccsid === 65535) && this.userDefaultCCSID > 0 ? true : false); const ccsid = fallback ? this.userDefaultCCSID : this.qccsid; return { fallback, ccsid, - invalid: (ccsid === 65535 || ccsid < 1) + invalid: (ccsid < 1 || ccsid === 65535) }; } From e090e19dbf9f4ec4f48e32956204de9350d81ffa Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 17:21:44 -0400 Subject: [PATCH 03/16] Remove enableSQL config Signed-off-by: worksofliam --- package.json | 5 ----- src/webviews/settings/index.ts | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index 230d88f81..6ce2785b6 100644 --- a/package.json +++ b/package.json @@ -209,11 +209,6 @@ "default": true, "description": "Automatically delete temporary objects from the temporary library on startup." }, - "enableSQL": { - "type": "boolean", - "default": true, - "description": "Must be enabled to make the use of SQL and is enabled by default. If you find SQL isn't working for some reason, disable this. If this config is changed, you must reconnect to the system." - }, "currentLibrary": { "type": "string", "default": "QTEMP", diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index d192ec732..079bafbb3 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -51,13 +51,12 @@ export class SettingsUI { } } - const restartFields = [`enableSQL`, `showDescInLibList`, `tempDir`, `debugCertDirectory`]; + const restartFields = [`showDescInLibList`, `tempDir`, `debugCertDirectory`]; let restart = false; const featuresTab = new Section(); featuresTab .addCheckbox(`quickConnect`, `Quick Connect`, `When enabled, server settings from previous connection will be used, resulting in much quicker connection. If server settings are changed, right-click the connection in Connection Browser and select Connect and Reload Server Settings to refresh the cache.`, config.quickConnect) - .addCheckbox(`enableSQL`, `Enable SQL`, `Must be enabled to make the use of SQL and is enabled by default. If you find SQL isn't working for some reason, disable this. If your QCCSID system value is set to 65535, it is recommended that SQL is disabled. When disabled, will use import files where possible.`, config.enableSQL) .addCheckbox(`showDescInLibList`, `Show description of libraries in User Library List view`, `When enabled, library text and attribute will be shown in User Library List. It is recommended to also enable SQL for this.`, config.showDescInLibList) .addCheckbox(`showHiddenFiles`, `Show hidden files and directories in IFS browser.`, `When disabled, hidden files and directories (i.e. names starting with '.') will not be shown in the IFS browser, except for special config files.`, config.showHiddenFiles) .addCheckbox(`autoSortIFSShortcuts`, `Sort IFS shortcuts automatically`, `Automatically sort the shortcuts in IFS browser when shortcut is added or removed.`, config.autoSortIFSShortcuts) From 402cacd1e53d2da0866a64cb663b9f9b58249d36 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 17:22:52 -0400 Subject: [PATCH 04/16] Replace enableSQL config with new getter Signed-off-by: worksofliam --- src/api/Configuration.ts | 2 -- src/api/IBMiContent.ts | 6 +++--- src/api/Search.ts | 2 +- src/instantiate.ts | 7 +++---- src/views/helpView.ts | 2 +- src/views/objectBrowser.ts | 7 ++++--- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/api/Configuration.ts b/src/api/Configuration.ts index 3191bb45f..a86e0c1ca 100644 --- a/src/api/Configuration.ts +++ b/src/api/Configuration.ts @@ -37,7 +37,6 @@ export namespace ConnectionConfiguration { connectionProfiles: ConnectionProfile[]; commandProfiles: CommandProfile[]; autoSortIFSShortcuts: boolean; - enableSQL: boolean; tempLibrary: string; tempDir: string; sourceASP: string; @@ -118,7 +117,6 @@ export namespace ConnectionConfiguration { autoSortIFSShortcuts: parameters.autoSortIFSShortcuts || false, homeDirectory: parameters.homeDirectory || `.`, /** Undefined means not created, so default to on */ - enableSQL: (parameters.enableSQL === true || parameters.enableSQL === undefined), tempLibrary: parameters.tempLibrary || `ILEDITOR`, tempDir: parameters.tempDir || `/tmp`, currentLibrary: parameters.currentLibrary || ``, diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index 84396e8da..4600949dc 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -338,7 +338,7 @@ export default class IBMiContent { async getTable(library: string, file: string, member?: string, deleteTable?: boolean): Promise { if (!member) member = file; //Incase mbr is the same file - if (file === member && this.config.enableSQL) { + if (file === member && this.ibmi.enableSQL) { const data = await this.ibmi.runSQL(`SELECT * FROM ${library}.${file}`); if (deleteTable && this.config.autoClearTempData) { @@ -406,7 +406,7 @@ export default class IBMiContent { async getLibraryList(libraries: string[]): Promise { let results: Tools.DB2Row[]; - if (this.config.enableSQL) { + if (this.ibmi.enableSQL) { const statement = ` select os.OBJNAME as ODOBNM , coalesce(os.OBJTEXT, '') as ODOBTX @@ -426,7 +426,7 @@ export default class IBMiContent { const objects = results.map(object => ({ library: 'QSYS', type: '*LIB', - name: this.config.enableSQL ? object.ODOBNM : this.ibmi.sysNameInLocal(String(object.ODOBNM)), + name: this.ibmi.enableSQL ? object.ODOBNM : this.ibmi.sysNameInLocal(String(object.ODOBNM)), attribute: object.ODOBAT, text: object.ODOBTX } as IBMiObject)); diff --git a/src/api/Search.ts b/src/api/Search.ts index 5a0c59cad..de1f94626 100644 --- a/src/api/Search.ts +++ b/src/api/Search.ts @@ -27,7 +27,7 @@ export namespace Search { let asp = ``; if (config.sourceASP) { asp = `/${config.sourceASP}`; - } else if (config.enableSQL) { + } else if (connection.enableSQL) { try { const [row] = await content.runSQL(`SELECT IASP_NUMBER FROM TABLE(QSYS2.LIBRARY_INFO('${library}'))`); const iaspNumber = row?.IASP_NUMBER; diff --git a/src/instantiate.ts b/src/instantiate.ts index d9a7afb96..f28d12afa 100644 --- a/src/instantiate.ts +++ b/src/instantiate.ts @@ -192,7 +192,6 @@ export async function loadAllofExtension(context: vscode.ExtensionContext) { const LOADING_LABEL = `Please wait`; const storage = instance.getStorage(); const content = instance.getContent(); - const config = instance.getConfig(); const connection = instance.getConnection(); let starRemoved: boolean = false; @@ -237,7 +236,7 @@ export async function loadAllofExtension(context: vscode.ExtensionContext) { quickPick.show(); // Create a cache for Schema if autosuggest enabled - if (schemaItems.length === 0 && config && config.enableSQL) { + if (schemaItems.length === 0 && connection?.enableSQL) { content!.runSQL(` select cast( SYSTEM_SCHEMA_NAME as char( 10 ) for bit data ) as SYSTEM_SCHEMA_NAME , ifnull( cast( SCHEMA_TEXT as char( 50 ) for bit data ), '' ) as SCHEMA_TEXT @@ -271,7 +270,7 @@ export async function loadAllofExtension(context: vscode.ExtensionContext) { } // autosuggest - if (config && config.enableSQL && (!quickPick.value.startsWith(`/`)) && quickPick.value.endsWith(`*`)) { + if (connection && connection.enableSQL && (!quickPick.value.startsWith(`/`)) && quickPick.value.endsWith(`*`)) { const selectionSplit = connection!.upperCaseName(quickPick.value).split('/'); const lastPart = selectionSplit[selectionSplit.length - 1]; let filterText = lastPart.substring(0, lastPart.indexOf(`*`)); @@ -429,7 +428,7 @@ export async function loadAllofExtension(context: vscode.ExtensionContext) { } else { const selectionSplit = connection!.upperCaseName(selection).split('/') if (selectionSplit.length === 3 || selection.startsWith(`/`)) { - if (config && config.enableSQL && !selection.startsWith(`/`)) { + if (connection?.enableSQL && !selection.startsWith(`/`)) { const libUS = connection!.sysNameInAmerican(selectionSplit[0]); const fileUS = connection!.sysNameInAmerican(selectionSplit[1]); const memberUS = path.parse(connection!.sysNameInAmerican(selectionSplit[2])); diff --git a/src/views/helpView.ts b/src/views/helpView.ts index 341c5d317..d551551d3 100644 --- a/src/views/helpView.ts +++ b/src/views/helpView.ts @@ -156,7 +156,7 @@ async function getRemoteSection() { `|Tech Refresh|${osVersion?.TR || '?'}|`, `|QCCSID|${ccsids.qccsid || '?'}|`, `|Default CCSID|${ccsids.userDefaultCCSID || '?'}|`, - `|SQL|${config.enableSQL ? 'Enabled' : 'Disabled'}`, + `|SQL|${connection.enableSQL ? 'Enabled' : 'Disabled'}`, `|Source dates|${config.enableSourceDates ? 'Enabled' : 'Disabled'}`, '', `### Enabled features`, diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index f2aa89fd8..443dcbc03 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -257,6 +257,7 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O } async getChildren(): Promise { + const connection = getConnection(); const content = getContent(); const writable = await content.checkObject({ @@ -281,9 +282,9 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O } catch (e: any) { console.log(e); - // Work around since we can't get the member list if the users QCCSID is not setup. + // Work around since we can't get the member list if the users CCSID is not setup. const config = getConfig(); - if (config.enableSQL) { + if (connection.enableSQL) { if (e && e.message && e.message.includes(`CCSID`)) { vscode.window.showErrorMessage(`Error getting member list. Disabling SQL and refreshing. It is recommended you reload. ${e.message}`, `Reload`).then(async (value) => { if (value === `Reload`) { @@ -291,7 +292,7 @@ class ObjectBrowserSourcePhysicalFileItem extends ObjectBrowserItem implements O } }); - config.enableSQL = false; + connection.enableSQL = false; await ConnectionConfiguration.update(config); return this.getChildren(); } From a09ee29da760e42ae9ba42f8e888b73e0f62bc21 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 17:23:06 -0400 Subject: [PATCH 05/16] Use new SQL runner available API Signed-off-by: worksofliam --- src/filesystems/qsys/QSysFs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystems/qsys/QSysFs.ts b/src/filesystems/qsys/QSysFs.ts index fd621ca9b..1fe3a10d9 100644 --- a/src/filesystems/qsys/QSysFs.ts +++ b/src/filesystems/qsys/QSysFs.ts @@ -62,7 +62,7 @@ export class QSysFS implements vscode.FileSystemProvider { const config = connection?.config; if (connection && config?.enableSourceDates) { - if (connection.remoteFeatures[`QZDFMDB2.PGM`]) { + if (connection.sqlRunnerAvailable()) { this.extendedMemberSupport = true; this.sourceDateHandler.changeSourceDateMode(config.sourceDateMode); const ccsidDetail = connection.getEncoding(); From a1b2098cb019ed10ee936006f487c07156b7b779 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 17:23:21 -0400 Subject: [PATCH 06/16] Reset SQL runner state for each test Signed-off-by: worksofliam --- src/testing/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/testing/index.ts b/src/testing/index.ts index e773772a7..404dd5f04 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -112,11 +112,15 @@ async function runTests() { } async function runTest(test: TestCase) { + const connection = instance.getConnection(); + console.log(`\tRunning ${test.name}`); test.status = "running"; testSuitesTreeProvider.refresh(test); const start = +(new Date()); try { + connection!.enableSQL = true; + await test.test(); test.status = "pass"; } From 9e5f645363b2642ca474b64ab44406641ebfa3c5 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 2 Apr 2024 17:23:36 -0400 Subject: [PATCH 07/16] Ensure all tests now use enableSQL getter Signed-off-by: worksofliam --- src/testing/content.ts | 91 +++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/testing/content.ts b/src/testing/content.ts index a2ff25954..c15467f7b 100644 --- a/src/testing/content.ts +++ b/src/testing/content.ts @@ -224,17 +224,13 @@ export const ContentSuite: TestSuite = { { name: `Test getTable (SQL disabled)`, test: async () => { - const config = instance.getConfig(); + const connection = instance.getConnection(); const content = instance.getContent(); - const resetValue = config!.enableSQL; - // SQL needs to be disabled for this test. - config!.enableSQL = false; + connection!.enableSQL = false; const rows = await content?.getTable(`qiws`, `qcustcdt`, `*all`); - config!.enableSQL = resetValue; - assert.notStrictEqual(rows?.length, 0); const firstRow = rows![0]; @@ -249,42 +245,41 @@ export const ContentSuite: TestSuite = { const content = instance.getContent(); const connection = instance.getConnection(); - const resetValue = config!.enableSQL; - - // First we fetch the table in SQL mode - config!.enableSQL = true; const tempLib = config!.tempLibrary; const TempName = Tools.makeid(); await connection?.runCommand({ command: `DSPOBJD OBJ(QSYS/QSYSINC) OBJTYPE(*LIB) DETAIL(*TEXTATR) OUTPUT(*OUTFILE) OUTFILE(${tempLib}/${TempName})`, noLibList: true }); + + // First we fetch the table in SQL mode + connection!.enableSQL = true; const tableA = await content?.getTable(tempLib, TempName, TempName, false); // Then we fetch the table without SQL - config!.enableSQL = false; + connection!.enableSQL = false; const tableB = await content?.getTable(tempLib, TempName, TempName, true); - // Reset the config - config!.enableSQL = resetValue; - - assert.notDeepStrictEqual(tableA, tableB); + assert.deepStrictEqual(tableA, tableB); } }, { name: `Test getTable (SQL enabled)`, test: async () => { - const config = instance.getConfig(); + const connection = instance.getConnection(); const content = instance.getContent(); + const config = instance.getConfig(); - const resetValue = config!.enableSQL; - - config!.enableSQL = true; + const tempLib = config!.tempLibrary; + const TempName = Tools.makeid(); + await connection?.runCommand({ + command: `DSPOBJD OBJ(QSYS/QSYSINC) OBJTYPE(*LIB) DETAIL(*TEXTATR) OUTPUT(*OUTFILE) OUTFILE(${tempLib}/${TempName})`, + noLibList: true + }); - const rows = await content?.getTable(`qiws`, `qcustcdt`, `qcustcdt`); + connection!.enableSQL = true; - // Reset the config - config!.enableSQL = resetValue; + const rows = await content?.getTable(tempLib, TempName, TempName); assert.notStrictEqual(rows?.length, 0); } @@ -437,20 +432,14 @@ export const ContentSuite: TestSuite = { }, { name: `getMemberList (SQL, no filter)`, test: async () => { - const config = instance.getConfig(); + const connection = instance.getConnection(); const content = instance.getContent(); - const resetValue = config!.enableSQL; - - config!.enableSQL = true; let members = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih`, members: `*inxen` }); - config!.enableSQL = resetValue; assert.strictEqual(members?.length, 3); - config!.enableSQL = true; members = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih` }); - config!.enableSQL = resetValue; const actbpgm = members?.find(mbr => mbr.name === `ACTBPGM`); @@ -464,45 +453,48 @@ export const ContentSuite: TestSuite = { { name: `getMemberList (SQL compared to nosql)`, test: async () => { - const config = instance.getConfig(); + const connection = instance.getConnection(); const content = instance.getContent(); - const resetValue = config!.enableSQL; - // First we fetch the members in SQL mode - config!.enableSQL = true; const membersA = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih` }); - // Then we fetch the members without SQL - config!.enableSQL = false; - const membersB = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih` }); + assert.notStrictEqual(membersA?.length, 0); - // Reset the config - config!.enableSQL = resetValue; + // Then we fetch the members without SQL + connection!.enableSQL = false; - assert.deepStrictEqual(membersA, membersB); + try { + await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih` }); + assert.fail(`Should have thrown an error`); + } catch (e) { + // This fails because getMemberList has no ability to fetch members without SQL + assert.ok(e); + } } }, { name: `getMemberList (name filter, SQL compared to nosql)`, test: async () => { - const config = instance.getConfig(); + const connection = instance.getConnection(); const content = instance.getContent(); - const resetValue = config!.enableSQL; - // First we fetch the members in SQL mode - config!.enableSQL = true; + connection!.enableSQL = true; const membersA = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih`, members: 'C*' }); - // Then we fetch the members without SQL - config!.enableSQL = false; - const membersB = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih`, members: 'C*' }); + assert.notStrictEqual(membersA?.length, 0); - // Reset the config - config!.enableSQL = resetValue; + // Then we fetch the members without SQL + connection!.enableSQL = false; - assert.deepStrictEqual(membersA, membersB); + try { + await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih`, members: 'C*' }); + assert.fail(`Should have thrown an error`); + } catch (e) { + // This fails because getMemberList has no ability to fetch members without SQL + assert.ok(e); + } } }, { @@ -611,6 +603,7 @@ export const ContentSuite: TestSuite = { }, { name: `Write tab to member using SQL`, test: async () => { + // Note: This is a known failure. const lines = [ `if (a) {`, `\tcoolstuff();\t`, From 12142ca2d78c1b6e94df418c59f994fc90797d57 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 3 Apr 2024 08:08:33 -0400 Subject: [PATCH 08/16] Rename qccsid to runtimeCcsid Signed-off-by: worksofliam --- src/api/IBMi.ts | 36 +++++++++++++++++------------------- src/api/Storage.ts | 4 ++-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 1735855cf..30c957417 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -45,8 +45,8 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures ]; export default class IBMi { - private qccsid: number = 65535; - private userDefaultCCSID: number = CCSID_SYSVAL; + private runtimeCcsid: number = CCSID_SYSVAL; + private userDefaultCCSID: number = 0; client: node_ssh.NodeSSH; currentHost: string = ``; @@ -607,10 +607,10 @@ export default class IBMi { } // Fetch conversion values? - if (quickConnect === true && cachedServerSettings?.qccsid !== null && cachedServerSettings?.variantChars && cachedServerSettings?.defaultCCSID) { - this.qccsid = cachedServerSettings.qccsid; + if (quickConnect === true && cachedServerSettings?.runtimeCcsid !== null && cachedServerSettings?.variantChars && cachedServerSettings?.userDefaultCCSID) { + this.runtimeCcsid = cachedServerSettings.runtimeCcsid; this.variantChars = cachedServerSettings.variantChars; - this.userDefaultCCSID = cachedServerSettings.defaultCCSID; + this.userDefaultCCSID = cachedServerSettings.userDefaultCCSID; } else { progress.report({ message: `Fetching conversion values.` @@ -619,18 +619,21 @@ export default class IBMi { // Next, we're going to see if we can get the CCSID from the user or the system. // Some things don't work without it!!! try { + // First we grab the users default CCSID const [userInfo] = await runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`); if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') { - this.qccsid = userInfo.CHARACTER_CODE_SET_ID; + this.runtimeCcsid = userInfo.CHARACTER_CODE_SET_ID; } - if (!this.qccsid || this.qccsid === CCSID_SYSVAL) { + // But if that CCSID is *SYSVAL, then we need to grab the system CCSID (QCCSID) + if (!this.runtimeCcsid || this.runtimeCcsid === CCSID_SYSVAL) { const [systemCCSID] = await runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`); if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') { - this.qccsid = systemCCSID.CURRENT_NUMERIC_VALUE; + this.runtimeCcsid = systemCCSID.CURRENT_NUMERIC_VALUE; } } + // Let's also get the user's default CCSID try { const [activeJob] = await runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`); this.userDefaultCCSID = Number(activeJob.DEFAULT_CCSID); @@ -676,12 +679,7 @@ export default class IBMi { } if (!this.enableSQL) { - const ccsidDetail = this.getEncoding(); - vscode.window.showErrorMessage(`CCSID is set to ${ccsidDetail.ccsid}. Using fallback methods to access the IBM i file systems.`); - } - - if ((this.qccsid < 1 || this.qccsid === 65535)) { - this.outputChannel?.appendLine(`\nUser CCSID is ${this.qccsid}; falling back to using default CCSID ${this.userDefaultCCSID}\n`); + vscode.window.showErrorMessage(`SQL is disabled for this connection. Using fallback methods to access the IBM i file systems.`); } // give user option to set bash as default shell. @@ -862,7 +860,7 @@ export default class IBMi { GlobalStorage.get().setServerSettingsCache(this.currentConnectionName, { aspInfo: this.aspInfo, - qccsid: this.qccsid, + runtimeCcsid: this.runtimeCcsid, remoteFeatures: this.remoteFeatures, remoteFeaturesKeys: Object.keys(this.remoteFeatures).sort().toString(), variantChars: { @@ -872,7 +870,7 @@ export default class IBMi { badDataAreasChecked: true, libraryListValidated: true, pathChecked: true, - defaultCCSID: this.userDefaultCCSID + userDefaultCCSID: this.userDefaultCCSID }); //Keep track of variant characters that can be uppercased @@ -1277,8 +1275,8 @@ export default class IBMi { } getEncoding() { - const fallback = ((this.qccsid < 1 || this.qccsid === 65535) && this.userDefaultCCSID > 0 ? true : false); - const ccsid = fallback ? this.userDefaultCCSID : this.qccsid; + const fallback = ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0 ? true : false); + const ccsid = fallback ? this.userDefaultCCSID : this.runtimeCcsid; return { fallback, ccsid, @@ -1288,7 +1286,7 @@ export default class IBMi { getCcsids() { return { - qccsid: this.qccsid, + qccsid: this.runtimeCcsid, userDefaultCCSID: this.userDefaultCCSID }; } diff --git a/src/api/Storage.ts b/src/api/Storage.ts index eb4dfaf6c..f590a49b2 100644 --- a/src/api/Storage.ts +++ b/src/api/Storage.ts @@ -46,14 +46,14 @@ export type LastConnection = { export type CachedServerSettings = { aspInfo: { [id: number]: string }; - qccsid: number | null; + runtimeCcsid: number | null; remoteFeatures: { [name: string]: string | undefined }; remoteFeaturesKeys: string | null; variantChars: { american: string, local: string }; badDataAreasChecked: boolean | null, libraryListValidated: boolean | null, pathChecked?: boolean - defaultCCSID: number | null; + userDefaultCCSID: number | null; } | undefined; export class GlobalStorage extends Storage { From 3e5285c69924a47150b68e83e24e7d6573c9cb7b Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 3 Apr 2024 08:13:45 -0400 Subject: [PATCH 09/16] Add method to find runtime ccsid origin Signed-off-by: worksofliam --- src/api/IBMi.ts | 8 ++++++-- src/typings.ts | 5 +++++ src/views/helpView.ts | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 30c957417..1d108422b 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -7,7 +7,7 @@ import { existsSync } from "fs"; import os from "os"; import path from 'path'; import { instance } from "../instantiate"; -import { CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand } from "../typings"; +import { CcsidOrigin, CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand } from "../typings"; import { CompileTools } from "./CompileTools"; import { CachedServerSettings, GlobalStorage } from './Storage'; import { Tools } from './Tools'; @@ -45,6 +45,7 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures ]; export default class IBMi { + private runtimeCcsidOrigin = CcsidOrigin.User; private runtimeCcsid: number = CCSID_SYSVAL; private userDefaultCCSID: number = 0; @@ -623,6 +624,7 @@ export default class IBMi { const [userInfo] = await runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`); if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') { this.runtimeCcsid = userInfo.CHARACTER_CODE_SET_ID; + this.runtimeCcsidOrigin = CcsidOrigin.User; } // But if that CCSID is *SYSVAL, then we need to grab the system CCSID (QCCSID) @@ -630,6 +632,7 @@ export default class IBMi { const [systemCCSID] = await runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`); if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') { this.runtimeCcsid = systemCCSID.CURRENT_NUMERIC_VALUE; + this.runtimeCcsidOrigin = CcsidOrigin.System; } } @@ -1286,7 +1289,8 @@ export default class IBMi { getCcsids() { return { - qccsid: this.runtimeCcsid, + origin: this.runtimeCcsidOrigin, + runtimeCcsid: this.runtimeCcsid, userDefaultCCSID: this.userDefaultCCSID }; } diff --git a/src/typings.ts b/src/typings.ts index 95bfb0a70..84531447b 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -36,6 +36,11 @@ export type ActionType = "member" | "streamfile" | "object" | "file"; export type ActionRefresh = "no" | "parent" | "filter" | "browser"; export type ActionEnvironment = "ile" | "qsh" | "pase"; +export enum CcsidOrigin { + User = "user", + System = "system", +}; + export interface RemoteCommand { title?: string; command: string; diff --git a/src/views/helpView.ts b/src/views/helpView.ts index d551551d3..9973eecd3 100644 --- a/src/views/helpView.ts +++ b/src/views/helpView.ts @@ -154,7 +154,8 @@ async function getRemoteSection() { '|-|-|', `|IBM i OS|${osVersion?.OS || '?'}|`, `|Tech Refresh|${osVersion?.TR || '?'}|`, - `|QCCSID|${ccsids.qccsid || '?'}|`, + `|CCSID Origin|${ccsids.origin}|`, + `|Runtime CCSID|${ccsids.runtimeCcsid || '?'}|`, `|Default CCSID|${ccsids.userDefaultCCSID || '?'}|`, `|SQL|${connection.enableSQL ? 'Enabled' : 'Disabled'}`, `|Source dates|${config.enableSourceDates ? 'Enabled' : 'Disabled'}`, From 3fd09f0b883750f68d8ab6a787da5c345571ba5e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 3 Apr 2024 08:30:02 -0400 Subject: [PATCH 10/16] Improved messaging Signed-off-by: worksofliam --- src/api/IBMi.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 1d108422b..3351e213f 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -682,7 +682,18 @@ export default class IBMi { } if (!this.enableSQL) { - vscode.window.showErrorMessage(`SQL is disabled for this connection. Using fallback methods to access the IBM i file systems.`); + const encoding = this.getEncoding(); + // Show a message if the system CCSID is bad + const ccsidMessage = this.runtimeCcsidOrigin === CcsidOrigin.System && this.runtimeCcsid === 65535 ? `The system QCCSID is not set correctly. We recommend changing the CCSID on your user profile.` : undefined; + + // Show a message if the runtime CCSID is bad (which means both runtime and default CCSID are bad) - in theory should never happen + const encodingMessage = encoding.invalid ? `Runtime CCSID detected as ${encoding.ccsid} and is invalid. Please change the CCSID in your user profile.` : undefined; + + vscode.window.showErrorMessage([ + ccsidMessage, + encodingMessage, + `Using fallback methods to access the IBM i file systems.` + ].filter(x => x).join(` `)); } // give user option to set bash as default shell. From 9b1b2cab222b1a7a18b46a757353a748dad49714 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 9 Apr 2024 21:49:53 -0400 Subject: [PATCH 11/16] Add ability to override CCSID on runSql Signed-off-by: worksofliam --- src/api/IBMi.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 3351e213f..9b9a7bda2 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -1265,12 +1265,13 @@ export default class IBMi { * @param statements * @returns a Result set */ - async runSQL(statements: string): Promise { + async runSQL(statements: string, opts: {userCcsid?: number} = {}): Promise { const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.remoteFeatures; if (QZDFMDB2) { const ccsidDetail = this.getEncoding(); - const possibleChangeCommand = (ccsidDetail.fallback && !ccsidDetail.invalid ? `@CHGJOB CCSID(${ccsidDetail.ccsid});\n` : ''); + const useCcsid = opts.userCcsid || (ccsidDetail.fallback && !ccsidDetail.invalid ? ccsidDetail.ccsid : undefined); + const possibleChangeCommand = (useCcsid ? `@CHGJOB CCSID(${useCcsid});\n` : ''); const output = await this.sendCommand({ command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`, From 07bd1b1d5cfeccf3f9d3763eb83218d2f414ca0c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 9 Apr 2024 22:26:22 -0400 Subject: [PATCH 12/16] Encoding test Signed-off-by: worksofliam --- src/api/IBMi.ts | 15 +++++-- src/testing/encoding.ts | 86 +++++++++++++++++++++++++++++++++++++++++ src/testing/index.ts | 4 +- 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 src/testing/encoding.ts diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 9b9a7bda2..07be64364 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -46,8 +46,12 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures export default class IBMi { private runtimeCcsidOrigin = CcsidOrigin.User; + /** Runtime CCSID is either job CCSID or QCCSID */ private runtimeCcsid: number = CCSID_SYSVAL; + /** User default CCSID is job default CCSID */ private userDefaultCCSID: number = 0; + /** override allows the API to hardcode a CCSID. Usually good for testing */ + private overrideCcsid: number | undefined; client: node_ssh.NodeSSH; currentHost: string = ``; @@ -1289,9 +1293,13 @@ export default class IBMi { } } + setOverrideCcsid(ccsid: number | undefined) { + this.overrideCcsid = ccsid; + } + getEncoding() { - const fallback = ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0 ? true : false); - const ccsid = fallback ? this.userDefaultCCSID : this.runtimeCcsid; + const fallback = this.overrideCcsid !== undefined || ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0 ? true : false); + const ccsid = fallback ? (this.overrideCcsid || this.userDefaultCCSID) : this.runtimeCcsid; return { fallback, ccsid, @@ -1303,7 +1311,8 @@ export default class IBMi { return { origin: this.runtimeCcsidOrigin, runtimeCcsid: this.runtimeCcsid, - userDefaultCCSID: this.userDefaultCCSID + userDefaultCCSID: this.userDefaultCCSID, + overrideCcsid: this.overrideCcsid, }; } } \ No newline at end of file diff --git a/src/testing/encoding.ts b/src/testing/encoding.ts new file mode 100644 index 000000000..1c141cff2 --- /dev/null +++ b/src/testing/encoding.ts @@ -0,0 +1,86 @@ +import assert from "assert"; +import tmp from 'tmp'; +import util, { TextDecoder } from 'util'; +import { Uri, workspace } from "vscode"; +import { TestSuite } from "."; +import { Tools } from "../api/Tools"; +import { instance } from "../instantiate"; +import { CommandResult } from "../typings"; +import { getMemberUri } from "../filesystems/qsys/QSysFs"; + +export const EncodingSuite: TestSuite = { + name: `Encoding tests`, + after: async () => { + const connection = instance.getConnection(); + connection?.setOverrideCcsid(undefined); + }, + + before: async () => { + const config = instance.getConfig()!; + assert.ok(config.enableSourceDates, `Source dates must be enabled for this test.`); + }, + + tests: [ + { + name: `Encoding 37`, test: async () => { + const connection = instance.getConnection(); + const config = instance.getConfig()!; + + const lines = [ + `Hello world` + ].join(`\n`); + + const tempLib = config!.tempLibrary; + + connection?.setOverrideCcsid(37); + + const file = `TEST37`; + + await connection!.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112) CCSID(37)`, noLibList: true }); + await connection!.runCommand({ command: `ADDPFM FILE(${tempLib}/${file}) MBR(THEMEMBER) SRCTYPE(TXT)`, noLibList: true }); + + const theBadOneUri = getMemberUri({library: tempLib, file, name: `THEMEMBER`, extension: `TXT`}); + + await workspace.fs.readFile(theBadOneUri); + + await workspace.fs.writeFile(theBadOneUri, Buffer.from(lines, `utf8`)); + + const memberContentBuf = await workspace.fs.readFile(theBadOneUri); + const fileContent = new TextDecoder().decode(memberContentBuf) + + assert.strictEqual(fileContent, lines); + }, + }, + // { + // name: `Encoding 273`, test: async () => { + // const connection = instance.getConnection(); + // const config = instance.getConfig()!; + + // const lines = [ + // `Hello world`, + // `àáãÄÜö£øß` + // ].join(`\n`); + + // const tempLib = config!.tempLibrary; + + // connection?.setOverrideCcsid(37); + + // const file = `TEST273`; + + // await connection!.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112) CCSID(273)`, noLibList: true }); + // await connection!.runCommand({ command: `ADDPFM FILE(${tempLib}/${file}) MBR(THEMEMBER) SRCTYPE(TXT)`, noLibList: true }); + + // const theBadOneUri = getMemberUri({library: tempLib, file, name: `THEMEMBER`, extension: `TXT`}); + + // await workspace.fs.readFile(theBadOneUri); + + // await workspace.fs.writeFile(theBadOneUri, Buffer.from(lines, `utf8`)); + + // const memberContentBuf = await workspace.fs.readFile(theBadOneUri); + // const fileContent = new TextDecoder().decode(memberContentBuf) + + // assert.strictEqual(fileContent, lines); + // } + // } + ] +}; diff --git a/src/testing/index.ts b/src/testing/index.ts index 404dd5f04..f9f7ee843 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -10,6 +10,7 @@ import { ILEErrorSuite } from "./ileErrors"; import { StorageSuite } from "./storage"; import { TestSuitesTreeProvider } from "./testCasesTree"; import { ToolsSuite } from "./tools"; +import { EncodingSuite } from "./encoding"; const suites: TestSuite[] = [ ActionSuite, @@ -19,7 +20,8 @@ const suites: TestSuite[] = [ ToolsSuite, ILEErrorSuite, FilterSuite, - StorageSuite + StorageSuite, + EncodingSuite ] export type TestSuite = { From abf67169423439d6d23d25e22c38124673736507 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 10 Apr 2024 15:35:33 -0400 Subject: [PATCH 13/16] Remove override CCSID ability Signed-off-by: worksofliam --- src/api/IBMi.ts | 11 ++--------- src/testing/encoding.ts | 7 ------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 07be64364..5273d6713 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -50,8 +50,6 @@ export default class IBMi { private runtimeCcsid: number = CCSID_SYSVAL; /** User default CCSID is job default CCSID */ private userDefaultCCSID: number = 0; - /** override allows the API to hardcode a CCSID. Usually good for testing */ - private overrideCcsid: number | undefined; client: node_ssh.NodeSSH; currentHost: string = ``; @@ -1293,13 +1291,9 @@ export default class IBMi { } } - setOverrideCcsid(ccsid: number | undefined) { - this.overrideCcsid = ccsid; - } - getEncoding() { - const fallback = this.overrideCcsid !== undefined || ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0 ? true : false); - const ccsid = fallback ? (this.overrideCcsid || this.userDefaultCCSID) : this.runtimeCcsid; + const fallback = ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0 ? true : false); + const ccsid = fallback ? (this.userDefaultCCSID) : this.runtimeCcsid; return { fallback, ccsid, @@ -1312,7 +1306,6 @@ export default class IBMi { origin: this.runtimeCcsidOrigin, runtimeCcsid: this.runtimeCcsid, userDefaultCCSID: this.userDefaultCCSID, - overrideCcsid: this.overrideCcsid, }; } } \ No newline at end of file diff --git a/src/testing/encoding.ts b/src/testing/encoding.ts index 1c141cff2..0a0480ebd 100644 --- a/src/testing/encoding.ts +++ b/src/testing/encoding.ts @@ -10,11 +10,6 @@ import { getMemberUri } from "../filesystems/qsys/QSysFs"; export const EncodingSuite: TestSuite = { name: `Encoding tests`, - after: async () => { - const connection = instance.getConnection(); - connection?.setOverrideCcsid(undefined); - }, - before: async () => { const config = instance.getConfig()!; assert.ok(config.enableSourceDates, `Source dates must be enabled for this test.`); @@ -32,8 +27,6 @@ export const EncodingSuite: TestSuite = { const tempLib = config!.tempLibrary; - connection?.setOverrideCcsid(37); - const file = `TEST37`; await connection!.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112) CCSID(37)`, noLibList: true }); From ad2d23874d029908639949b361885b86eda754de Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Thu, 11 Apr 2024 21:58:48 +0200 Subject: [PATCH 14/16] Removed unused variable Signed-off-by: Seb Julliand --- src/testing/content.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/testing/content.ts b/src/testing/content.ts index c15467f7b..28f7646c8 100644 --- a/src/testing/content.ts +++ b/src/testing/content.ts @@ -4,9 +4,9 @@ import util, { TextDecoder } from 'util'; import { Uri, workspace } from "vscode"; import { TestSuite } from "."; import { Tools } from "../api/Tools"; +import { getMemberUri } from "../filesystems/qsys/QSysFs"; import { instance } from "../instantiate"; import { CommandResult } from "../typings"; -import { getMemberUri } from "../filesystems/qsys/QSysFs"; export const ContentSuite: TestSuite = { name: `Content API tests`, @@ -432,7 +432,6 @@ export const ContentSuite: TestSuite = { }, { name: `getMemberList (SQL, no filter)`, test: async () => { - const connection = instance.getConnection(); const content = instance.getContent(); let members = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih`, members: `*inxen` }); From 33bc87a50c0acc8fbae2df74d65f0f890676a9e3 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Thu, 11 Apr 2024 21:59:14 +0200 Subject: [PATCH 15/16] Added a link to find replacement method for deprecated runSQL Signed-off-by: Seb Julliand --- src/api/IBMiContent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index 4600949dc..fe7057222 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -291,7 +291,7 @@ export default class IBMiContent { * * @param statements * @returns a Result set - * @deprecated Use `IBMi#runSQL` instead + * @deprecated Use {@linkcode IBMi.runSQL IBMi.runSQL} instead */ runSQL(statements: string) { return this.ibmi.runSQL(statements); From a53dcec413c8a4a45f2c9ede72134cdb19bdf994 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 11 Apr 2024 23:30:57 -0400 Subject: [PATCH 16/16] Remove silly ternary Signed-off-by: worksofliam --- src/api/IBMi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 5273d6713..f627ae8c6 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -1292,7 +1292,7 @@ export default class IBMi { } getEncoding() { - const fallback = ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0 ? true : false); + const fallback = ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0); const ccsid = fallback ? (this.userDefaultCCSID) : this.runtimeCcsid; return { fallback,