diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 74a3f787393..e420de7a165 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## [UNRELEASED] +- Add a command _CodeQL: Run Query on Multiple Databases_, which lets users select multiple databases to run a query on. [#898](https://github.com/github/vscode-codeql/pull/898) + ## 1.5.2 - 13 July 2021 - Add the _Add Database Source to Workspace_ command to the right-click context menu in the databases view. This lets users re-add a database's source folder to the workspace and browse the source code. [#891](https://github.com/github/vscode-codeql/pull/891) diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index ee8f6aca9a4..837a8b23282 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -245,6 +245,10 @@ "command": "codeQL.runQuery", "title": "CodeQL: Run Query" }, + { + "command": "codeQL.runQueryOnMultipleDatabases", + "title": "CodeQL: Run Query on Multiple Databases" + }, { "command": "codeQL.runRemoteQuery", "title": "CodeQL: Run Remote Query" @@ -680,6 +684,10 @@ "command": "codeQL.runQuery", "when": "resourceLangId == ql && resourceExtname == .ql" }, + { + "command": "codeQL.runQueryOnMultipleDatabases", + "when": "resourceLangId == ql && resourceExtname == .ql" + }, { "command": "codeQL.runRemoteQuery", "when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql" @@ -830,6 +838,10 @@ "command": "codeQL.runQuery", "when": "editorLangId == ql && resourceExtname == .ql" }, + { + "command": "codeQL.runQueryOnMultipleDatabases", + "when": "editorLangId == ql && resourceExtname == .ql" + }, { "command": "codeQL.runRemoteQuery", "when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql" diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 89b7ad802e6..0f2f2f47be6 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -10,7 +10,8 @@ import { Uri, window as Window, env, - window + window, + QuickPickItem } from 'vscode'; import { LanguageClient } from 'vscode-languageclient'; import * as os from 'os'; @@ -29,7 +30,7 @@ import { QueryServerConfigListener } from './config'; import * as languageSupport from './languageSupport'; -import { DatabaseManager } from './databases'; +import { DatabaseItem, DatabaseManager } from './databases'; import { DatabaseUI } from './databases-ui'; import { TemplateQueryDefinitionProvider, @@ -467,16 +468,18 @@ async function activateWithInstalledDistribution( selectedQuery: Uri | undefined, progress: ProgressCallback, token: CancellationToken, + databaseItem: DatabaseItem | undefined, ): Promise { if (qs !== undefined) { - const dbItem = await databaseUI.getDatabaseItem(progress, token); - if (dbItem === undefined) { + // If no databaseItem is specified, use the database currently selected in the Databases UI + databaseItem = databaseItem || await databaseUI.getDatabaseItem(progress, token); + if (databaseItem === undefined) { throw new Error('Can\'t run query without a selected database'); } const info = await compileAndRunQueryAgainstDatabase( cliServer, qs, - dbItem, + databaseItem, quickEval, selectedQuery, progress, @@ -549,13 +552,66 @@ async function activateWithInstalledDistribution( progress: ProgressCallback, token: CancellationToken, uri: Uri | undefined - ) => await compileAndRunQuery(false, uri, progress, token), + ) => await compileAndRunQuery(false, uri, progress, token, undefined), { title: 'Running query', cancellable: true } ) ); + interface DatabaseQuickPickItem extends QuickPickItem { + databaseItem: DatabaseItem; + } + ctx.subscriptions.push( + commandRunnerWithProgress( + 'codeQL.runQueryOnMultipleDatabases', + async ( + progress: ProgressCallback, + token: CancellationToken, + uri: Uri | undefined + ) => { + const quickPickItems = dbm.databaseItems.map(dbItem => ( + { + databaseItem: dbItem, + label: dbItem.name, + description: dbItem.language, + } + )); + /** + * Databases that were selected in the quick pick menu. + */ + const quickpick = await window.showQuickPick( + quickPickItems, + { canPickMany: true } + ); + if (quickpick !== undefined) { + // Collect all skipped databases and display them at the end (instead of popping up individual errors) + const skippedDatabases = []; + const errors = []; + for (const item of quickpick) { + try { + await compileAndRunQuery(false, uri, progress, token, item.databaseItem); + } catch (error) { + skippedDatabases.push(item.label); + errors.push(error.message); + } + } + if (skippedDatabases.length > 0) { + void logger.log(`Errors:\n${errors.join('\n')}`); + void helpers.showAndLogWarningMessage( + `The following databases were skipped:\n${skippedDatabases.join('\n')}.\nFor details about the errors, see the logs.` + ); + } + } else { + void helpers.showAndLogErrorMessage('No databases selected.'); + } + }, + { + title: 'Running query on selected databases', + cancellable: true + } + ) + ); ctx.subscriptions.push( commandRunnerWithProgress( 'codeQL.runQueries', @@ -611,7 +667,7 @@ async function activateWithInstalledDistribution( }); await Promise.all(queryUris.map(async uri => - compileAndRunQuery(false, uri, wrappedProgress, token) + compileAndRunQuery(false, uri, wrappedProgress, token, undefined) .then(() => queriesRemaining--) )); }, @@ -627,7 +683,7 @@ async function activateWithInstalledDistribution( progress: ProgressCallback, token: CancellationToken, uri: Uri | undefined - ) => await compileAndRunQuery(true, uri, progress, token), + ) => await compileAndRunQuery(true, uri, progress, token, undefined), { title: 'Running query', cancellable: true diff --git a/extensions/ql-vscode/src/run-queries.ts b/extensions/ql-vscode/src/run-queries.ts index 4a90532feb2..cd9a5429dce 100644 --- a/extensions/ql-vscode/src/run-queries.ts +++ b/extensions/ql-vscode/src/run-queries.ts @@ -563,7 +563,7 @@ export async function compileAndRunQueryAgainstDatabase( const dbSchemaName = path.basename(db.contents.dbSchemeUri.fsPath); if (querySchemaName != dbSchemaName) { void logger.log(`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`); - throw new Error(`The query ${path.basename(queryPath)} cannot be run against the selected database: their target languages are different. Please select a different database and try again.`); + throw new Error(`The query ${path.basename(queryPath)} cannot be run against the selected database (${db.name}): their target languages are different. Please select a different database and try again.`); } const qlProgram: messages.QlProgram = {