Use LocalQueryRun in LocalQueries

This commit is contained in:
Dave Bartolomeo
2023-03-27 11:48:31 -04:00
parent 543bada323
commit 348e9231e3
2 changed files with 103 additions and 220 deletions

View File

@@ -22,25 +22,23 @@ import {
tryGetQueryMetadata,
} from "./helpers";
import { displayQuickQuery } from "./quick-query";
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "./queryRunner";
import { 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";
import {
createInitialQueryInfo,
determineSelectedQuery,
EvaluatorLogPaths,
generateEvalLogSummaries,
logEndSummary,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
SelectedQuery,
} from "./run-queries-shared";
import {
CompletedLocalQueryInfo,
InitialQueryInfo,
LocalQueryInfo,
} from "./query-results";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
import { WebviewReveal } from "./interface-utils";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { CodeQLCliServer } from "./cli";
@@ -87,7 +85,7 @@ export class LocalQueryRun {
public constructor(
private readonly outputDir: QueryOutputDir,
private readonly localQueries: LocalQueries,
private readonly queryInfo: LocalQueryInfo,
public readonly queryInfo: LocalQueryInfo,
private readonly dbItem: DatabaseItem,
public readonly logger: Logger,
) {}
@@ -101,7 +99,7 @@ export class LocalQueryRun {
* successful or not.
* */
public async complete(results: CoreQueryResults): Promise<void> {
const evalLogPaths = await this.localQueries.summarizeEvalLog(
const evalLogPaths = await this.summarizeEvalLog(
results.resultType,
this.outputDir,
this.logger,
@@ -123,6 +121,38 @@ export class LocalQueryRun {
await this.localQueries.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.localQueries.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.
@@ -342,47 +372,47 @@ export class LocalQueries extends DisposableObject {
* object to update the UI based on the results of the query.
*/
public async createLocalQueryRun(
queryPath: string,
quickEval: boolean,
range: Range | undefined,
selectedQuery: SelectedQuery,
dbItem: DatabaseItem,
outputDir: string,
outputDir: QueryOutputDir,
tokenSource: CancellationTokenSource,
): Promise<LocalQueryRun> {
const queryOutputDir = new QueryOutputDir(outputDir);
await createTimestampFile(outputDir.querySaveDir);
await createTimestampFile(outputDir);
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 ${outputDir.logPath}`,
);
}
const initialInfo = await createInitialQueryInfo(
Uri.file(queryPath),
{
databaseUri: dbItem.databaseUri.toString(),
name: dbItem.name,
},
quickEval,
range,
);
const initialInfo = await createInitialQueryInfo(selectedQuery, {
databaseUri: dbItem.databaseUri.toString(),
name: dbItem.name,
});
// When cancellation is requested from the query history view, we just stop the debug session.
const queryInfo = new LocalQueryInfo(initialInfo, tokenSource);
this.queryHistoryManager.addQuery(queryInfo);
const logger = new TeeLogger(
this.queryRunner.logger,
queryOutputDir.logPath,
);
return new LocalQueryRun(queryOutputDir, this, queryInfo, dbItem, logger);
const logger = new TeeLogger(this.queryRunner.logger, outputDir.logPath);
return new LocalQueryRun(outputDir, this, queryInfo, dbItem, logger);
}
public async compileAndRunQuery(
quickEval: boolean,
selectedQuery: Uri | undefined,
queryUri: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<void> {
if (this.queryRunner !== undefined) {
const selectedQuery = await determineSelectedQuery(
queryUri,
quickEval,
range,
);
// If no databaseItem is specified, use the database currently selected in the Databases UI
databaseItem =
databaseItem ||
@@ -390,45 +420,46 @@ export class LocalQueries extends DisposableObject {
if (databaseItem === undefined) {
throw new Error("Can't run query without a selected database");
}
const databaseInfo = {
name: databaseItem.name,
databaseUri: databaseItem.databaseUri.toString(),
};
const coreQueryRun = this.queryRunner.createQueryRun(
databaseItem.databaseUri.fsPath,
{
queryPath: selectedQuery.queryPath,
quickEvalPosition: selectedQuery.quickEvalPosition,
},
true,
getOnDiskWorkspaceFolders(),
this.queryStorageDir,
undefined,
undefined,
);
// 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(
token.onCancellationRequested(() => source.cancel());
const localQueryRun = await this.createLocalQueryRun(
selectedQuery,
databaseItem,
initialInfo,
this.queryStorageDir,
progress,
source.token,
undefined,
item,
coreQueryRun.outputDir,
source,
);
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;
try {
const results = await coreQueryRun.evaluate(
progress,
source.token,
localQueryRun.logger,
);
await localQueryRun.complete(results);
} catch (e) {
const err = asError(e);
err.message = `Error running query: ${err.message}`;
localQueryRun.queryInfo.failureReason = err.message;
throw e;
}
} finally {
await this.queryHistoryManager.refreshTreeView();
source.dispose();
@@ -510,145 +541,4 @@ export class LocalQueries extends DisposableObject {
): 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.
*/
private 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

@@ -386,7 +386,7 @@ export interface QueryWithResults {
* Information about which query will be to be run. `quickEvalPosition` and `quickEvalText`
* is only filled in if the query is a quick query.
*/
interface SelectedQuery {
export interface SelectedQuery {
queryPath: string;
quickEvalPosition?: messages.Position;
quickEvalText?: string;
@@ -581,36 +581,29 @@ async function convertToQlPath(filePath: string): Promise<string> {
* Determines the initial information for a query. This is everything of interest
* we know about this query that is available before it is run.
*
* @param selectedQueryUri The Uri of the document containing the query to be run.
* @param selectedQuer The query to run, including any quickeval info.
* @param databaseInfo The database to run the query against.
* @param isQuickEval true if this is a quick evaluation.
* @param range the selection range of the query to be run. Only used if isQuickEval is true.
* @returns The initial information for the query to be run.
*/
export async function createInitialQueryInfo(
selectedQueryUri: Uri | undefined,
selectedQuery: SelectedQuery,
databaseInfo: DatabaseInfo,
isQuickEval: boolean,
range?: Range,
): Promise<InitialQueryInfo> {
// Determine which query to run, based on the selection and the active editor.
const { queryPath, quickEvalPosition, quickEvalText } =
await determineSelectedQuery(selectedQueryUri, isQuickEval, range);
const isQuickEval = selectedQuery.quickEvalPosition !== undefined;
return {
queryPath,
queryPath: selectedQuery.queryPath,
isQuickEval,
isQuickQuery: isQuickQueryPath(queryPath),
isQuickQuery: isQuickQueryPath(selectedQuery.queryPath),
databaseInfo,
id: `${basename(queryPath)}-${nanoid()}`,
id: `${basename(selectedQuery.queryPath)}-${nanoid()}`,
start: new Date(),
...(isQuickEval
? {
queryText: quickEvalText!, // if this query is quick eval, it must have quick eval text
quickEvalPosition,
queryText: selectedQuery.quickEvalText!, // if this query is quick eval, it must have quick eval text
quickEvalPosition: selectedQuery.quickEvalPosition,
}
: {
queryText: await readFile(queryPath, "utf8"),
queryText: await readFile(selectedQuery.queryPath, "utf8"),
}),
};
}