Move UI-related code from QueryRunner into LocalQueries

This commit is contained in:
Dave Bartolomeo
2023-03-24 18:32:16 -04:00
parent f99f465365
commit d004f206f7
6 changed files with 534 additions and 610 deletions

View File

@@ -5,31 +5,18 @@ import {
TemplatePrintAstProvider,
TemplatePrintCfgProvider,
} from "./contextual/templateProvider";
import { compileAndRunQuery } from "./local-queries";
import { QueryRunner } from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { AstCfgCommands } from "./common/commands";
import { LocalQueries } from "./local-queries";
type AstCfgOptions = {
queryRunner: QueryRunner;
queryHistoryManager: QueryHistoryManager;
databaseUI: DatabaseUI;
localQueryResultsView: ResultsView;
queryStorageDir: string;
localQueries: LocalQueries;
astViewer: AstViewer;
astTemplateProvider: TemplatePrintAstProvider;
cfgTemplateProvider: TemplatePrintCfgProvider;
};
export function getAstCfgCommands({
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
localQueries,
astViewer,
astTemplateProvider,
cfgTemplateProvider,
@@ -59,12 +46,7 @@ export function getAstCfgCommands({
window.activeTextEditor?.document,
);
if (res) {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
await localQueries.compileAndRunQuery(
false,
res[0],
progress,

View File

@@ -115,10 +115,7 @@ import {
QueryServerCommands,
TestUICommands,
} from "./common/commands";
import {
getLocalQueryCommands,
showResultsForCompletedQuery,
} from "./local-queries";
import { LocalQueries } from "./local-queries";
import { getAstCfgCommands } from "./ast-cfg-commands";
import { getQueryEditorCommands } from "./query-editor";
import { App } from "./common/app";
@@ -712,12 +709,6 @@ async function activateWithInstalledDistribution(
void extLogger.log("Initializing query history manager.");
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
ctx.subscriptions.push(queryHistoryConfigurationListener);
const showResults = async (item: CompletedLocalQueryInfo) =>
showResultsForCompletedQuery(
localQueryResultsView,
item,
WebviewReveal.Forced,
);
const queryStorageDir = join(ctx.globalStorageUri.fsPath, "queries");
await ensureDir(queryStorageDir);
@@ -791,8 +782,10 @@ async function activateWithInstalledDistribution(
ctx,
queryHistoryConfigurationListener,
labelProvider,
async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) =>
showResultsForComparison(compareView, from, to),
async (
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
): Promise<void> => showResultsForComparison(compareView, from, to),
);
ctx.subscriptions.push(qhm);
@@ -813,7 +806,8 @@ async function activateWithInstalledDistribution(
cliServer,
queryServerLogger,
labelProvider,
showResults,
async (item: CompletedLocalQueryInfo) =>
localQueries.showResultsForCompletedQuery(item, WebviewReveal.Forced),
);
ctx.subscriptions.push(compareView);
@@ -849,6 +843,18 @@ async function activateWithInstalledDistribution(
true,
);
const localQueries = new LocalQueries(
app,
qs,
qhm,
dbm,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
);
ctx.subscriptions.push(localQueries);
void extLogger.log("Initializing QLTest interface.");
const testExplorerExtension = extensions.getExtension<TestHub>(
testExplorerExtensionId,
@@ -902,11 +908,7 @@ async function activateWithInstalledDistribution(
...databaseUI.getCommands(),
...dbModule.getCommands(),
...getAstCfgCommands({
queryRunner: qs,
queryHistoryManager: qhm,
databaseUI,
localQueryResultsView,
queryStorageDir,
localQueries,
astViewer,
astTemplateProvider,
cfgTemplateProvider,
@@ -926,16 +928,7 @@ async function activateWithInstalledDistribution(
}
const queryServerCommands: QueryServerCommands = {
...getLocalQueryCommands({
app,
queryRunner: qs,
queryHistoryManager: qhm,
databaseManager: dbm,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
}),
...localQueries.getCommands(),
};
for (const [commandName, command] of Object.entries(queryServerCommands)) {

View File

@@ -57,7 +57,7 @@ export class LegacyQueryRunner extends QueryRunner {
await clearCacheInDatabase(this.qs, dbItem, progress, token);
}
protected async compileAndRunQueryAgainstDatabaseCore(
public async compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],

View File

@@ -7,384 +7,508 @@ import {
Uri,
window,
} from "vscode";
import { extLogger } from "./common";
import { BaseLogger, extLogger, TeeLogger } from "./common";
import { MAX_QUERIES } from "./config";
import { gatherQlFiles } from "./pure/files";
import { basename } from "path";
import {
createTimestampFile,
findLanguage,
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
showBinaryChoiceDialog,
tryGetQueryMetadata,
} from "./helpers";
import { displayQuickQuery } from "./quick-query";
import { QueryRunner } from "./queryRunner";
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { DatabaseItem, DatabaseManager } from "./local-databases";
import { createInitialQueryInfo } from "./run-queries-shared";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
import {
createInitialQueryInfo,
EvaluatorLogPaths,
generateEvalLogSummaries,
logEndSummary,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
} from "./run-queries-shared";
import {
CompletedLocalQueryInfo,
InitialQueryInfo,
LocalQueryInfo,
} from "./query-results";
import { WebviewReveal } from "./interface-utils";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { CodeQLCliServer } from "./cli";
import { LocalQueryCommands } from "./common/commands";
import { App } from "./common/app";
type LocalQueryOptions = {
app: App;
queryRunner: QueryRunner;
queryHistoryManager: QueryHistoryManager;
databaseManager: DatabaseManager;
cliServer: CodeQLCliServer;
databaseUI: DatabaseUI;
localQueryResultsView: ResultsView;
queryStorageDir: string;
};
export function getLocalQueryCommands({
app,
queryRunner,
queryHistoryManager,
databaseManager,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
}: LocalQueryOptions): LocalQueryCommands {
const runQuery = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) => {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
progress,
token,
undefined,
);
},
{
title: "Running query",
cancellable: true,
},
);
const runQueryOnMultipleDatabases = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) =>
await compileAndRunQueryOnMultipleDatabases(
cliServer,
queryRunner,
queryHistoryManager,
databaseManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
progress,
token,
uri,
),
{
title: "Running query on selected databases",
cancellable: true,
},
);
const runQueries = async (_: Uri | undefined, multi: Uri[]) =>
withProgress(
async (progress, token) => {
const maxQueryCount = MAX_QUERIES.getValue() as number;
const [files, dirFound] = await gatherQlFiles(
multi.map((uri) => uri.fsPath),
);
if (files.length > maxQueryCount) {
throw new Error(
`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`,
);
}
// warn user and display selected files when a directory is selected because some ql
// files may be hidden from the user.
if (dirFound) {
const fileString = files.map((file) => basename(file)).join(", ");
const res = await showBinaryChoiceDialog(
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`,
);
if (!res) {
return;
}
}
const queryUris = files.map((path) => Uri.parse(`file:${path}`, true));
// Use a wrapped progress so that messages appear with the queries remaining in it.
let queriesRemaining = queryUris.length;
function wrappedProgress(update: ProgressUpdate) {
const message =
queriesRemaining > 1
? `${queriesRemaining} remaining. ${update.message}`
: update.message;
progress({
...update,
message,
});
}
wrappedProgress({
maxStep: queryUris.length,
step: queryUris.length - queriesRemaining,
message: "",
});
await Promise.all(
queryUris.map(async (uri) =>
compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
wrappedProgress,
token,
undefined,
).then(() => queriesRemaining--),
),
);
},
{
title: "Running queries",
cancellable: true,
},
);
const quickEval = async (uri: Uri) =>
withProgress(
async (progress, token) => {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
true,
uri,
progress,
token,
undefined,
);
},
{
title: "Running query",
cancellable: true,
},
);
const codeLensQuickEval = async (uri: Uri, range: Range) =>
withProgress(
async (progress, token) =>
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
true,
uri,
progress,
token,
undefined,
range,
),
{
title: "Running query",
cancellable: true,
},
);
const quickQuery = async () =>
withProgress(
async (progress, token) =>
displayQuickQuery(app, cliServer, databaseUI, progress, token),
{
title: "Run Quick Query",
},
);
return {
"codeQL.runQuery": runQuery,
"codeQL.runQueryContextEditor": runQuery,
"codeQL.runQueryOnMultipleDatabases": runQueryOnMultipleDatabases,
"codeQL.runQueryOnMultipleDatabasesContextEditor":
runQueryOnMultipleDatabases,
"codeQL.runQueries": runQueries,
"codeQL.quickEval": quickEval,
"codeQL.quickEvalContextEditor": quickEval,
"codeQL.codeLensQuickEval": codeLensQuickEval,
"codeQL.quickQuery": quickQuery,
};
}
export async function compileAndRunQuery(
qs: QueryRunner,
qhm: QueryHistoryManager,
databaseUI: DatabaseUI,
localQueryResultsView: ResultsView,
queryStorageDir: string,
quickEval: boolean,
selectedQuery: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<void> {
if (qs !== 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 databaseInfo = {
name: databaseItem.name,
databaseUri: databaseItem.databaseUri.toString(),
};
// handle cancellation from the history view.
const source = new CancellationTokenSource();
token.onCancellationRequested(() => source.cancel());
const initialInfo = await createInitialQueryInfo(
selectedQuery,
databaseInfo,
quickEval,
range,
);
const item = new LocalQueryInfo(initialInfo, source);
qhm.addQuery(item);
try {
const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase(
databaseItem,
initialInfo,
queryStorageDir,
progress,
source.token,
undefined,
item,
);
qhm.completeQuery(item, completedQueryInfo);
await showResultsForCompletedQuery(
localQueryResultsView,
item as CompletedLocalQueryInfo,
WebviewReveal.Forced,
);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
} catch (e) {
const err = asError(e);
err.message = `Error running query: ${err.message}`;
item.failureReason = err.message;
throw e;
} finally {
await qhm.refreshTreeView();
source.dispose();
}
}
}
import { DisposableObject } from "./pure/disposable-object";
import { QueryResultType } from "./pure/new-messages";
import { redactableError } from "./pure/errors";
interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
}
async function compileAndRunQueryOnMultipleDatabases(
cliServer: CodeQLCliServer,
qs: QueryRunner,
qhm: QueryHistoryManager,
dbm: DatabaseManager,
databaseUI: DatabaseUI,
localQueryResultsView: ResultsView,
queryStorageDir: string,
progress: ProgressCallback,
token: CancellationToken,
uri: Uri | undefined,
): Promise<void> {
let filteredDBs = dbm.databaseItems;
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
"No databases found. Please add a suitable database to your workspace.",
);
return;
}
// If possible, only show databases with the right language (otherwise show all databases).
const queryLanguage = await findLanguage(cliServer, uri);
if (queryLanguage) {
filteredDBs = dbm.databaseItems.filter(
(db) => db.language === queryLanguage,
);
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
);
return;
}
}
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>((dbItem) => ({
databaseItem: dbItem,
label: dbItem.name,
description: dbItem.language,
}));
/**
* Databases that were selected in the quick pick menu.
*/
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
quickPickItems,
{ canPickMany: true, ignoreFocusOut: 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(
qs,
qhm,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
progress,
token,
item.databaseItem,
);
} catch (e) {
skippedDatabases.push(item.label);
errors.push(getErrorMessage(e));
}
}
if (skippedDatabases.length > 0) {
void extLogger.log(`Errors:\n${errors.join("\n")}`);
void showAndLogWarningMessage(
`The following databases were skipped:\n${skippedDatabases.join(
"\n",
)}.\nFor details about the errors, see the logs.`,
);
}
} else {
void showAndLogErrorMessage("No databases selected.");
function formatResultMessage(result: CoreQueryResults): string {
switch (result.resultType) {
case QueryResultType.CANCELLATION:
return `cancelled after ${Math.round(
result.evaluationTime / 1000,
)} seconds`;
case QueryResultType.OOM:
return "out of memory";
case QueryResultType.SUCCESS:
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
case QueryResultType.COMPILATION_ERROR:
return `compilation failed: ${result.message}`;
case QueryResultType.OTHER_ERROR:
default:
return result.message ? `failed: ${result.message}` : "failed";
}
}
export async function showResultsForCompletedQuery(
localQueryResultsView: ResultsView,
query: CompletedLocalQueryInfo,
forceReveal: WebviewReveal,
): Promise<void> {
await localQueryResultsView.showResults(query, forceReveal, false);
export class LocalQueries extends DisposableObject {
public constructor(
private readonly app: App,
private readonly queryRunner: QueryRunner,
private readonly queryHistoryManager: QueryHistoryManager,
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
private readonly databaseUI: DatabaseUI,
private readonly localQueryResultsView: ResultsView,
private readonly queryStorageDir: string,
) {
super();
}
public getCommands(): LocalQueryCommands {
const runQuery = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) => {
await this.compileAndRunQuery(false, uri, progress, token, undefined);
},
{
title: "Running query",
cancellable: true,
},
);
const runQueryOnMultipleDatabases = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) =>
await this.compileAndRunQueryOnMultipleDatabases(
progress,
token,
uri,
),
{
title: "Running query on selected databases",
cancellable: true,
},
);
const runQueries = async (_: Uri | undefined, multi: Uri[]) =>
withProgress(
async (progress, token) => {
const maxQueryCount = MAX_QUERIES.getValue() as number;
const [files, dirFound] = await gatherQlFiles(
multi.map((uri) => uri.fsPath),
);
if (files.length > maxQueryCount) {
throw new Error(
`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`,
);
}
// warn user and display selected files when a directory is selected because some ql
// files may be hidden from the user.
if (dirFound) {
const fileString = files.map((file) => basename(file)).join(", ");
const res = await showBinaryChoiceDialog(
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`,
);
if (!res) {
return;
}
}
const queryUris = files.map((path) =>
Uri.parse(`file:${path}`, true),
);
// Use a wrapped progress so that messages appear with the queries remaining in it.
let queriesRemaining = queryUris.length;
function wrappedProgress(update: ProgressUpdate) {
const message =
queriesRemaining > 1
? `${queriesRemaining} remaining. ${update.message}`
: update.message;
progress({
...update,
message,
});
}
wrappedProgress({
maxStep: queryUris.length,
step: queryUris.length - queriesRemaining,
message: "",
});
await Promise.all(
queryUris.map(async (uri) =>
this.compileAndRunQuery(
false,
uri,
wrappedProgress,
token,
undefined,
).then(() => queriesRemaining--),
),
);
},
{
title: "Running queries",
cancellable: true,
},
);
const quickEval = async (uri: Uri) =>
withProgress(
async (progress, token) => {
await this.compileAndRunQuery(true, uri, progress, token, undefined);
},
{
title: "Running query",
cancellable: true,
},
);
const codeLensQuickEval = async (uri: Uri, range: Range) =>
withProgress(
async (progress, token) =>
await this.compileAndRunQuery(
true,
uri,
progress,
token,
undefined,
range,
),
{
title: "Running query",
cancellable: true,
},
);
const quickQuery = async () =>
withProgress(
async (progress, token) =>
displayQuickQuery(
this.app,
this.cliServer,
this.databaseUI,
progress,
token,
),
{
title: "Run Quick Query",
},
);
return {
"codeQL.runQuery": runQuery,
"codeQL.runQueryContextEditor": runQuery,
"codeQL.runQueryOnMultipleDatabases": runQueryOnMultipleDatabases,
"codeQL.runQueryOnMultipleDatabasesContextEditor":
runQueryOnMultipleDatabases,
"codeQL.runQueries": runQueries,
"codeQL.quickEval": quickEval,
"codeQL.quickEvalContextEditor": quickEval,
"codeQL.codeLensQuickEval": codeLensQuickEval,
"codeQL.quickQuery": quickQuery,
};
}
async compileAndRunQuery(
quickEval: boolean,
selectedQuery: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<void> {
if (this.queryRunner !== undefined) {
// If no databaseItem is specified, use the database currently selected in the Databases UI
databaseItem =
databaseItem ||
(await this.databaseUI.getDatabaseItem(progress, token));
if (databaseItem === undefined) {
throw new Error("Can't run query without a selected database");
}
const databaseInfo = {
name: databaseItem.name,
databaseUri: databaseItem.databaseUri.toString(),
};
// handle cancellation from the history view.
const source = new CancellationTokenSource();
token.onCancellationRequested(() => source.cancel());
const initialInfo = await createInitialQueryInfo(
selectedQuery,
databaseInfo,
quickEval,
range,
);
const item = new LocalQueryInfo(initialInfo, source);
this.queryHistoryManager.addQuery(item);
try {
const completedQueryInfo = await this.compileAndRunQueryAgainstDatabase(
databaseItem,
initialInfo,
this.queryStorageDir,
progress,
source.token,
undefined,
item,
);
this.queryHistoryManager.completeQuery(item, completedQueryInfo);
await this.showResultsForCompletedQuery(
item as CompletedLocalQueryInfo,
WebviewReveal.Forced,
);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
} catch (e) {
const err = asError(e);
err.message = `Error running query: ${err.message}`;
item.failureReason = err.message;
throw e;
} finally {
await this.queryHistoryManager.refreshTreeView();
source.dispose();
}
}
}
async compileAndRunQueryOnMultipleDatabases(
progress: ProgressCallback,
token: CancellationToken,
uri: Uri | undefined,
): Promise<void> {
let filteredDBs = this.databaseManager.databaseItems;
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
"No databases found. Please add a suitable database to your workspace.",
);
return;
}
// If possible, only show databases with the right language (otherwise show all databases).
const queryLanguage = await findLanguage(this.cliServer, uri);
if (queryLanguage) {
filteredDBs = this.databaseManager.databaseItems.filter(
(db) => db.language === queryLanguage,
);
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
);
return;
}
}
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>((dbItem) => ({
databaseItem: dbItem,
label: dbItem.name,
description: dbItem.language,
}));
/**
* Databases that were selected in the quick pick menu.
*/
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
quickPickItems,
{ canPickMany: true, ignoreFocusOut: 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 this.compileAndRunQuery(
false,
uri,
progress,
token,
item.databaseItem,
);
} catch (e) {
skippedDatabases.push(item.label);
errors.push(getErrorMessage(e));
}
}
if (skippedDatabases.length > 0) {
void extLogger.log(`Errors:\n${errors.join("\n")}`);
void showAndLogWarningMessage(
`The following databases were skipped:\n${skippedDatabases.join(
"\n",
)}.\nFor details about the errors, see the logs.`,
);
}
} else {
void showAndLogErrorMessage("No databases selected.");
}
}
async showResultsForCompletedQuery(
query: CompletedLocalQueryInfo,
forceReveal: WebviewReveal,
): Promise<void> {
await this.localQueryResultsView.showResults(query, forceReveal, false);
}
private async compileAndRunQueryAgainstDatabase(
db: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query.
): Promise<QueryWithResults> {
const queryTarget: CoreQueryTarget = {
queryPath: initialInfo.queryPath,
quickEvalPosition: initialInfo.quickEvalPosition,
};
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
const queryRun = this.queryRunner.createQueryRun(
db.databaseUri.fsPath,
queryTarget,
queryInfo !== undefined,
diskWorkspaceFolders,
queryStorageDir,
initialInfo.id,
templates,
);
await createTimestampFile(queryRun.outputDir.querySaveDir);
const logPath = queryRun.outputDir.logPath;
if (this.queryRunner.customLogDirectory) {
void showAndLogWarningMessage(
`Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${logPath}`,
);
}
const logger = new TeeLogger(this.queryRunner.logger, logPath);
const coreResults = await queryRun.evaluate(progress, token, logger);
if (queryInfo !== undefined) {
const evalLogPaths = await this.summarizeEvalLog(
coreResults.resultType,
queryRun.outputDir,
logger,
);
if (evalLogPaths !== undefined) {
queryInfo.setEvaluatorLogPaths(evalLogPaths);
}
}
return await this.getCompletedQueryInfo(
db,
queryTarget,
queryRun.outputDir,
coreResults,
);
}
/**
* Generate summaries of the structured evaluator log.
*/
public async summarizeEvalLog(
resultType: QueryResultType,
outputDir: QueryOutputDir,
logger: BaseLogger,
): Promise<EvaluatorLogPaths | undefined> {
const evalLogPaths = await generateEvalLogSummaries(
this.cliServer,
outputDir,
);
if (evalLogPaths !== undefined) {
if (evalLogPaths.endSummary !== undefined) {
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
}
} else {
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
switch (resultType) {
case QueryResultType.COMPILATION_ERROR:
case QueryResultType.DBSCHEME_MISMATCH_NAME:
case QueryResultType.DBSCHEME_NO_UPGRADE:
// In these cases, the evaluator was never invoked anyway, so don't bother warning.
break;
default:
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
);
break;
}
}
return evalLogPaths;
}
/**
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
* result, in the form expected by the query history UI.
*/
public async getCompletedQueryInfo(
dbItem: DatabaseItem,
queryTarget: CoreQueryTarget,
outputDir: QueryOutputDir,
results: CoreQueryResults,
): Promise<QueryWithResults> {
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(
this.cliServer,
queryTarget.queryPath,
);
const query = new QueryEvaluationInfo(
outputDir.querySaveDir,
dbItem.databaseUri.fsPath,
await dbItem.hasMetadataFile(),
queryTarget.quickEvalPosition,
metadata,
);
if (results.resultType !== QueryResultType.SUCCESS) {
const message = results.message
? redactableError`${results.message}`
: redactableError`Failed to run query`;
void extLogger.log(message.fullMessage);
void showAndLogExceptionWithTelemetry(
redactableError`Failed to run query: ${message}`,
);
}
const message = formatResultMessage(results);
const successful = results.resultType === QueryResultType.SUCCESS;
return {
query,
result: {
evaluationTime: results.evaluationTime,
queryId: 0,
resultType: successful
? QueryResultType.SUCCESS
: QueryResultType.OTHER_ERROR,
runId: 0,
message,
},
message,
successful,
};
}
}

View File

@@ -68,7 +68,7 @@ export class NewQueryRunner extends QueryRunner {
await this.qs.sendRequest(clearCache, params, token, progress);
}
protected async compileAndRunQueryAgainstDatabaseCore(
public async compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],

View File

@@ -2,26 +2,10 @@ import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "./cli";
import { ProgressCallback } from "./progress";
import { DatabaseItem } from "./local-databases";
import { InitialQueryInfo, LocalQueryInfo } from "./query-results";
import {
EvaluatorLogPaths,
generateEvalLogSummaries,
logEndSummary,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
} from "./run-queries-shared";
import { QueryOutputDir } from "./run-queries-shared";
import { Position, QueryResultType } from "./pure/new-messages";
import {
createTimestampFile,
getOnDiskWorkspaceFolders,
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
tryGetQueryMetadata,
} from "./helpers";
import { BaseLogger, Logger } from "./common";
import { basename, join } from "path";
import { BaseLogger, extLogger, Logger, TeeLogger } from "./common";
import { redactableError } from "./pure/errors";
import { nanoid } from "nanoid";
export interface CoreQueryTarget {
@@ -57,24 +41,6 @@ export interface CoreQueryRun {
export type CoreCompletedQuery = CoreQueryResults &
Omit<CoreQueryRun, "evaluate">;
function formatResultMessage(result: CoreQueryResults): string {
switch (result.resultType) {
case QueryResultType.CANCELLATION:
return `cancelled after ${Math.round(
result.evaluationTime / 1000,
)} seconds`;
case QueryResultType.OOM:
return "out of memory";
case QueryResultType.SUCCESS:
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
case QueryResultType.COMPILATION_ERROR:
return `compilation failed: ${result.message}`;
case QueryResultType.OTHER_ERROR:
default:
return result.message ? `failed: ${result.message}` : "failed";
}
}
export abstract class QueryRunner {
abstract restartQueryServer(
progress: ProgressCallback,
@@ -97,146 +63,40 @@ export abstract class QueryRunner {
token: CancellationToken,
): Promise<void>;
public async compileAndRunQueryAgainstDatabase(
db: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
/**
* Overridden in subclasses to evaluate the query via the query server and return the results.
*/
public abstract compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],
generateEvalLog: boolean,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query.
): Promise<QueryWithResults> {
const queryTarget: CoreQueryTarget = {
queryPath: initialInfo.queryPath,
quickEvalPosition: initialInfo.quickEvalPosition,
};
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
const queryRun = this.createQueryRun(
db.databaseUri.fsPath,
queryTarget,
queryInfo !== undefined,
diskWorkspaceFolders,
queryStorageDir,
initialInfo.id,
templates,
);
await createTimestampFile(queryRun.outputDir.querySaveDir);
const logPath = queryRun.outputDir.logPath;
if (this.customLogDirectory) {
void showAndLogWarningMessage(
`Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${logPath}`,
);
}
const logger = new TeeLogger(this.logger, logPath);
const coreResults = await queryRun.evaluate(progress, token, logger);
if (queryInfo !== undefined) {
const evalLogPaths = await this.summarizeEvalLog(
coreResults.resultType,
queryRun.outputDir,
logger,
);
if (evalLogPaths !== undefined) {
queryInfo.setEvaluatorLogPaths(evalLogPaths);
}
}
return await this.getCompletedQueryInfo(
db,
queryTarget,
queryRun.outputDir,
coreResults,
);
}
/**
* Generate summaries of the structured evaluator log.
*/
public async summarizeEvalLog(
resultType: QueryResultType,
outputDir: QueryOutputDir,
templates: Record<string, string> | undefined,
logger: BaseLogger,
): Promise<EvaluatorLogPaths | undefined> {
const evalLogPaths = await generateEvalLogSummaries(
this.cliServer,
outputDir,
);
if (evalLogPaths !== undefined) {
if (evalLogPaths.endSummary !== undefined) {
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
}
} else {
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
switch (resultType) {
case QueryResultType.COMPILATION_ERROR:
case QueryResultType.DBSCHEME_MISMATCH_NAME:
case QueryResultType.DBSCHEME_NO_UPGRADE:
// In these cases, the evaluator was never invoked anyway, so don't bother warning.
break;
): Promise<CoreQueryResults>;
default:
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
);
break;
}
}
return evalLogPaths;
}
/**
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
* result, in the form expected by the query history UI.
*/
public async getCompletedQueryInfo(
abstract deregisterDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
queryTarget: CoreQueryTarget,
outputDir: QueryOutputDir,
results: CoreQueryResults,
): Promise<QueryWithResults> {
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(
this.cliServer,
queryTarget.queryPath,
);
const query = new QueryEvaluationInfo(
outputDir.querySaveDir,
dbItem.databaseUri.fsPath,
await dbItem.hasMetadataFile(),
queryTarget.quickEvalPosition,
metadata,
);
): Promise<void>;
if (results.resultType !== QueryResultType.SUCCESS) {
const message = results.message
? redactableError`${results.message}`
: redactableError`Failed to run query`;
void extLogger.log(message.fullMessage);
void showAndLogExceptionWithTelemetry(
redactableError`Failed to run query: ${message}`,
);
}
const message = formatResultMessage(results);
const successful = results.resultType === QueryResultType.SUCCESS;
return {
query,
result: {
evaluationTime: results.evaluationTime,
queryId: 0,
resultType: successful
? QueryResultType.SUCCESS
: QueryResultType.OTHER_ERROR,
runId: 0,
message,
},
message,
successful,
};
}
abstract registerDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void>;
abstract upgradeDatabaseExplicit(
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void>;
abstract clearPackCache(): Promise<void>;
/**
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
@@ -286,39 +146,4 @@ export abstract class QueryRunner {
},
};
}
/**
* Overridden in subclasses to evaluate the query via the query server and return the results.
*/
protected abstract compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],
generateEvalLog: boolean,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates: Record<string, string> | undefined,
logger: BaseLogger,
): Promise<CoreQueryResults>;
abstract deregisterDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void>;
abstract registerDatabase(
progress: ProgressCallback,
token: CancellationToken,
dbItem: DatabaseItem,
): Promise<void>;
abstract upgradeDatabaseExplicit(
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void>;
abstract clearPackCache(): Promise<void>;
}