From 6c822613b6661c4580cacfbe5175f690f4771453 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 12:01:43 -0400 Subject: [PATCH 01/12] Ability to run multiple statements Signed-off-by: worksofliam --- package.json | 38 ++++- src/connection/manager.ts | 24 +-- src/language/providers/problemProvider.ts | 6 +- src/views/results/contributes.json | 38 ++++- src/views/results/index.ts | 179 +++++++++++++++++----- 5 files changed, 227 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 3836c67a..9508e786 100644 --- a/package.json +++ b/package.json @@ -436,6 +436,21 @@ "category": "Db2 for i", "icon": "$(window)" }, + { + "command": "vscode-db2i.runEditorStatement.multiple.all", + "title": "Run all statements", + "category": "Db2 for i" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.selected", + "title": "Run selected statements", + "category": "Db2 for i" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.from", + "title": "Run statements from cursor", + "category": "Db2 for i" + }, { "command": "vscode-db2i.statement.cancel", "title": "Cancel", @@ -1118,22 +1133,37 @@ { "command": "vscode-db2i.runEditorStatement.inView", "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true", - "group": "navigation@1" + "group": "navigation@2" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.all", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && !editorHasSelection", + "group": "navigation_multiple@1" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.selected", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && editorHasSelection", + "group": "navigation_multiple@2" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.from", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && !editorHasSelection", + "group": "navigation_multiple@2" }, { "command": "vscode-db2i.editorExplain.withRun", "when": "editorLangId == sql", - "group": "2_explain@1" + "group": "navigation_explain@1" }, { "command": "vscode-db2i.editorExplain.withoutRun", "when": "editorLangId == sql", - "group": "2_explain@2" + "group": "navigation_explain@2" }, { "command": "vscode-db2i.notebook.fromSqlUri", "when": "editorLangId == sql", - "group": "3_notebook@1" + "group": "navigation_notebook@1" } ], "notebook/toolbar": [ diff --git a/src/connection/manager.ts b/src/connection/manager.ts index 9caf802e..4bbae9a2 100644 --- a/src/connection/manager.ts +++ b/src/connection/manager.ts @@ -5,7 +5,7 @@ import { OldSQLJob } from "./sqlJob"; import { askAboutNewJob, onConnectOrServerInstall, osDetail } from "../config"; import { SelfValue } from "../views/jobManager/selfCodes/nodes"; import Configuration from "../configuration"; -import { QueryOptions } from "@ibm/mapepire-js/dist/src/types"; +import { QueryOptions, QueryResult } from "@ibm/mapepire-js/dist/src/types"; import { Query } from "@ibm/mapepire-js/dist/src/query"; export interface JobInfo { @@ -115,18 +115,9 @@ export class SQLJobManager { return this.jobs[jobExists]; } - /** - * Runs SQL - * @param query the SQL query - * @param parameters the list of parameters (indicated by '?' parameter parkers in the SQL query) - * @param isTerseResults whether the returned data is in terse format. When set to true, the data is returned as an array - * of arrays. When set to false, data is returned as an array of objects (compatible with legacy API). - * @returns - */ - async runSQL(query: string, opts?: QueryOptions): Promise { + async runSQL(query: string, opts?: QueryOptions, rowsToFetch = 2147483647): Promise { // 2147483647 is NOT arbitrary. On the server side, this is processed as a Java // int. This is the largest number available without overflow (Integer.MAX_VALUE) - const rowsToFetch = 2147483647; const statement = await this.getPagingStatement(query, opts); const results = await statement.execute(rowsToFetch); @@ -134,6 +125,17 @@ export class SQLJobManager { return results.data; } + async runSQLVerbose(query: string, opts?: QueryOptions, rowsToFetch = 2147483647): Promise> { + // 2147483647 is NOT arbitrary. On the server side, this is processed as a Java + // int. This is the largest number available without overflow (Integer.MAX_VALUE) + + const statement = await this.getPagingStatement(query, opts); + const results = await statement.execute(rowsToFetch); + statement.close(); + + return results; + } + async getPagingStatement(query: string, opts?: QueryOptions): Promise> { const selected = this.jobs[this.selectedJob] if (ServerComponent.isInstalled() && selected) { diff --git a/src/language/providers/problemProvider.ts b/src/language/providers/problemProvider.ts index 57c1917f..83623399 100644 --- a/src/language/providers/problemProvider.ts +++ b/src/language/providers/problemProvider.ts @@ -1,4 +1,4 @@ -import { commands, CompletionItemKind, Diagnostic, DiagnosticSeverity, languages, ProgressLocation, Range, TextDocument, Uri, window, workspace } from "vscode"; +import { commands, CompletionItemKind, Diagnostic, Disposable, DiagnosticSeverity, languages, ProgressLocation, Range, TextDocument, Uri, window, workspace } from "vscode"; import { SQLType, } from "../../database/schemas"; @@ -73,7 +73,9 @@ export const checkDocumentDefintion = commands.registerCommand(CHECK_DOCUMENT_CO } }); -export const problemProvider = [ +export const problemProvider: Disposable[] = [ + sqlDiagnosticCollection, + workspace.onDidCloseTextDocument(e => { // Only clear errors from unsaved files. if (e.isUntitled) { diff --git a/src/views/results/contributes.json b/src/views/results/contributes.json index d3425790..9f0329bc 100644 --- a/src/views/results/contributes.json +++ b/src/views/results/contributes.json @@ -51,6 +51,21 @@ "category": "Db2 for i", "icon": "$(window)" }, + { + "command": "vscode-db2i.runEditorStatement.multiple.all", + "title": "Run all statements", + "category": "Db2 for i" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.selected", + "title": "Run selected statements", + "category": "Db2 for i" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.from", + "title": "Run statements from cursor", + "category": "Db2 for i" + }, { "command": "vscode-db2i.statement.cancel", "title": "Cancel", @@ -111,22 +126,37 @@ { "command": "vscode-db2i.runEditorStatement.inView", "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true", - "group": "navigation@1" + "group": "navigation@2" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.all", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && !editorHasSelection", + "group": "navigation_multiple@1" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.selected", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && editorHasSelection", + "group": "navigation_multiple@2" + }, + { + "command": "vscode-db2i.runEditorStatement.multiple.from", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && !editorHasSelection", + "group": "navigation_multiple@2" }, { "command": "vscode-db2i.editorExplain.withRun", "when": "editorLangId == sql", - "group": "2_explain@1" + "group": "navigation_explain@1" }, { "command": "vscode-db2i.editorExplain.withoutRun", "when": "editorLangId == sql", - "group": "2_explain@2" + "group": "navigation_explain@2" }, { "command": "vscode-db2i.notebook.fromSqlUri", "when": "editorLangId == sql", - "group": "3_notebook@1" + "group": "navigation_notebook@1" } ], "view/title": [ diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 113c2dcd..c732c1b6 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -23,6 +23,8 @@ export type StatementQualifier = "statement" | "update" | "explain" | "onlyexpla export interface StatementInfo { content: string, qualifier: StatementQualifier, + group?: StatementGroup, + noUi?: boolean, open?: boolean, viewColumn?: ViewColumn, viewFocus?: boolean, @@ -140,6 +142,10 @@ export function initialise(context: vscode.ExtensionContext) { doveNodeView.close(); }), + vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.multiple.all`, () => { runMultipleHandler(`all`) }), + vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.multiple.selected`, () => { runMultipleHandler(`selected`) }), + vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.multiple.from`, () => { runMultipleHandler(`from`) }), + vscode.commands.registerCommand(`vscode-db2i.editorExplain.withRun`, (options?: StatementInfo) => { runHandler({ qualifier: `explain`, ...options }) }), vscode.commands.registerCommand(`vscode-db2i.editorExplain.withoutRun`, (options?: StatementInfo) => { runHandler({ qualifier: `onlyexplain`, ...options }) }), vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.inView`, (options?: StatementInfo) => { runHandler({ viewColumn: ViewColumn.Beside, ...options }) }), @@ -147,6 +153,81 @@ export function initialise(context: vscode.ExtensionContext) { ) } +const ALLOWED_PREFIXES_FOR_MULTIPLE: StatementQualifier[] = [`cl`, `json`, `csv`, `sql`, `statement`]; + +function isStop(statement: Statement) { + return (statement.type === StatementType.Unknown && statement.tokens.length === 1 && statement.tokens[0].value.toUpperCase() === `STOP`); +} + +async function runMultipleHandler(mode: `all`|`selected`|`from`) { + const editor = vscode.window.activeTextEditor; + if (editor && editor.document.languageId === `sql`) { + const selection = editor.selection; + const startPos = editor.document.offsetAt(selection.start); + const endPos = editor.document.offsetAt(selection.end); + + const sqlDocument = new Document(editor.document.getText()); + const statementGroups = sqlDocument.getStatementGroups(); + + let statementsToRun: StatementGroup[]; + + switch (mode) { + case `selected`: statementsToRun = statementGroups.filter(group => (group.range.start >= startPos && group.range.end <= endPos)); break; + case `from`: statementsToRun = statementGroups.filter(group => (startPos <= group.range.end)); break; + default: statementsToRun = statementGroups; + } + + const statementInfos: StatementInfo[] = []; + + for (let i = 0; i < statementsToRun.length; i++) { + let group = statementsToRun[i]; + + if (group.statements.length >= 1) { + const statement = group.statements[0]; + + if (isStop(statement)) { + break; + } + + const label = statement.getLabel(); + const prefix = (label as StatementQualifier) || `statement`; + + if (!ALLOWED_PREFIXES_FOR_MULTIPLE.includes(prefix)) { + vscode.window.showErrorMessage(`Cannot run multiple statements with prefix ${prefix}.`); + editor.selection = new vscode.Selection( + editor.document.positionAt(group.range.start), + editor.document.positionAt(group.range.start + label.length) + ); + return; + } + + statementInfos.push({ + content: sqlDocument.content.substring( + group.range.start, group.range.end + ), + group: statementsToRun[i], + qualifier: prefix, + noUi: i < statementsToRun.length - 1, // Only the last one should have a UI + }); + } + } + + if (statementInfos.length === 0) { + vscode.window.showErrorMessage(`No statements to run.`); + return; + } + + for (let statementInfo of statementInfos) { + try { + await runHandler(statementInfo); + } catch (e) { + vscode.window.showErrorMessage(`Error running statement: ${e instanceof Error ? e.message : e}`); + break; + } + } + } +} + async function runHandler(options?: StatementInfo) { if (options === undefined || options.viewColumn === undefined) { await resultSetProvider.ensureActivation(); @@ -180,7 +261,7 @@ async function runHandler(options?: StatementInfo) { } if (editor) { - const group = statementDetail.group; + let group = statementDetail.group; editor.selection = new vscode.Selection(editor.document.positionAt(group.range.start), editor.document.positionAt(group.range.end)); if (group.statements.length === 1 && statementDetail.embeddedInfo && statementDetail.embeddedInfo.changed) { @@ -214,23 +295,38 @@ async function runHandler(options?: StatementInfo) { const inWindow = Boolean(options && options.viewColumn); if (statementDetail.qualifier === `cl`) { - if (inWindow) { - useWindow(`CL results`, options.viewColumn); + // TODO: handle noUi + if (options.noUi) { + const command = statementDetail.content.split(` `)[0].toUpperCase(); + + chosenView.setLoadingText(`Running CL command... (${command})`, false); + await JobManager.runSQLVerbose(statementDetail.content, {isClCommand: true}); // Can throw + + } else { + if (inWindow) { + useWindow(`CL results`, options.viewColumn); + } + chosenView.setScrolling(statementDetail.content, true); // Never errors } - chosenView.setScrolling(statementDetail.content, true); // Never errors } else if ([`statement`, `update`].includes(statementDetail.qualifier)) { // If it's a basic statement, we can let it scroll! - if (inWindow) { - useWindow(possibleTitle, options.viewColumn); - } + if (statementDetail.noUi) { + chosenView.setLoadingText(`Running SQL statement... (${possibleTitle})`, false); + await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); - let updatableTable: ObjectRef | undefined; - if (statementDetail.qualifier === `update` && statement.type === StatementType.Select && refs.length === 1) { - updatableTable = refs[0]; - } + } else { + if (inWindow) { + useWindow(possibleTitle, options.viewColumn); + } - chosenView.setScrolling(statementDetail.content, false, undefined, inWindow, updatableTable); // Never errors + let updatableTable: ObjectRef | undefined; + if (statementDetail.qualifier === `update` && statement.type === StatementType.Select && refs.length === 1) { + updatableTable = refs[0]; + } + + chosenView.setScrolling(statementDetail.content, false, undefined, inWindow, updatableTable); // Never errors + } } else if ([`explain`, `onlyexplain`].includes(statementDetail.qualifier)) { // If it's an explain, we need to @@ -259,6 +355,7 @@ async function runHandler(options?: StatementInfo) { } else { vscode.window.showInformationMessage(`No job currently selected.`); } + } else { // Otherwise... it's a bit complicated. chosenView.setLoadingText(`Executing SQL statement...`, false); @@ -348,6 +445,10 @@ async function runHandler(options?: StatementInfo) { } else { vscode.window.showErrorMessage(errorText); } + + if (options.noUi) { + throw new Error(errorText); + } } updateStatusBar(); @@ -369,14 +470,16 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem if (existingInfo) { statementInfo = { ...existingInfo, - group: undefined, + group: existingInfo.group, statement: undefined, embeddedInfo: undefined }; - // Running from existing data - sqlDocument = new Document(statementInfo.content); - statementInfo.group = sqlDocument.getStatementGroups()[0]; + if (!existingInfo.group) { + // Running from existing data + sqlDocument = new Document(statementInfo.content); + statementInfo.group = sqlDocument.getStatementGroups()[0]; + } } else if (editor) { // Is being run from the editor @@ -386,34 +489,36 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem sqlDocument = new Document(document.getText()); statementInfo.group = sqlDocument.getGroupByOffset(cursor); + } - if (statementInfo.group) { - statementInfo.content = sqlDocument.content.substring( - statementInfo.group.range.start, statementInfo.group.range.end - ); - } + statementInfo.statement = statementInfo.group.statements[0]; - if (statementInfo.content) { - [`cl`, `json`, `csv`, `sql`, `explain`, `update`].forEach(mode => { - if (statementInfo.content.trim().toLowerCase().startsWith(mode + `:`)) { - statementInfo.content = statementInfo.content.substring(mode.length + 1).trim(); + if (statementInfo.group && !statementInfo.content) { + statementInfo.content = sqlDocument.content.substring( + statementInfo.group.range.start, statementInfo.group.range.end + ); + } - //@ts-ignore We know the type. - statementInfo.qualifier = mode; - } - }); - } + if (statementInfo.content) { + [`cl`, `json`, `csv`, `sql`, `explain`, `update`].forEach(mode => { + if (statementInfo.content.trim().toLowerCase().startsWith(mode + `:`)) { + statementInfo.content = statementInfo.content.substring(mode.length + 1).trim(); - if (statementInfo.qualifier === `cl`) { - const eol = document.eol === vscode.EndOfLine.CRLF ? `\r\n` : `\n`; - statementInfo.content = statementInfo.content.split(eol).map(line => line.trim()).join(` `); - } + //@ts-ignore We know the type. + statementInfo.qualifier = mode; + } + }); } - statementInfo.statement = statementInfo.group.statements[0]; + if (editor && statementInfo.qualifier === `cl`) { + const eol = editor.document.eol === vscode.EndOfLine.CRLF ? `\r\n` : `\n`; + statementInfo.content = statementInfo.content.split(eol).map(line => line.trim()).join(` `); + } - if (statementInfo.qualifier !== `cl`) { - statementInfo.embeddedInfo = sqlDocument.removeEmbeddedAreas(statementInfo.statement, true); + if (sqlDocument) { + if (statementInfo.qualifier !== `cl`) { + statementInfo.embeddedInfo = sqlDocument.removeEmbeddedAreas(statementInfo.statement, true); + } } return statementInfo; From e3c580812ae3d1f7a8de4e2e9acd983fb0855ee3 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 12:04:26 -0400 Subject: [PATCH 02/12] No point enabling stop if it's the first item in the list Signed-off-by: worksofliam --- src/views/results/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index c732c1b6..0366d396 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -185,7 +185,7 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { if (group.statements.length >= 1) { const statement = group.statements[0]; - if (isStop(statement)) { + if (isStop(statement) && i > 0) { break; } From ceabe1cc083b6fdda5f5dd56db6f9a0b5cf89a2c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 16:21:44 -0400 Subject: [PATCH 03/12] Improvements to running selected statements Signed-off-by: worksofliam --- src/views/results/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 0366d396..c764d287 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -172,7 +172,12 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { let statementsToRun: StatementGroup[]; switch (mode) { - case `selected`: statementsToRun = statementGroups.filter(group => (group.range.start >= startPos && group.range.end <= endPos)); break; + case `selected`: + const doc = editor.document; + const firstStatement = statementGroups.findIndex(group => (startPos >= group.range.start && startPos <= group.range.end)); + const lastStatement = statementGroups.findIndex(group => (endPos >= group.range.start && endPos <= group.range.end)); + statementsToRun = statementGroups.slice(firstStatement, lastStatement + 1); + break; case `from`: statementsToRun = statementGroups.filter(group => (startPos <= group.range.end)); break; default: statementsToRun = statementGroups; } From 053668d5df177141992158de85141d2f7e7bf362 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 16:30:33 -0400 Subject: [PATCH 04/12] CL now throws correctly when it fails. Signed-off-by: worksofliam --- src/views/results/index.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index c764d287..26cd7ba0 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -226,7 +226,7 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { try { await runHandler(statementInfo); } catch (e) { - vscode.window.showErrorMessage(`Error running statement: ${e instanceof Error ? e.message : e}`); + // No error needed. runHandler still shows an error. break; } } @@ -305,7 +305,11 @@ async function runHandler(options?: StatementInfo) { const command = statementDetail.content.split(` `)[0].toUpperCase(); chosenView.setLoadingText(`Running CL command... (${command})`, false); - await JobManager.runSQLVerbose(statementDetail.content, {isClCommand: true}); // Can throw + // CL does not throw + const result = await JobManager.runSQLVerbose<{SUMMARY: string}>(statementDetail.content, {isClCommand: true}); + if (!result.success) { + throw new Error(result.data && result.data[0] ? result.data[0].SUMMARY : `CL command ${command} executed successfully.`); + } } else { if (inWindow) { @@ -318,7 +322,7 @@ async function runHandler(options?: StatementInfo) { // If it's a basic statement, we can let it scroll! if (statementDetail.noUi) { chosenView.setLoadingText(`Running SQL statement... (${possibleTitle})`, false); - await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); + await JobManager.runSQL(statementDetail.content, undefined, 1); } else { if (inWindow) { @@ -445,7 +449,7 @@ async function runHandler(options?: StatementInfo) { errorText = e.message || `Error running SQL statement.`; } - if ([`statement`, `explain`, `onlyexplain`].includes(statementDetail.qualifier) && statementDetail.history !== false) { + if ([`statement`, `explain`, `onlyexplain`, `cl`].includes(statementDetail.qualifier) && statementDetail.history !== false) { chosenView.setError(errorText); } else { vscode.window.showErrorMessage(errorText); From fbed62902d2c796ce9719a6d8fd45f3dda31f348 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 16:31:19 -0400 Subject: [PATCH 05/12] Run all is always available Signed-off-by: worksofliam --- src/views/results/contributes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/results/contributes.json b/src/views/results/contributes.json index 9f0329bc..9f4f529d 100644 --- a/src/views/results/contributes.json +++ b/src/views/results/contributes.json @@ -130,7 +130,7 @@ }, { "command": "vscode-db2i.runEditorStatement.multiple.all", - "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && !editorHasSelection", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true", "group": "navigation_multiple@1" }, { From a4048cfeba2aebf3403daa1198357d2317cb239f Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 16:33:26 -0400 Subject: [PATCH 06/12] Run all is always available Signed-off-by: worksofliam --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9508e786..cc910760 100644 --- a/package.json +++ b/package.json @@ -1137,7 +1137,7 @@ }, { "command": "vscode-db2i.runEditorStatement.multiple.all", - "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true && !editorHasSelection", + "when": "editorLangId == sql && vscode-db2i:statementCanCancel != true", "group": "navigation_multiple@1" }, { From b2809391b4f706f3bcaba3515813b17c786ac1ed Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 19:02:07 -0400 Subject: [PATCH 07/12] Enable cancel button for non-UI tasks + fix stop causing no result set Signed-off-by: worksofliam --- src/views/results/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 26cd7ba0..3a4d258f 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -212,7 +212,7 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { ), group: statementsToRun[i], qualifier: prefix, - noUi: i < statementsToRun.length - 1, // Only the last one should have a UI + noUi: true }); } } @@ -222,6 +222,9 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { return; } + // Last statement should have UI + statementInfos[statementInfos.length - 1].noUi = false; + for (let statementInfo of statementInfos) { try { await runHandler(statementInfo); @@ -302,6 +305,7 @@ async function runHandler(options?: StatementInfo) { if (statementDetail.qualifier === `cl`) { // TODO: handle noUi if (options.noUi) { + setCancelButtonVisibility(true); const command = statementDetail.content.split(` `)[0].toUpperCase(); chosenView.setLoadingText(`Running CL command... (${command})`, false); @@ -321,6 +325,7 @@ async function runHandler(options?: StatementInfo) { } else if ([`statement`, `update`].includes(statementDetail.qualifier)) { // If it's a basic statement, we can let it scroll! if (statementDetail.noUi) { + setCancelButtonVisibility(true); chosenView.setLoadingText(`Running SQL statement... (${possibleTitle})`, false); await JobManager.runSQL(statementDetail.content, undefined, 1); @@ -460,6 +465,10 @@ async function runHandler(options?: StatementInfo) { } } + if (options.noUi) { + setCancelButtonVisibility(false); + } + updateStatusBar(); } } From 9c331f846e7d7765cdb07ff065283047b6b57a68 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 20:43:27 -0400 Subject: [PATCH 08/12] Scroll into VS Code when statement is executed Signed-off-by: worksofliam --- src/views/results/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 3a4d258f..7fd74c79 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -271,6 +271,7 @@ async function runHandler(options?: StatementInfo) { if (editor) { let group = statementDetail.group; editor.selection = new vscode.Selection(editor.document.positionAt(group.range.start), editor.document.positionAt(group.range.end)); + editor.revealRange(editor.selection); if (group.statements.length === 1 && statementDetail.embeddedInfo && statementDetail.embeddedInfo.changed) { editor.insertSnippet(new SnippetString(statementDetail.embeddedInfo.content)); From 28ab3195f6edcc64c88b84a79ec45adf8540c00a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 20:51:27 -0400 Subject: [PATCH 09/12] Change where noUi is referenced from Signed-off-by: worksofliam --- src/views/results/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 7fd74c79..afa18559 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -325,7 +325,7 @@ async function runHandler(options?: StatementInfo) { } else if ([`statement`, `update`].includes(statementDetail.qualifier)) { // If it's a basic statement, we can let it scroll! - if (statementDetail.noUi) { + if (options.noUi) { setCancelButtonVisibility(true); chosenView.setLoadingText(`Running SQL statement... (${possibleTitle})`, false); await JobManager.runSQL(statementDetail.content, undefined, 1); From c91b7405acb2f9b0a9808b4e1683cd2a0abb3869 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 21:11:40 -0400 Subject: [PATCH 10/12] Use correct object for noUi Signed-off-by: worksofliam --- src/views/results/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index afa18559..a3b545fd 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -305,7 +305,7 @@ async function runHandler(options?: StatementInfo) { if (statementDetail.qualifier === `cl`) { // TODO: handle noUi - if (options.noUi) { + if (statementDetail.noUi) { setCancelButtonVisibility(true); const command = statementDetail.content.split(` `)[0].toUpperCase(); @@ -325,7 +325,7 @@ async function runHandler(options?: StatementInfo) { } else if ([`statement`, `update`].includes(statementDetail.qualifier)) { // If it's a basic statement, we can let it scroll! - if (options.noUi) { + if (statementDetail.noUi) { setCancelButtonVisibility(true); chosenView.setLoadingText(`Running SQL statement... (${possibleTitle})`, false); await JobManager.runSQL(statementDetail.content, undefined, 1); @@ -461,12 +461,12 @@ async function runHandler(options?: StatementInfo) { vscode.window.showErrorMessage(errorText); } - if (options.noUi) { + if (statementDetail.noUi) { throw new Error(errorText); } } - if (options.noUi) { + if (statementDetail.noUi) { setCancelButtonVisibility(false); } From 218b33bf829425084799f6eacf040cfde2d7d832 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 22:32:05 -0400 Subject: [PATCH 11/12] Support for mixed case prefixes Signed-off-by: worksofliam --- src/views/results/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index a3b545fd..35931b98 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -173,7 +173,6 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { switch (mode) { case `selected`: - const doc = editor.document; const firstStatement = statementGroups.findIndex(group => (startPos >= group.range.start && startPos <= group.range.end)); const lastStatement = statementGroups.findIndex(group => (endPos >= group.range.start && endPos <= group.range.end)); statementsToRun = statementGroups.slice(firstStatement, lastStatement + 1); @@ -195,7 +194,7 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { } const label = statement.getLabel(); - const prefix = (label as StatementQualifier) || `statement`; + const prefix = (label || `statement`).toLowerCase() as StatementQualifier; if (!ALLOWED_PREFIXES_FOR_MULTIPLE.includes(prefix)) { vscode.window.showErrorMessage(`Cannot run multiple statements with prefix ${prefix}.`); From 67644d2a85c570e1a55305488ab7af6648305344 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 13:03:52 -0400 Subject: [PATCH 12/12] Improve range checking for statement groups in runMultipleHandler Signed-off-by: worksofliam --- src/views/results/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 35931b98..69ab7169 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -166,16 +166,22 @@ async function runMultipleHandler(mode: `all`|`selected`|`from`) { const startPos = editor.document.offsetAt(selection.start); const endPos = editor.document.offsetAt(selection.end); - const sqlDocument = new Document(editor.document.getText()); + const sqlDocument = new Document(editor.document.getText(), false); const statementGroups = sqlDocument.getStatementGroups(); let statementsToRun: StatementGroup[]; + const isInRange = (group: StatementGroup) => { + const groupStart = group.statements[0].tokens[0].range.start; + const groupEnd = group.statements[group.statements.length - 1].tokens[group.statements[group.statements.length - 1].tokens.length - 1].range.end; + + return (startPos >= groupStart && startPos <= groupEnd) || (endPos >= groupStart && endPos <= groupEnd) || + (groupStart >= startPos && groupStart <= endPos) || (groupEnd >= startPos && groupEnd <= endPos); + } + switch (mode) { case `selected`: - const firstStatement = statementGroups.findIndex(group => (startPos >= group.range.start && startPos <= group.range.end)); - const lastStatement = statementGroups.findIndex(group => (endPos >= group.range.start && endPos <= group.range.end)); - statementsToRun = statementGroups.slice(firstStatement, lastStatement + 1); + statementsToRun = statementGroups.filter(group => isInRange(group) || isInRange(group)) break; case `from`: statementsToRun = statementGroups.filter(group => (startPos <= group.range.end)); break; default: statementsToRun = statementGroups;