Merge pull request #2347 from github/nora/break-down-local-queries-1
Break down local-queries.ts
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
177
extensions/ql-vscode/src/local-queries/local-query-run.ts
Normal file
177
extensions/ql-vscode/src/local-queries/local-query-run.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user