Merge pull request #2347 from github/nora/break-down-local-queries-1

Break down local-queries.ts
This commit is contained in:
Nora
2023-04-19 13:49:30 +02:00
committed by GitHub
8 changed files with 205 additions and 193 deletions

View File

@@ -6,7 +6,7 @@ import {
TemplatePrintCfgProvider,
} from "./contextual/templateProvider";
import { AstCfgCommands } from "./common/commands";
import { LocalQueries } from "./local-queries";
import { LocalQueries } from "./local-queries/local-queries";
type AstCfgOptions = {
localQueries: LocalQueries;

View File

@@ -5,7 +5,7 @@ import {
WorkspaceFolder,
} from "vscode";
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from "../helpers";
import { LocalQueries } from "../local-queries";
import { LocalQueries } from "../local-queries/local-queries";
import { getQuickEvalContext, validateQueryPath } from "../run-queries-shared";
import * as CodeQLProtocol from "./debug-protocol";
import { getErrorMessage } from "../pure/helpers-pure";

View File

@@ -9,7 +9,7 @@ import {
ProviderResult,
} from "vscode";
import { isCanary } from "../config";
import { LocalQueries } from "../local-queries";
import { LocalQueries } from "../local-queries/local-queries";
import { DisposableObject } from "../pure/disposable-object";
import { QueryRunner } from "../queryRunner";
import { QLDebugConfigurationProvider } from "./debug-configuration";

View File

@@ -9,7 +9,7 @@ import {
} from "vscode";
import { DebuggerCommands } from "../common/commands";
import { DatabaseManager } from "../local-databases";
import { LocalQueries, LocalQueryRun } from "../local-queries";
import { LocalQueries } from "../local-queries/local-queries";
import { DisposableObject } from "../pure/disposable-object";
import { CoreQueryResults } from "../queryRunner";
import {
@@ -20,6 +20,7 @@ import {
import { QLResolvedDebugConfiguration } from "./debug-configuration";
import * as CodeQLProtocol from "./debug-protocol";
import { App } from "../common/app";
import { LocalQueryRun } from "../local-queries/local-query-run";
/**
* Listens to messages passing between VS Code and the debug adapter, so that we can supplement the

View File

@@ -117,7 +117,7 @@ import {
PreActivationCommands,
QueryServerCommands,
} from "./common/commands";
import { LocalQueries } from "./local-queries";
import { LocalQueries } from "./local-queries/local-queries";
import { getAstCfgCommands } from "./ast-cfg-commands";
import { getQueryEditorCommands } from "./query-editor";
import { App } from "./common/app";

View File

@@ -1,4 +1,4 @@
import { ProgressCallback, ProgressUpdate, withProgress } from "./progress";
import { ProgressCallback, ProgressUpdate, withProgress } from "../progress";
import {
CancellationToken,
CancellationTokenSource,
@@ -8,76 +8,46 @@ import {
window,
workspace,
} from "vscode";
import { BaseLogger, extLogger, Logger, TeeLogger } from "./common";
import { isCanary, MAX_QUERIES } from "./config";
import { gatherQlFiles } from "./pure/files";
import { extLogger, TeeLogger } from "../common";
import { isCanary, 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 {
CoreCompletedQuery,
CoreQueryResults,
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";
} from "../helpers";
import { displayQuickQuery } from "../quick-query";
import { CoreCompletedQuery, 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,
EvaluatorLogPaths,
generateEvalLogSummaries,
getQuickEvalContext,
logEndSummary,
promptUserToSaveChanges,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
SelectedQuery,
validateQueryUri,
} from "./run-queries-shared";
import { CompletedLocalQueryInfo, 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";
import { DisposableObject } from "./pure/disposable-object";
import { QueryResultType } from "./pure/new-messages";
import { redactableError } from "./pure/errors";
import { SkeletonQueryWizard } from "./skeleton-query-wizard";
} from "../run-queries-shared";
import { CompletedLocalQueryInfo, 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";
import { DisposableObject } from "../pure/disposable-object";
import { SkeletonQueryWizard } from "../skeleton-query-wizard";
import { LocalQueryRun } from "./local-query-run";
interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
}
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";
}
}
/**
* If either the query file or the quickeval file is dirty, give the user the chance to save them.
*/
@@ -97,142 +67,6 @@ async function promptToSaveQueryIfNeeded(query: SelectedQuery): Promise<void> {
}
}
/**
* Tracks the evaluation of a local query, including its interactions with the UI.
*
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
* the `complete()` function once the query has completed (successfully or otherwise).
*
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
* evaluation, and we can only respond to events from the debug adapter.
*/
export class LocalQueryRun {
public constructor(
private readonly outputDir: QueryOutputDir,
private readonly localQueries: LocalQueries,
private readonly queryInfo: LocalQueryInfo,
private readonly dbItem: DatabaseItem,
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
private readonly queryHistoryManager: QueryHistoryManager,
private readonly cliServer: CodeQLCliServer,
) {}
/**
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
* summaries, updates the query history item for the evaluation with the results and evaluation
* time, and displays the results view.
*
* This function must be called when the evaluation completes, whether the evaluation was
* successful or not.
* */
public async complete(results: CoreQueryResults): Promise<void> {
const evalLogPaths = await this.summarizeEvalLog(
results.resultType,
this.outputDir,
this.logger,
);
if (evalLogPaths !== undefined) {
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
}
const queryWithResults = await this.getCompletedQueryInfo(results);
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
await this.localQueries.showResultsForCompletedQuery(
this.queryInfo 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
await this.queryHistoryManager.refreshTreeView();
}
/**
* Updates the UI in the case where query evaluation throws an exception.
*/
public async fail(err: Error): Promise<void> {
err.message = `Error running query: ${err.message}`;
this.queryInfo.failureReason = err.message;
await this.queryHistoryManager.refreshTreeView();
}
/**
* Generate summaries of the structured evaluator log.
*/
private 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.
if (resultType === QueryResultType.SUCCESS) {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
);
} else {
// Don't bother notifying the user if there's no log. For some errors, like compilation
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
// a log depends on how far execution got before termination.
}
}
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.
*/
private async getCompletedQueryInfo(
results: CoreQueryResults,
): Promise<QueryWithResults> {
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(
this.cliServer,
this.queryInfo.initialInfo.queryPath,
);
const query = new QueryEvaluationInfo(
this.outputDir.querySaveDir,
this.dbItem.databaseUri.fsPath,
await this.dbItem.hasMetadataFile(),
this.queryInfo.initialInfo.quickEvalPosition,
metadata,
);
if (results.resultType !== QueryResultType.SUCCESS) {
const message = results.message
? redactableError`Failed to run query: ${results.message}`
: redactableError`Failed to run query`;
void showAndLogExceptionWithTelemetry(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,
};
}
}
export class LocalQueries extends DisposableObject {
public constructor(
private readonly app: App,

View File

@@ -0,0 +1,177 @@
import { BaseLogger, Logger } from "../common";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
tryGetQueryMetadata,
} from "../helpers";
import { CoreQueryResults } from "../queryRunner";
import { QueryHistoryManager } from "../query-history/query-history-manager";
import { DatabaseItem } from "../local-databases";
import {
EvaluatorLogPaths,
generateEvalLogSummaries,
logEndSummary,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
} from "../run-queries-shared";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
import { WebviewReveal } from "../interface-utils";
import { CodeQLCliServer } from "../cli";
import { QueryResultType } from "../pure/new-messages";
import { redactableError } from "../pure/errors";
import { LocalQueries } from "./local-queries";
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";
}
}
/**
* Tracks the evaluation of a local query, including its interactions with the UI.
*
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
* the `complete()` function once the query has completed (successfully or otherwise).
*
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
* evaluation, and we can only respond to events from the debug adapter.
*/
export class LocalQueryRun {
public constructor(
private readonly outputDir: QueryOutputDir,
private readonly localQueries: LocalQueries,
private readonly queryInfo: LocalQueryInfo,
private readonly dbItem: DatabaseItem,
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
private readonly queryHistoryManager: QueryHistoryManager,
private readonly cliServer: CodeQLCliServer,
) {}
/**
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
* summaries, updates the query history item for the evaluation with the results and evaluation
* time, and displays the results view.
*
* This function must be called when the evaluation completes, whether the evaluation was
* successful or not.
* */
public async complete(results: CoreQueryResults): Promise<void> {
const evalLogPaths = await this.summarizeEvalLog(
results.resultType,
this.outputDir,
this.logger,
);
if (evalLogPaths !== undefined) {
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
}
const queryWithResults = await this.getCompletedQueryInfo(results);
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
await this.localQueries.showResultsForCompletedQuery(
this.queryInfo 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
await this.queryHistoryManager.refreshTreeView();
}
/**
* Updates the UI in the case where query evaluation throws an exception.
*/
public async fail(err: Error): Promise<void> {
err.message = `Error running query: ${err.message}`;
this.queryInfo.failureReason = err.message;
await this.queryHistoryManager.refreshTreeView();
}
/**
* Generate summaries of the structured evaluator log.
*/
private 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.
if (resultType === QueryResultType.SUCCESS) {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
);
} else {
// Don't bother notifying the user if there's no log. For some errors, like compilation
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
// a log depends on how far execution got before termination.
}
}
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.
*/
private async getCompletedQueryInfo(
results: CoreQueryResults,
): Promise<QueryWithResults> {
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(
this.cliServer,
this.queryInfo.initialInfo.queryPath,
);
const query = new QueryEvaluationInfo(
this.outputDir.querySaveDir,
this.dbItem.databaseUri.fsPath,
await this.dbItem.hasMetadataFile(),
this.queryInfo.initialInfo.quickEvalPosition,
metadata,
);
if (results.resultType !== QueryResultType.SUCCESS) {
const message = results.message
? redactableError`Failed to run query: ${results.message}`
: redactableError`Failed to run query`;
void showAndLogExceptionWithTelemetry(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

@@ -19,7 +19,7 @@ import { CliVersionConstraint, CodeQLCliServer } from "../../../src/cli";
import { describeWithCodeQL } from "../cli";
import { CoreCompletedQuery, QueryRunner } from "../../../src/queryRunner";
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
import { LocalQueries } from "../../../src/local-queries";
import { LocalQueries } from "../../../src/local-queries/local-queries";
import { QueryResultType } from "../../../src/pure/new-messages";
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
import {