Skip to content

Commit 8239670

Browse files
authored
Merge pull request #1968 from codefori/cleanup/apis
2 parents 77ac4c5 + a53dcec commit 8239670

File tree

15 files changed

+298
-162
lines changed

15 files changed

+298
-162
lines changed

package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,6 @@
203203
"default": true,
204204
"description": "Automatically delete temporary objects from the temporary library on startup."
205205
},
206-
"enableSQL": {
207-
"type": "boolean",
208-
"default": true,
209-
"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."
210-
},
211206
"currentLibrary": {
212207
"type": "string",
213208
"default": "QTEMP",

src/api/Configuration.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export namespace ConnectionConfiguration {
3737
connectionProfiles: ConnectionProfile[];
3838
commandProfiles: CommandProfile[];
3939
autoSortIFSShortcuts: boolean;
40-
enableSQL: boolean;
4140
tempLibrary: string;
4241
tempDir: string;
4342
sourceASP: string;
@@ -119,7 +118,6 @@ export namespace ConnectionConfiguration {
119118
autoSortIFSShortcuts: parameters.autoSortIFSShortcuts || false,
120119
homeDirectory: parameters.homeDirectory || `.`,
121120
/** Undefined means not created, so default to on */
122-
enableSQL: (parameters.enableSQL === true || parameters.enableSQL === undefined),
123121
tempLibrary: parameters.tempLibrary || `ILEDITOR`,
124122
tempDir: parameters.tempDir || `/tmp`,
125123
currentLibrary: parameters.currentLibrary || ``,

src/api/IBMi.ts

Lines changed: 131 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { existsSync } from "fs";
77
import os from "os";
88
import path from 'path';
99
import { instance } from "../instantiate";
10-
import { CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand } from "../typings";
10+
import { CcsidOrigin, CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand } from "../typings";
1111
import { CompileTools } from "./CompileTools";
1212
import { CachedServerSettings, GlobalStorage } from './Storage';
1313
import { Tools } from './Tools';
@@ -45,20 +45,35 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures
4545
];
4646

4747
export default class IBMi {
48+
private runtimeCcsidOrigin = CcsidOrigin.User;
49+
/** Runtime CCSID is either job CCSID or QCCSID */
50+
private runtimeCcsid: number = CCSID_SYSVAL;
51+
/** User default CCSID is job default CCSID */
52+
private userDefaultCCSID: number = 0;
53+
4854
client: node_ssh.NodeSSH;
49-
currentHost: string;
50-
currentPort: number;
51-
currentUser: string;
52-
currentConnectionName: string;
53-
tempRemoteFiles: { [name: string]: string };
54-
defaultUserLibraries: string[];
55+
currentHost: string = ``;
56+
currentPort: number = 22;
57+
currentUser: string = ``;
58+
currentConnectionName: string = ``;
59+
tempRemoteFiles: { [name: string]: string } = {};
60+
defaultUserLibraries: string[] = [];
5561
outputChannel?: vscode.OutputChannel;
56-
aspInfo: { [id: number]: string };
57-
qccsid: number;
58-
defaultCCSID: number;
62+
63+
/**
64+
* Used to store ASP numbers and their names
65+
* Their names usually maps up to a directory in
66+
* the root of the IFS, thus why we store it.
67+
*/
68+
aspInfo: { [id: number]: string } = {};
5969
remoteFeatures: { [name: string]: string | undefined };
6070
variantChars: { american: string, local: string };
61-
lastErrors: object[];
71+
72+
/**
73+
* Strictly for storing errors from sendCommand.
74+
* Used when creating issues on GitHub.
75+
* */
76+
lastErrors: object[] = [];
6277
config?: ConnectionConfiguration.Parameters;
6378
shell?: string;
6479

@@ -68,22 +83,6 @@ export default class IBMi {
6883

6984
constructor() {
7085
this.client = new node_ssh.NodeSSH;
71-
this.currentHost = ``;
72-
this.currentPort = 22;
73-
this.currentUser = ``;
74-
this.currentConnectionName = ``;
75-
76-
this.tempRemoteFiles = {};
77-
this.defaultUserLibraries = [];
78-
79-
/**
80-
* Used to store ASP numbers and their names
81-
* THeir names usually maps up to a directory in
82-
* the root of the IFS, thus why we store it.
83-
*/
84-
this.aspInfo = {};
85-
this.qccsid = CCSID_SYSVAL;
86-
this.defaultCCSID = 0;
8786

8887
this.remoteFeatures = {
8988
git: undefined,
@@ -108,13 +107,6 @@ export default class IBMi {
108107
american: `#@$`,
109108
local: `#@$`
110109
};
111-
112-
/**
113-
* Strictly for storing errors from sendCommand.
114-
* Used when creating issues on GitHub.
115-
* */
116-
this.lastErrors = [];
117-
118110
}
119111

120112
/**
@@ -574,8 +566,10 @@ export default class IBMi {
574566
}
575567
}
576568

577-
if (this.remoteFeatures[`QZDFMDB2.PGM`]) {
569+
if (this.sqlRunnerAvailable()) {
578570
//Temporary function to run SQL
571+
572+
// TODO: stop using this runSQL function and this.runSql
579573
const runSQL = async (statement: string) => {
580574
const output = await this.sendCommand({
581575
command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`,
@@ -616,10 +610,10 @@ export default class IBMi {
616610
}
617611

618612
// Fetch conversion values?
619-
if (quickConnect === true && cachedServerSettings?.qccsid !== null && cachedServerSettings?.variantChars && cachedServerSettings?.defaultCCSID) {
620-
this.qccsid = cachedServerSettings.qccsid;
613+
if (quickConnect === true && cachedServerSettings?.runtimeCcsid !== null && cachedServerSettings?.variantChars && cachedServerSettings?.userDefaultCCSID) {
614+
this.runtimeCcsid = cachedServerSettings.runtimeCcsid;
621615
this.variantChars = cachedServerSettings.variantChars;
622-
this.defaultCCSID = cachedServerSettings.defaultCCSID;
616+
this.userDefaultCCSID = cachedServerSettings.userDefaultCCSID;
623617
} else {
624618
progress.report({
625619
message: `Fetching conversion values.`
@@ -628,21 +622,26 @@ export default class IBMi {
628622
// Next, we're going to see if we can get the CCSID from the user or the system.
629623
// Some things don't work without it!!!
630624
try {
625+
// First we grab the users default CCSID
631626
const [userInfo] = await runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`);
632627
if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
633-
this.qccsid = userInfo.CHARACTER_CODE_SET_ID;
628+
this.runtimeCcsid = userInfo.CHARACTER_CODE_SET_ID;
629+
this.runtimeCcsidOrigin = CcsidOrigin.User;
634630
}
635631

636-
if (!this.qccsid || this.qccsid === CCSID_SYSVAL) {
632+
// But if that CCSID is *SYSVAL, then we need to grab the system CCSID (QCCSID)
633+
if (!this.runtimeCcsid || this.runtimeCcsid === CCSID_SYSVAL) {
637634
const [systemCCSID] = await runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
638635
if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
639-
this.qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
636+
this.runtimeCcsid = systemCCSID.CURRENT_NUMERIC_VALUE;
637+
this.runtimeCcsidOrigin = CcsidOrigin.System;
640638
}
641639
}
642640

641+
// Let's also get the user's default CCSID
643642
try {
644643
const [activeJob] = await runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
645-
this.defaultCCSID = Number(activeJob.DEFAULT_CCSID);
644+
this.userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
646645
}
647646
catch (error) {
648647
const [defaultCCSID] = (await this.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
@@ -652,15 +651,10 @@ export default class IBMi {
652651

653652
const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
654653
if (defaultCCSCID && !isNaN(defaultCCSCID)) {
655-
this.defaultCCSID = defaultCCSCID;
654+
this.userDefaultCCSID = defaultCCSCID;
656655
}
657656
}
658657

659-
if (this.config.enableSQL && this.qccsid === 65535) {
660-
this.config.enableSQL = false;
661-
vscode.window.showErrorMessage(`QCCSID is set to 65535. Using fallback methods to access the IBM i file systems.`);
662-
}
663-
664658
progress.report({
665659
message: `Fetching local encoding values.`
666660
});
@@ -682,16 +676,26 @@ export default class IBMi {
682676
}
683677
} else {
684678
// Disable it if it's not found
685-
if (this.config.enableSQL) {
679+
if (this.enableSQL) {
686680
progress.report({
687681
message: `SQL program not installed. Disabling SQL.`
688682
});
689-
this.config.enableSQL = false;
690683
}
691684
}
692685

693-
if ((this.qccsid < 1 || this.qccsid === 65535)) {
694-
this.outputChannel?.appendLine(`\nUser CCSID is ${this.qccsid}; falling back to using default CCSID ${this.defaultCCSID}\n`);
686+
if (!this.enableSQL) {
687+
const encoding = this.getEncoding();
688+
// Show a message if the system CCSID is bad
689+
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;
690+
691+
// Show a message if the runtime CCSID is bad (which means both runtime and default CCSID are bad) - in theory should never happen
692+
const encodingMessage = encoding.invalid ? `Runtime CCSID detected as ${encoding.ccsid} and is invalid. Please change the CCSID in your user profile.` : undefined;
693+
694+
vscode.window.showErrorMessage([
695+
ccsidMessage,
696+
encodingMessage,
697+
`Using fallback methods to access the IBM i file systems.`
698+
].filter(x => x).join(` `));
695699
}
696700

697701
// give user option to set bash as default shell.
@@ -872,7 +876,7 @@ export default class IBMi {
872876

873877
GlobalStorage.get().setServerSettingsCache(this.currentConnectionName, {
874878
aspInfo: this.aspInfo,
875-
qccsid: this.qccsid,
879+
runtimeCcsid: this.runtimeCcsid,
876880
remoteFeatures: this.remoteFeatures,
877881
remoteFeaturesKeys: Object.keys(this.remoteFeatures).sort().toString(),
878882
variantChars: {
@@ -882,7 +886,7 @@ export default class IBMi {
882886
badDataAreasChecked: true,
883887
libraryListValidated: true,
884888
pathChecked: true,
885-
defaultCCSID: this.defaultCCSID
889+
userDefaultCCSID: this.userDefaultCCSID
886890
});
887891

888892
//Keep track of variant characters that can be uppercased
@@ -1042,6 +1046,28 @@ export default class IBMi {
10421046
vscode.window.showInformationMessage(`Disconnected from ${this.currentHost}.`);
10431047
}
10441048

1049+
/**
1050+
* SQL only available when runner is installed and CCSID is valid.
1051+
*/
1052+
get enableSQL(): boolean {
1053+
const sqlRunner = this.sqlRunnerAvailable();
1054+
const encodings = this.getEncoding();
1055+
return sqlRunner && encodings.invalid === false;
1056+
}
1057+
1058+
/**
1059+
* Do not use this API directly.
1060+
* It exists to support some backwards compatability.
1061+
* @deprecated
1062+
*/
1063+
set enableSQL(value: boolean) {
1064+
this.remoteFeatures[`QZDFMDB2.PGM`] = value ? `/QSYS.LIB/QZDFMDB2.PGM` : undefined;
1065+
}
1066+
1067+
public sqlRunnerAvailable() {
1068+
return this.remoteFeatures[`QZDFMDB2.PGM`] !== undefined;
1069+
}
1070+
10451071
/**
10461072
* Generates path to a temp file on the IBM i
10471073
* @param {string} key Key to the temp file to be re-used
@@ -1231,4 +1257,55 @@ export default class IBMi {
12311257
return name.toLocaleUpperCase();
12321258
}
12331259
}
1260+
1261+
1262+
/**
1263+
* Run SQL statements.
1264+
* Each statement must be separated by a semi-colon and a new line (i.e. ;\n).
1265+
* If a statement starts with @, it will be run as a CL command.
1266+
*
1267+
* @param statements
1268+
* @returns a Result set
1269+
*/
1270+
async runSQL(statements: string, opts: {userCcsid?: number} = {}): Promise<Tools.DB2Row[]> {
1271+
const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.remoteFeatures;
1272+
1273+
if (QZDFMDB2) {
1274+
const ccsidDetail = this.getEncoding();
1275+
const useCcsid = opts.userCcsid || (ccsidDetail.fallback && !ccsidDetail.invalid ? ccsidDetail.ccsid : undefined);
1276+
const possibleChangeCommand = (useCcsid ? `@CHGJOB CCSID(${useCcsid});\n` : '');
1277+
1278+
const output = await this.sendCommand({
1279+
command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i' '-t')"`,
1280+
stdin: Tools.fixSQL(`${possibleChangeCommand}${statements}`)
1281+
})
1282+
1283+
if (output.stdout) {
1284+
return Tools.db2Parse(output.stdout);
1285+
} else {
1286+
throw new Error(`There was an error running the SQL statement.`);
1287+
}
1288+
1289+
} else {
1290+
throw new Error(`There is no way to run SQL on this system.`);
1291+
}
1292+
}
1293+
1294+
getEncoding() {
1295+
const fallback = ((this.runtimeCcsid < 1 || this.runtimeCcsid === 65535) && this.userDefaultCCSID > 0);
1296+
const ccsid = fallback ? (this.userDefaultCCSID) : this.runtimeCcsid;
1297+
return {
1298+
fallback,
1299+
ccsid,
1300+
invalid: (ccsid < 1 || ccsid === 65535)
1301+
};
1302+
}
1303+
1304+
getCcsids() {
1305+
return {
1306+
origin: this.runtimeCcsidOrigin,
1307+
runtimeCcsid: this.runtimeCcsid,
1308+
userDefaultCCSID: this.userDefaultCCSID,
1309+
};
1310+
}
12341311
}

0 commit comments

Comments
 (0)