Skip to content

Commit 0baba74

Browse files
authored
Merge pull request #23 from ThePrez/rewrite/query
Various enhancements for using server-side component
2 parents b0019a9 + 4d4ff3b commit 0baba74

File tree

5 files changed

+238
-99
lines changed

5 files changed

+238
-99
lines changed

src/connection/manager.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getInstance } from "../base";
2+
import { Query } from "./query";
23
import { JobStatus, SQLJob } from "./sqlJob";
3-
import { Rows } from "./types";
4+
import { QueryOptions, QueryResult, Rows } from "./types";
45

56
export interface JobInfo {
67
name: string;
@@ -88,11 +89,14 @@ export class SQLJobManager {
8889
return (this.selectedJob >= 0);
8990
}
9091

91-
runSQL<T>(query: string): Promise<T[]> {
92+
async runSQL<T>(query: string): Promise<T[]> {
9293
const selected = this.jobs[this.selectedJob]
9394
if (SQLJobManager.jobSupport && selected) {
94-
return selected.job.query(query) as Promise<T[]>;
95-
95+
// 2147483647 is NOT arbitrary. On the server side, this is processed as a Java
96+
// int. This is the largest number available without overflow (Integer.MAX_VALUE)
97+
const rowsToFetch = 2147483647;
98+
const results = await selected.job.query<T>(query).run(rowsToFetch);
99+
return results.data;
96100
} else {
97101
const instance = getInstance();
98102
const config = instance.getConfig();
@@ -102,7 +106,16 @@ export class SQLJobManager {
102106
`SET CURRENT SCHEMA = '${config.currentLibrary.toUpperCase()}'`,
103107
query
104108
].join(`;\n`);
109+
105110
return content.runSQL(queryContext) as Promise<T[]>;
106111
}
107112
}
113+
getPagingStatement<T>(query: string, opts?: QueryOptions): Query<T> {
114+
const selected = this.jobs[this.selectedJob]
115+
if (SQLJobManager.jobSupport && selected) {
116+
return selected.job.query<T>(query, opts);
117+
} else {
118+
throw new Error(`Active SQL job is required. Please spin one up first.`);
119+
}
120+
}
108121
}

src/connection/query.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { SQLJob } from "./sqlJob";
2+
import { CLCommandResult, JobLogEntry, QueryOptions, QueryResult, ServerResponse } from "./types";
3+
export enum QueryState {
4+
NOT_YET_RUN = 1,
5+
RUN_MORE_DATA_AVAILABLE = 2,
6+
RUN_DONE = 3,
7+
ERROR = 4
8+
}
9+
export class Query<T> {
10+
11+
private static uniqueIdCounter: number = 0;
12+
private static globalQueryList: Query<any>[] = [];
13+
private correlationId: string;
14+
private sql: string;
15+
private isPrepared: boolean = false;
16+
private parameters: any[];
17+
private rowsToFetch: number = 100;
18+
private isCLCommand: boolean;
19+
private state: QueryState = QueryState.NOT_YET_RUN;
20+
21+
constructor(private job: SQLJob, query: string, opts: QueryOptions = {isClCommand: false, parameters: undefined}) {
22+
this.job = job;
23+
this.isPrepared = (undefined !== opts.parameters);
24+
this.parameters = opts.parameters;
25+
this.sql = query;
26+
this.isCLCommand = opts.isClCommand;
27+
Query.globalQueryList.push(this);
28+
}
29+
public static byId(id: string) {
30+
return (undefined === id || '' === id) ? undefined : Query.globalQueryList.find(query => query.correlationId === id);
31+
}
32+
public async run(rowsToFetch: number = this.rowsToFetch): Promise<QueryResult<T>> {
33+
switch (this.state) {
34+
case QueryState.RUN_MORE_DATA_AVAILABLE:
35+
throw new Error('Statement has already been run');
36+
case QueryState.RUN_DONE:
37+
throw new Error('Statement has already been fully run');
38+
}
39+
let queryObject;
40+
if (this.isCLCommand) {
41+
queryObject = {
42+
id: `clcommand` + (++Query.uniqueIdCounter),
43+
type: `cl`,
44+
cmd: this.sql
45+
};
46+
} else {
47+
queryObject = {
48+
id: `query` + (++Query.uniqueIdCounter),
49+
type: this.isPrepared ? `prepare_sql_execute` : `sql`,
50+
sql: this.sql,
51+
rows: rowsToFetch,
52+
parameters: this.parameters
53+
};
54+
}
55+
this.rowsToFetch = rowsToFetch;
56+
let result = await this.job.send(JSON.stringify(queryObject));
57+
let queryResult: QueryResult<T> = JSON.parse(result);
58+
59+
this.state = queryResult.is_done? QueryState.RUN_DONE: QueryState.RUN_MORE_DATA_AVAILABLE;
60+
61+
if (queryResult.success !== true && !this.isCLCommand) {
62+
this.state = QueryState.ERROR;
63+
throw new Error(queryResult.error || `Failed to run query (unknown error)`);
64+
}
65+
this.correlationId = queryResult.id;
66+
return queryResult;
67+
}
68+
public async fetchMore(rowsToFetch: number = this.rowsToFetch): Promise<QueryResult<T>> {
69+
//TODO: verify that the SQL job hasn't changed
70+
switch (this.state) {
71+
case QueryState.NOT_YET_RUN:
72+
throw new Error('Statement has not yet been run');
73+
case QueryState.RUN_DONE:
74+
throw new Error('Statement has already been fully run');
75+
}
76+
let queryObject = {
77+
id: `fetchMore` + (++Query.uniqueIdCounter),
78+
cont_id: this.correlationId,
79+
type: `sqlmore`,
80+
sql: this.sql,
81+
rows: rowsToFetch
82+
};
83+
84+
this.rowsToFetch = rowsToFetch;
85+
let result = await this.job.send(JSON.stringify(queryObject));
86+
87+
let queryResult: QueryResult<T> = JSON.parse(result);
88+
this.state = queryResult.is_done? QueryState.RUN_DONE: QueryState.RUN_MORE_DATA_AVAILABLE;
89+
90+
if (queryResult.success !== true) {
91+
this.state = QueryState.ERROR;
92+
throw new Error(queryResult.error || `Failed to run query (unknown error)`);
93+
}
94+
return queryResult;
95+
}
96+
public async close() {
97+
this.state = QueryState.RUN_DONE;
98+
}
99+
public getId(): string {
100+
return this.correlationId;
101+
}
102+
public getState(): QueryState {
103+
return this.state;
104+
}
105+
}

src/connection/sqlJob.ts

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { CommandResult } from "@halcyontech/vscode-ibmi-types";
22
import { getInstance } from "../base";
33
import { ServerComponent } from "./serverComponent";
4-
import { JDBCOptions, ConnectionResult, Rows, QueryResult, JobLogEntry, CLCommandResult, VersionCheckResult } from "./types";
4+
import { JDBCOptions, ConnectionResult, Rows, QueryResult, JobLogEntry, CLCommandResult, VersionCheckResult, GetTraceDataResult, ServerTraceDest, ServerTraceLevel, SetConfigResult, QueryOptions } from "./types";
5+
import { Query } from "./query";
56

67
export enum JobStatus {
78
NotStarted = "notStarted",
@@ -29,7 +30,7 @@ export class SQLJob {
2930
})
3031
}
3132

32-
private async send(content: string): Promise<string> {
33+
async send(content: string): Promise<string> {
3334
if (this.status === JobStatus.Ready) {
3435
this.status = JobStatus.Busy;
3536
ServerComponent.writeOutput(content);
@@ -101,92 +102,62 @@ export class SQLJob {
101102

102103
return connectResult;
103104
}
105+
query<T>(sql: string, opts?: QueryOptions): Query<T> {
106+
return new Query(this, sql, opts);
107+
}
104108

105-
async query<T>(sql: string, parameters?: (number | string)[]): Promise<T[]> {
106-
const hasParms = (parameters && parameters.length > 0);
107-
108-
const queryObject = {
109+
async getVersion(): Promise<VersionCheckResult> {
110+
const verObj = {
109111
id: `boop`,
110-
type: hasParms ? `prepare_sql_execute` : `sql`,
111-
sql,
112-
parameters: hasParms ? parameters : undefined
112+
type: `getversion`
113113
};
114114

115-
const result = await this.send(JSON.stringify(queryObject));
115+
const result = await this.send(JSON.stringify(verObj));
116116

117-
const queryResult: QueryResult = JSON.parse(result);
117+
const version: VersionCheckResult = JSON.parse(result);
118118

119-
if (queryResult.success !== true) {
120-
throw new Error(queryResult.error || `Failed to run query (unknown error)`);
119+
if (version.success !== true) {
120+
throw new Error(version.error || `Failed to get version from backend`);
121121
}
122122

123-
return queryResult.data;
124-
}
125-
126-
127-
async pagingQuery(correlation_id: string, sql: string, rowsToFetch: number = 1000): Promise<QueryResult> {
128-
129-
const queryObject = {
130-
id: correlation_id,
131-
type: `sql`,
132-
sql,
133-
rows: rowsToFetch
134-
};
135-
136-
const result = await this.send(JSON.stringify(queryObject));
137-
138-
const queryResult: QueryResult = JSON.parse(result);
139-
140-
if (queryResult.success !== true) {
141-
throw new Error(queryResult.error || `Failed to run query (unknown error)`);
142-
}
143-
return queryResult;
123+
return version;
144124
}
145-
async pagingQueryMoreData(correlation_id: string, rowsToFetch: number = 1000): Promise<QueryResult> {
146-
147-
const queryObject = {
125+
async getTraceData(): Promise<GetTraceDataResult> {
126+
const tracedataReqObj = {
148127
id: `boop`,
149-
cont_id: correlation_id,
150-
type: `sqlmore`,
151-
rows: rowsToFetch
128+
type: `gettracedata`
152129
};
153130

154-
const result = await this.send(JSON.stringify(queryObject));
131+
const result = await this.send(JSON.stringify(tracedataReqObj));
155132

156-
const queryResult: QueryResult = JSON.parse(result);
133+
const rpy: GetTraceDataResult = JSON.parse(result);
157134

158-
if (queryResult.success !== true) {
159-
throw new Error(queryResult.error || `Failed to run query (unknown error)`);
135+
if (rpy.success !== true) {
136+
throw new Error(rpy.error || `Failed to get trace data from backend`);
160137
}
161-
return queryResult;
138+
return rpy;
162139
}
163-
164-
async getVersion(): Promise<VersionCheckResult> {
165-
const verObj = {
140+
//TODO: add/modify this API to allow manipulation of JTOpen tracing, and add tests
141+
async setTraceConfig(dest: ServerTraceDest, level: ServerTraceLevel): Promise<SetConfigResult> {
142+
const reqObj = {
166143
id: `boop`,
167-
type: `getversion`
144+
type: `setconfig`,
145+
tracedest: dest,
146+
tracelevel: level
168147
};
169148

170-
const result = await this.send(JSON.stringify(verObj));
149+
const result = await this.send(JSON.stringify(reqObj));
171150

172-
const version: VersionCheckResult = JSON.parse(result);
151+
const rpy: SetConfigResult = JSON.parse(result);
173152

174-
if (version.success !== true) {
175-
throw new Error(version.error || `Failed to get version from backend`);
153+
if (rpy.success !== true) {
154+
throw new Error(rpy.error || `Failed to set trace options on backend`);
176155
}
177-
178-
return version;
156+
return rpy;
179157
}
180-
async clcommand(cmd: string): Promise<CLCommandResult> {
181-
const cmdObj = {
182-
id: `boop`,
183-
type: `cl`,
184-
cmd: cmd
185-
};
186-
const result = await this.send(JSON.stringify(cmdObj));
187158

188-
const commandResult: CLCommandResult = JSON.parse(result);
189-
return commandResult;
159+
clcommand(cmd: string): Query<any> {
160+
return new Query(this, cmd, {isClCommand: true})
190161
}
191162

192163
async close() {

src/connection/types.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,33 @@ export interface VersionCheckResult extends ServerResponse {
1414
version: string;
1515
}
1616

17-
export interface QueryResult extends ServerResponse {
17+
export interface GetTraceDataResult extends ServerResponse {
18+
tracedata: string
19+
}
20+
21+
export enum ServerTraceLevel {
22+
OFF = "OFF", // off
23+
ON = "ON", // all except datastream
24+
ERRORS = "ERRORS", // errors only
25+
DATASTREAM = "DATASTREAM" // all including datastream
26+
}
27+
export enum ServerTraceDest {
28+
FILE = "FILE",
29+
IN_MEM = "IN_MEM"
30+
}
31+
export interface QueryOptions {
32+
isClCommand: boolean,
33+
parameters?: any[]
34+
}
35+
export interface SetConfigResult extends ServerResponse {
36+
tracedest: ServerTraceDest,
37+
tracelevel: ServerTraceLevel
38+
}
39+
40+
export interface QueryResult<T> extends ServerResponse {
1841
metadata: QueryMetaData,
1942
is_done: boolean;
20-
data: any[];
43+
data: T[];
2144
}
2245

2346
export interface JobLogEntry {

0 commit comments

Comments
 (0)