@@ -7,7 +7,7 @@ import { existsSync } from "fs";
7
7
import os from "os" ;
8
8
import path from 'path' ;
9
9
import { instance } from "../instantiate" ;
10
- import { CommandData , CommandResult , ConnectionData , IBMiMember , RemoteCommand } from "../typings" ;
10
+ import { CcsidOrigin , CommandData , CommandResult , ConnectionData , IBMiMember , RemoteCommand } from "../typings" ;
11
11
import { CompileTools } from "./CompileTools" ;
12
12
import { CachedServerSettings , GlobalStorage } from './Storage' ;
13
13
import { Tools } from './Tools' ;
@@ -45,20 +45,35 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures
45
45
] ;
46
46
47
47
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
+
48
54
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 [ ] = [ ] ;
55
61
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 } = { } ;
59
69
remoteFeatures : { [ name : string ] : string | undefined } ;
60
70
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 [ ] = [ ] ;
62
77
config ?: ConnectionConfiguration . Parameters ;
63
78
shell ?: string ;
64
79
@@ -68,22 +83,6 @@ export default class IBMi {
68
83
69
84
constructor ( ) {
70
85
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 ;
87
86
88
87
this . remoteFeatures = {
89
88
git : undefined ,
@@ -108,13 +107,6 @@ export default class IBMi {
108
107
american : `#@$` ,
109
108
local : `#@$`
110
109
} ;
111
-
112
- /**
113
- * Strictly for storing errors from sendCommand.
114
- * Used when creating issues on GitHub.
115
- * */
116
- this . lastErrors = [ ] ;
117
-
118
110
}
119
111
120
112
/**
@@ -574,8 +566,10 @@ export default class IBMi {
574
566
}
575
567
}
576
568
577
- if ( this . remoteFeatures [ `QZDFMDB2.PGM` ] ) {
569
+ if ( this . sqlRunnerAvailable ( ) ) {
578
570
//Temporary function to run SQL
571
+
572
+ // TODO: stop using this runSQL function and this.runSql
579
573
const runSQL = async ( statement : string ) => {
580
574
const output = await this . sendCommand ( {
581
575
command : `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"` ,
@@ -616,10 +610,10 @@ export default class IBMi {
616
610
}
617
611
618
612
// 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 ;
621
615
this . variantChars = cachedServerSettings . variantChars ;
622
- this . defaultCCSID = cachedServerSettings . defaultCCSID ;
616
+ this . userDefaultCCSID = cachedServerSettings . userDefaultCCSID ;
623
617
} else {
624
618
progress . report ( {
625
619
message : `Fetching conversion values.`
@@ -628,21 +622,26 @@ export default class IBMi {
628
622
// Next, we're going to see if we can get the CCSID from the user or the system.
629
623
// Some things don't work without it!!!
630
624
try {
625
+ // First we grab the users default CCSID
631
626
const [ userInfo ] = await runSQL ( `select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${ this . currentUser } ') ) )` ) ;
632
627
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 ;
634
630
}
635
631
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 ) {
637
634
const [ systemCCSID ] = await runSQL ( `select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'` ) ;
638
635
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 ;
640
638
}
641
639
}
642
640
641
+ // Let's also get the user's default CCSID
643
642
try {
644
643
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 ) ;
646
645
}
647
646
catch ( error ) {
648
647
const [ defaultCCSID ] = ( await this . runCommand ( { command : "DSPJOB OPTION(*DFNA)" } ) )
@@ -652,15 +651,10 @@ export default class IBMi {
652
651
653
652
const defaultCCSCID = Number ( defaultCCSID . split ( "DFTCCSID" ) . at ( 1 ) ?. trim ( ) ) ;
654
653
if ( defaultCCSCID && ! isNaN ( defaultCCSCID ) ) {
655
- this . defaultCCSID = defaultCCSCID ;
654
+ this . userDefaultCCSID = defaultCCSCID ;
656
655
}
657
656
}
658
657
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
-
664
658
progress . report ( {
665
659
message : `Fetching local encoding values.`
666
660
} ) ;
@@ -682,16 +676,26 @@ export default class IBMi {
682
676
}
683
677
} else {
684
678
// Disable it if it's not found
685
- if ( this . config . enableSQL ) {
679
+ if ( this . enableSQL ) {
686
680
progress . report ( {
687
681
message : `SQL program not installed. Disabling SQL.`
688
682
} ) ;
689
- this . config . enableSQL = false ;
690
683
}
691
684
}
692
685
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 ( ` ` ) ) ;
695
699
}
696
700
697
701
// give user option to set bash as default shell.
@@ -872,7 +876,7 @@ export default class IBMi {
872
876
873
877
GlobalStorage . get ( ) . setServerSettingsCache ( this . currentConnectionName , {
874
878
aspInfo : this . aspInfo ,
875
- qccsid : this . qccsid ,
879
+ runtimeCcsid : this . runtimeCcsid ,
876
880
remoteFeatures : this . remoteFeatures ,
877
881
remoteFeaturesKeys : Object . keys ( this . remoteFeatures ) . sort ( ) . toString ( ) ,
878
882
variantChars : {
@@ -882,7 +886,7 @@ export default class IBMi {
882
886
badDataAreasChecked : true ,
883
887
libraryListValidated : true ,
884
888
pathChecked : true ,
885
- defaultCCSID : this . defaultCCSID
889
+ userDefaultCCSID : this . userDefaultCCSID
886
890
} ) ;
887
891
888
892
//Keep track of variant characters that can be uppercased
@@ -1042,6 +1046,28 @@ export default class IBMi {
1042
1046
vscode . window . showInformationMessage ( `Disconnected from ${ this . currentHost } .` ) ;
1043
1047
}
1044
1048
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
+
1045
1071
/**
1046
1072
* Generates path to a temp file on the IBM i
1047
1073
* @param {string } key Key to the temp file to be re-used
@@ -1231,4 +1257,55 @@ export default class IBMi {
1231
1257
return name . toLocaleUpperCase ( ) ;
1232
1258
}
1233
1259
}
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
+ }
1234
1311
}
0 commit comments