diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 17b8cacf3..8c4452ed0 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -18,6 +18,7 @@ import * as configVars from './configVars'; import { DebugConfiguration } from "./debug/config"; import { debugPTFInstalled } from "./debug/server"; import { CopyToImport } from "../components/copyToImport"; +import { Db2iExt } from "../components/db2iExt"; export interface MemberParts extends IBMiMember { basename: string @@ -1316,6 +1317,12 @@ export default class IBMi { let returningAsCsv: WrapResult | undefined; + const db2iExt = this.getComponent(`Db2iExt`); + if (db2iExt && db2iExt.isReady()) { + const list = input.split(`\n`).join(` `).split(`;`).filter(x => x.trim().length > 0); + return await db2iExt.executeMany(list); + } + if (this.qccsid === 65535) { let list = input.split(`\n`).join(` `).split(`;`).filter(x => x.trim().length > 0); const lastStmt = list.pop()?.trim(); diff --git a/src/api/import/db2i.d.ts b/src/api/import/db2i.d.ts new file mode 100644 index 000000000..3e6ea7b7a --- /dev/null +++ b/src/api/import/db2i.d.ts @@ -0,0 +1,237 @@ +export interface Db2i { + sqlJob: (options?: JDBCOptions) => SQLJob; +} + +export interface JDBCOptions {} + +export declare enum JobStatus { + NotStarted = "notStarted", + Ready = "ready", + Busy = "busy", + Ended = "ended" +} +export declare enum ExplainType { + Run = 0, + DoNotRun = 1 +} + +export interface ServerResponse { + id: string; + success: boolean; + error?: string; + sql_rc: number; + sql_state: string; +} +export interface ConnectionResult extends ServerResponse { + job: string; +} +export interface VersionCheckResult extends ServerResponse { + build_date: string; + version: string; +} +export interface ExplainResults extends QueryResult { + vemetadata: QueryMetaData; + vedata: any; +} +export interface GetTraceDataResult extends ServerResponse { + tracedata: string; +} +export declare enum ServerTraceLevel { + OFF = "OFF", + ON = "ON", + ERRORS = "ERRORS", + DATASTREAM = "DATASTREAM" +} +export declare enum ServerTraceDest { + FILE = "FILE", + IN_MEM = "IN_MEM" +} +export interface QueryOptions { + isTerseResults?: boolean; + isClCommand?: boolean; + parameters?: any[]; + autoClose?: boolean; +} +export interface SetConfigResult extends ServerResponse { + tracedest: ServerTraceDest; + tracelevel: ServerTraceLevel; +} +export interface QueryResult extends ServerResponse { + metadata: QueryMetaData; + is_done: boolean; + has_results: boolean; + update_count: number; + data: T[]; +} +export interface JobLogEntry { + MESSAGE_ID: string; + SEVERITY: string; + MESSAGE_TIMESTAMP: string; + FROM_LIBRARY: string; + FROM_PROGRAM: string; + MESSAGE_TYPE: string; + MESSAGE_TEXT: string; + MESSAGE_SECOND_LEVEL_TEXT: string; +} +export interface CLCommandResult extends ServerResponse { + joblog: JobLogEntry[]; +} +export interface QueryMetaData { + column_count: number; + columns: ColumnMetaData[]; + job: string; +} +export interface ColumnMetaData { + display_size: number; + label: string; + name: string; + type: string; +} +export declare type Rows = { + [column: string]: string | number | boolean; +}[]; +export interface JDBCOptions { + "naming"?: "sql" | "system"; + "date format"?: "mdy" | "dmy" | "ymd" | "usa" | "iso" | "eur" | "jis" | "julian"; + "date separator"?: "/" | "-" | "." | "," | "b"; + "decimal separator"?: "." | ","; + "time format"?: "hms" | "usa" | "iso" | "eur" | "jis"; + "time separator"?: ":" | "." | "," | "b"; + "full open"?: boolean; + "access"?: "all" | "read call" | "read only"; + "autocommit exception"?: boolean; + "bidi string type"?: "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11"; + "bidi implicit reordering"?: boolean; + "bidi numeric ordering"?: boolean; + "data truncation"?: boolean; + "driver"?: "toolbox" | "native"; + "errors"?: "full" | "basic"; + "extended metadata"?: boolean; + "hold input locators"?: boolean; + "hold statements"?: boolean; + "ignore warnings"?: string; + "keep alive"?: boolean; + "key ring name"?: string; + "key ring password"?: string; + "metadata source"?: "0" | "1"; + "proxy server"?: string; + "remarks"?: "sql" | "system"; + "secondary URL"?: string; + "secure"?: boolean; + "server trace"?: "0" | "2" | "4" | "8" | "16" | "32" | "64"; + "thread used"?: boolean; + "toolbox trace"?: "" | "none" | "datastream" | "diagnostic" | "error" | "warning" | "conversion" | "jdbc" | "pcml" | "all" | "proxy" | "thread" | "information"; + "trace"?: boolean; + "translate binary"?: boolean; + "translate boolean"?: boolean; + "libraries"?: string[]; + "auto commit"?: boolean; + "concurrent access resolution"?: "1" | "2" | "3"; + "cursor hold"?: boolean; + "cursor sensitivity"?: "asensitive" | "insensitive" | "sensitive"; + "database name"?: string; + "decfloat rounding mode"?: "half even" | "half up" | "down" | "ceiling" | "floor" | "up" | "half down"; + "maximum precision"?: "31" | "63"; + "maximum scale"?: string; + "minimum divide scale"?: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; + "package ccsid"?: "1200" | "13488" | "system"; + "transaction isolation"?: "none" | "read uncommitted" | "read committed" | "repeatable read" | "serializable"; + "translate hex"?: "character" | "binary"; + "true autocommit"?: boolean; + "XA loosely coupled support"?: "0" | "1"; + "big decimal"?: boolean; + "block criteria"?: "0" | "1" | "2"; + "block size"?: "0" | "8" | "16" | "32" | "64" | "128" | "256" | "512"; + "data compression"?: boolean; + "extended dynamic"?: boolean; + "lazy close"?: boolean; + "lob threshold"?: string; + "maximum blocked input rows"?: string; + "package"?: string; + "package add"?: boolean; + "package cache"?: boolean; + "package criteria"?: "default" | "select"; + "package error"?: "exception" | "warning" | "none"; + "package library"?: string; + "prefetch"?: boolean; + "qaqqinilib"?: string; + "query optimize goal"?: "0" | "1" | "2"; + "query timeout mechanism"?: "qqrytimlmt" | "cancel"; + "query storage limit"?: string; + "receive buffer size"?: string; + "send buffer size"?: string; + "vairiable field compression"?: boolean; + "sort"?: "hex" | "language" | "table"; + "sort language"?: string; + "sort table"?: string; + "sort weight"?: "shared" | "unique"; +} + + +export declare enum TransactionEndType { + COMMIT = 0, + ROLLBACK = 1 +} +export declare class SQLJob { + options: JDBCOptions; + private static uniqueIdCounter; + private channel; + private responseEmitter; + private status; + private traceFile; + private isTracingChannelData; + private uniqueId; + id: string | undefined; + static getNewUniqueId(prefix?: string): string; + static useExec(): Promise; + constructor(options?: JDBCOptions); + private getChannel; + send(content: string): Promise; + getStatus(): JobStatus; + connect(): Promise; + query(sql: string, opts?: QueryOptions): Query; + requestCancel(): Promise; + getVersion(): Promise; + explain(statement: string, type?: ExplainType): Promise>; + getTraceFilePath(): string | undefined; + getTraceData(): Promise; + setTraceConfig(dest: ServerTraceDest, level: ServerTraceLevel): Promise; + clcommand(cmd: string): Query; + getJobLog(): Promise>; + underCommitControl(): boolean; + getPendingTransactions(): Promise; + endTransaction(type: TransactionEndType): Promise>; + getUniqueId(): string; + close(): Promise; + dispose(): void; +} + +export declare enum QueryState { + NOT_YET_RUN = 1, + RUN_MORE_DATA_AVAILABLE = 2, + RUN_DONE = 3, + ERROR = 4 +} +export declare class Query { + private job; + private static globalQueryList; + private correlationId; + private sql; + private isPrepared; + private parameters; + private rowsToFetch; + private isCLCommand; + private state; + private isTerseResults; + shouldAutoClose: boolean; + constructor(job: SQLJob, query: string, opts?: QueryOptions); + static byId(id: string): Query; + static getOpenIds(forJob?: SQLJob): string[]; + static cleanup(): Promise; + run(rowsToFetch?: number): Promise>; + fetchMore(rowsToFetch?: number): Promise>; + close(): Promise; + getHostJob(): SQLJob; + getId(): string; + getState(): QueryState; +} diff --git a/src/components/component.ts b/src/components/component.ts index 291e44ba9..52262ce76 100644 --- a/src/components/component.ts +++ b/src/components/component.ts @@ -1,5 +1,6 @@ import IBMi from "../api/IBMi"; import { CopyToImport } from "./copyToImport"; +import { Db2iExt } from "./db2iExt"; import { GetMemberInfo } from "./getMemberInfo"; import { GetNewLibl } from "./getNewLibl"; import { IfsWrite } from "./ifsWrite"; @@ -17,6 +18,7 @@ interface ComponentRegistry { IfsWrite?: IfsWrite; CopyToImport?: CopyToImport; GetMemberInfo?: GetMemberInfo; + Db2iExt?: Db2iExt; } export type ComponentId = keyof ComponentRegistry; @@ -50,6 +52,9 @@ export class ComponentManager { this.registered.GetMemberInfo = new GetMemberInfo(connection); await ComponentManager.checkState(this.registered.GetMemberInfo); + + this.registered.Db2iExt = new Db2iExt(connection); + await ComponentManager.checkState(this.registered.Db2iExt); } // TODO: return type based on ComponentIds diff --git a/src/components/db2iExt.ts b/src/components/db2iExt.ts new file mode 100644 index 000000000..d23a7f5aa --- /dev/null +++ b/src/components/db2iExt.ts @@ -0,0 +1,80 @@ +import IBMi from "../api/IBMi"; +import { ComponentState, ComponentT } from "./component"; +import { Extension, extensions } from "vscode"; +import { Db2i, SQLJob } from "../api/import/db2i"; +import { Tools } from "../api/Tools"; + +export class Db2iExt implements ComponentT { + public state: ComponentState = ComponentState.NotInstalled; + public currentVersion: number = 1; + private ext: Extension|undefined = undefined; + private job: SQLJob|undefined = undefined; + + constructor(public connection: IBMi) { } + + async getInstalledVersion(): Promise { + return 1; + } + + async checkState(): Promise { + this.ext = extensions.getExtension(`halcyontechltd.vscode-db2i`); + + if (this.ext && this.ext.isActive) { + this.state = ComponentState.Installed; + + this.ensureJob(); + + return true; + } + + return false; + } + + async ensureJob() { + if (this.ext && !this.job) { + try { + const job = this.ext?.exports.sqlJob({}); + await job.connect(); + this.job = job; + } catch (e) { + this.state = ComponentState.Error; + this.job = undefined; + } + } + } + + isReady() { + return this.state === ComponentState.Installed && this.job !== undefined; + } + + /** + * Only returns the result set of the last statement + */ + async executeMany(statements: string[]) { + const last = statements.length - 1; + let i = 0; + + for (const statement of statements) { + const result = await this.executeSingle(statement); + + if (i === last) { + return result as Tools.DB2Row[]; + } + } + + return []; + } + + async executeSingle(statement: string) { + statement = statement.replace(new RegExp(`for bit data`, `gi`), ``); + + const query = await this.job!.query(statement, {autoClose: true}) + const result = await query.run(99999); + query.close(); + return result.data as Tools.DB2Row[]; + } + + getState(): ComponentState { + return this.state; + } +} \ No newline at end of file