Merge pull request #2244 from github/dbartol/debug-context
Refactor local query evaluation to prepare for debug adapter
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -3,7 +3,8 @@ export interface LogOptions {
|
||||
trailingNewline?: boolean;
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
/** Minimal logger interface. */
|
||||
export interface BaseLogger {
|
||||
/**
|
||||
* Writes the given log message, optionally followed by a newline.
|
||||
* This function is asynchronous and will only resolve once the message is written
|
||||
@@ -15,7 +16,10 @@ export interface Logger {
|
||||
* @param options Optional settings.
|
||||
*/
|
||||
log(message: string, options?: LogOptions): Promise<void>;
|
||||
}
|
||||
|
||||
/** Full logger interface, including a function to show the log in the UI. */
|
||||
export interface Logger extends BaseLogger {
|
||||
/**
|
||||
* Reveal the logger channel in the UI.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@ import { DatabaseItem } from "../local-databases";
|
||||
import { ChildAstItem, AstItem } from "../astViewer";
|
||||
import fileRangeFromURI from "./fileRangeFromURI";
|
||||
import { Uri } from "vscode";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
|
||||
/**
|
||||
* A class that wraps a tree of QL results from a query that
|
||||
@@ -14,12 +14,12 @@ export default class AstBuilder {
|
||||
private roots: AstItem[] | undefined;
|
||||
private bqrsPath: string;
|
||||
constructor(
|
||||
queryResults: QueryWithResults,
|
||||
outputDir: QueryOutputDir,
|
||||
private cli: CodeQLCliServer,
|
||||
public db: DatabaseItem,
|
||||
public fileName: Uri,
|
||||
) {
|
||||
this.bqrsPath = queryResults.query.resultsPaths.resultsPath;
|
||||
this.bqrsPath = outputDir.bqrsPath;
|
||||
}
|
||||
|
||||
async getRoots(): Promise<AstItem[]> {
|
||||
|
||||
@@ -19,8 +19,9 @@ import {
|
||||
runContextualQuery,
|
||||
} from "./queryResolver";
|
||||
import { CancellationToken, LocationLink, Uri } from "vscode";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
|
||||
export const SELECT_QUERY_NAME = "#select";
|
||||
export const TEMPLATE_NAME = "selectedSourceFile";
|
||||
@@ -78,21 +79,23 @@ export async function getLocationsForUriString(
|
||||
token,
|
||||
templates,
|
||||
);
|
||||
if (results.successful) {
|
||||
links.push(...(await getLinksFromResults(results, cli, db, filter)));
|
||||
if (results.resultType === QueryResultType.SUCCESS) {
|
||||
links.push(
|
||||
...(await getLinksFromResults(results.outputDir, cli, db, filter)),
|
||||
);
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
async function getLinksFromResults(
|
||||
results: QueryWithResults,
|
||||
outputDir: QueryOutputDir,
|
||||
cli: CodeQLCliServer,
|
||||
db: DatabaseItem,
|
||||
filter: (srcFile: string, destFile: string) => boolean,
|
||||
): Promise<FullLocationLink[]> {
|
||||
const localLinks: FullLocationLink[] = [];
|
||||
const bqrsPath = results.query.resultsPaths.resultsPath;
|
||||
const bqrsPath = outputDir.bqrsPath;
|
||||
const info = await cli.bqrsInfo(bqrsPath);
|
||||
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
|
||||
if (isValidSelect(selectInfo)) {
|
||||
|
||||
@@ -13,11 +13,10 @@ import {
|
||||
import { KeyType, kindOfKeyType, nameOfKeyType, tagOfKeyType } from "./keyType";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { extLogger } from "../common";
|
||||
import { createInitialQueryInfo } from "../run-queries-shared";
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../pure/ql";
|
||||
|
||||
@@ -169,32 +168,30 @@ export async function runContextualQuery(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string>,
|
||||
) {
|
||||
): Promise<CoreCompletedQuery> {
|
||||
const { packPath, createdTempLockFile } = await resolveContextualQuery(
|
||||
cli,
|
||||
query,
|
||||
);
|
||||
const initialInfo = await createInitialQueryInfo(
|
||||
Uri.file(query),
|
||||
{
|
||||
name: db.name,
|
||||
databaseUri: db.databaseUri.toString(),
|
||||
},
|
||||
const queryRun = qs.createQueryRun(
|
||||
db.databaseUri.fsPath,
|
||||
{ queryPath: query, quickEvalPosition: undefined },
|
||||
false,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
queryStorageDir,
|
||||
undefined,
|
||||
templates,
|
||||
);
|
||||
void extLogger.log(
|
||||
`Running contextual query ${query}; results will be stored in ${queryStorageDir}`,
|
||||
`Running contextual query ${query}; results will be stored in ${queryRun.outputDir.querySaveDir}`,
|
||||
);
|
||||
const queryResult = await qs.compileAndRunQueryAgainstDatabase(
|
||||
db,
|
||||
initialInfo,
|
||||
queryStorageDir,
|
||||
const results = await queryRun.evaluate(
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
new TeeLogger(qs.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
if (createdTempLockFile) {
|
||||
await removeTemporaryLockFile(packPath);
|
||||
}
|
||||
return queryResult;
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ import {
|
||||
runContextualQuery,
|
||||
} from "./queryResolver";
|
||||
import { isCanary, NO_CACHE_AST_VIEWER } from "../config";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
|
||||
/**
|
||||
* Runs templated CodeQL queries to find definitions in
|
||||
@@ -155,17 +154,12 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
type QueryWithDb = {
|
||||
query: QueryWithResults;
|
||||
dbUri: Uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run templated CodeQL queries to produce AST information for
|
||||
* source-language files.
|
||||
*/
|
||||
export class TemplatePrintAstProvider {
|
||||
private cache: CachedOperation<QueryWithDb>;
|
||||
private cache: CachedOperation<CoreCompletedQuery>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
@@ -173,7 +167,9 @@ export class TemplatePrintAstProvider {
|
||||
private dbm: DatabaseManager,
|
||||
private queryStorageDir: string,
|
||||
) {
|
||||
this.cache = new CachedOperation<QueryWithDb>(this.getAst.bind(this));
|
||||
this.cache = new CachedOperation<CoreCompletedQuery>(
|
||||
this.getAst.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
async provideAst(
|
||||
@@ -186,14 +182,14 @@ export class TemplatePrintAstProvider {
|
||||
"Cannot view the AST. Please select a valid source file inside a CodeQL database.",
|
||||
);
|
||||
}
|
||||
const { query, dbUri } = this.shouldCache()
|
||||
const completedQuery = this.shouldCache()
|
||||
? await this.cache.get(fileUri.toString(), progress, token)
|
||||
: await this.getAst(fileUri.toString(), progress, token);
|
||||
|
||||
return new AstBuilder(
|
||||
query,
|
||||
completedQuery.outputDir,
|
||||
this.cli,
|
||||
this.dbm.findDatabaseItem(dbUri)!,
|
||||
this.dbm.findDatabaseItem(Uri.file(completedQuery.dbPath))!,
|
||||
fileUri,
|
||||
);
|
||||
}
|
||||
@@ -206,7 +202,7 @@ export class TemplatePrintAstProvider {
|
||||
uriString: string,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<QueryWithDb> {
|
||||
): Promise<CoreCompletedQuery> {
|
||||
const uri = Uri.parse(uriString, true);
|
||||
if (uri.scheme !== zipArchiveScheme) {
|
||||
throw new Error(
|
||||
@@ -242,7 +238,7 @@ export class TemplatePrintAstProvider {
|
||||
[TEMPLATE_NAME]: zippedArchive.pathWithinSourceArchive,
|
||||
};
|
||||
|
||||
const queryResult = await runContextualQuery(
|
||||
const results = await runContextualQuery(
|
||||
query,
|
||||
db,
|
||||
this.queryStorageDir,
|
||||
@@ -252,10 +248,7 @@ export class TemplatePrintAstProvider {
|
||||
token,
|
||||
templates,
|
||||
);
|
||||
return {
|
||||
query: queryResult,
|
||||
dbUri: db.databaseUri,
|
||||
};
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -260,6 +257,7 @@ export interface CodeQLExtensionInterface {
|
||||
readonly distributionManager: DistributionManager;
|
||||
readonly databaseManager: DatabaseManager;
|
||||
readonly databaseUI: DatabaseUI;
|
||||
readonly localQueries: LocalQueries;
|
||||
readonly variantAnalysisManager: VariantAnalysisManager;
|
||||
readonly dispose: () => void;
|
||||
}
|
||||
@@ -716,12 +714,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);
|
||||
|
||||
@@ -795,8 +787,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);
|
||||
@@ -817,7 +811,8 @@ async function activateWithInstalledDistribution(
|
||||
cliServer,
|
||||
queryServerLogger,
|
||||
labelProvider,
|
||||
showResults,
|
||||
async (item: CompletedLocalQueryInfo) =>
|
||||
localQueries.showResultsForCompletedQuery(item, WebviewReveal.Forced),
|
||||
);
|
||||
ctx.subscriptions.push(compareView);
|
||||
|
||||
@@ -853,6 +848,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,
|
||||
@@ -906,11 +913,7 @@ async function activateWithInstalledDistribution(
|
||||
...databaseUI.getCommands(),
|
||||
...dbModule.getCommands(),
|
||||
...getAstCfgCommands({
|
||||
queryRunner: qs,
|
||||
queryHistoryManager: qhm,
|
||||
databaseUI,
|
||||
localQueryResultsView,
|
||||
queryStorageDir,
|
||||
localQueries,
|
||||
astViewer,
|
||||
astTemplateProvider,
|
||||
cfgTemplateProvider,
|
||||
@@ -930,16 +933,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)) {
|
||||
@@ -989,6 +983,7 @@ async function activateWithInstalledDistribution(
|
||||
return {
|
||||
ctx,
|
||||
cliServer,
|
||||
localQueries,
|
||||
qs,
|
||||
distributionManager,
|
||||
databaseManager: dbm,
|
||||
|
||||
@@ -754,7 +754,7 @@ export async function tryGetQueryMetadata(
|
||||
* Creates a file in the query directory that indicates when this query was created.
|
||||
* This is important for keeping track of when queries should be removed.
|
||||
*
|
||||
* @param queryPath The directory that will containt all files relevant to a query result.
|
||||
* @param queryPath The directory that will contain all files relevant to a query result.
|
||||
* It does not need to exist.
|
||||
*/
|
||||
export async function createTimestampFile(storagePath: string) {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { Logger } from "../common";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
Dataset,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
} from "../pure/legacy-messages";
|
||||
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryServerClient } from "./queryserver-client";
|
||||
import {
|
||||
clearCacheInDatabase,
|
||||
compileAndRunQueryAgainstDatabase,
|
||||
compileAndRunQueryAgainstDatabaseCore,
|
||||
} from "./run-queries";
|
||||
import { upgradeDatabaseExplicit } from "./upgrades";
|
||||
|
||||
@@ -21,10 +22,18 @@ export class LegacyQueryRunner extends QueryRunner {
|
||||
super();
|
||||
}
|
||||
|
||||
get cliServer() {
|
||||
get cliServer(): CodeQLCliServer {
|
||||
return this.qs.cliServer;
|
||||
}
|
||||
|
||||
get customLogDirectory(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get logger(): Logger {
|
||||
return this.qs.logger;
|
||||
}
|
||||
|
||||
async restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
@@ -47,25 +56,29 @@ export class LegacyQueryRunner extends QueryRunner {
|
||||
): Promise<void> {
|
||||
await clearCacheInDatabase(this.qs, dbItem, progress, token);
|
||||
}
|
||||
async compileAndRunQueryAgainstDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
initialInfo: InitialQueryInfo,
|
||||
queryStorageDir: string,
|
||||
|
||||
public async compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates?: Record<string, string>,
|
||||
queryInfo?: LocalQueryInfo,
|
||||
): Promise<QueryWithResults> {
|
||||
return await compileAndRunQueryAgainstDatabase(
|
||||
this.qs.cliServer,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
return await compileAndRunQueryAgainstDatabaseCore(
|
||||
this.qs,
|
||||
dbItem,
|
||||
initialInfo,
|
||||
queryStorageDir,
|
||||
dbPath,
|
||||
query,
|
||||
generateEvalLog,
|
||||
additionalPacks,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
queryInfo,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,164 @@
|
||||
import * as tmp from "tmp-promise";
|
||||
import { basename, join } from "path";
|
||||
import { basename } from "path";
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
|
||||
|
||||
import * as cli from "../cli";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
tryGetQueryMetadata,
|
||||
upgradesTmpDir,
|
||||
} from "../helpers";
|
||||
DatabaseContentsWithDbScheme,
|
||||
DatabaseItem,
|
||||
DatabaseResolver,
|
||||
} from "../local-databases";
|
||||
import { showAndLogExceptionWithTelemetry, upgradesTmpDir } from "../helpers";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { QueryMetadata } from "../pure/interface-types";
|
||||
import { extLogger, Logger, TeeLogger } from "../common";
|
||||
import { extLogger, Logger } from "../common";
|
||||
import * as messages from "../pure/legacy-messages";
|
||||
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import * as newMessages from "../pure/new-messages";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { compileDatabaseUpgradeSequence } from "./upgrades";
|
||||
import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryEvaluationInfo, QueryOutputDir } from "../run-queries-shared";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
|
||||
import { Position } from "../pure/messages-shared";
|
||||
|
||||
export async function compileQuery(
|
||||
qs: qsClient.QueryServerClient,
|
||||
program: messages.QlProgram,
|
||||
quickEvalPosition: Position | undefined,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: Logger,
|
||||
): Promise<messages.CompilationMessage[]> {
|
||||
let compiled: messages.CheckQueryResult | undefined;
|
||||
try {
|
||||
const target: messages.CompilationTarget = quickEvalPosition
|
||||
? {
|
||||
quickEval: { quickEvalPos: quickEvalPosition },
|
||||
}
|
||||
: { query: {} };
|
||||
const params: messages.CompileQueryParams = {
|
||||
compilationOptions: {
|
||||
computeNoLocationUrls: true,
|
||||
failOnWarnings: false,
|
||||
fastCompilation: false,
|
||||
includeDilInQlo: true,
|
||||
localChecking: false,
|
||||
noComputeGetUrl: false,
|
||||
noComputeToString: false,
|
||||
computeDefaultStrings: true,
|
||||
emitDebugInfo: true,
|
||||
},
|
||||
extraOptions: {
|
||||
timeoutSecs: qs.config.timeoutSecs,
|
||||
},
|
||||
queryToCheck: program,
|
||||
resultPath: outputDir.compileQueryPath,
|
||||
target,
|
||||
};
|
||||
|
||||
// Update the active query logger every time there is a new request to compile.
|
||||
// This isn't ideal because in situations where there are queries running
|
||||
// in parallel, each query's log messages are interleaved. Fixing this
|
||||
// properly will require a change in the query server.
|
||||
qs.activeQueryLogger = logger;
|
||||
compiled = await qs.sendRequest(
|
||||
messages.compileQuery,
|
||||
params,
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
} finally {
|
||||
void logger.log(" - - - COMPILATION DONE - - - ");
|
||||
}
|
||||
return (compiled?.messages || []).filter(
|
||||
(msg) => msg.severity === messages.Severity.ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
async function runQuery(
|
||||
qs: qsClient.QueryServerClient,
|
||||
upgradeQlo: string | undefined,
|
||||
availableMlModels: cli.MlModelInfo[],
|
||||
dbContents: DatabaseContentsWithDbScheme,
|
||||
templates: Record<string, string> | undefined,
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<messages.EvaluationResult> {
|
||||
let result: messages.EvaluationResult | null = null;
|
||||
|
||||
const logPath = outputDir.logPath;
|
||||
|
||||
const callbackId = qs.registerCallback((res) => {
|
||||
result = {
|
||||
...res,
|
||||
logFileLocation: logPath,
|
||||
};
|
||||
});
|
||||
|
||||
const availableMlModelUris: messages.MlModel[] = availableMlModels.map(
|
||||
(model) => ({ uri: Uri.file(model.path).toString(true) }),
|
||||
);
|
||||
|
||||
const queryToRun: messages.QueryToRun = {
|
||||
resultsPath: outputDir.bqrsPath,
|
||||
qlo: Uri.file(outputDir.compileQueryPath).toString(),
|
||||
compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(),
|
||||
allowUnknownTemplates: true,
|
||||
templateValues: createSimpleTemplates(templates),
|
||||
availableMlModels: availableMlModelUris,
|
||||
id: callbackId,
|
||||
timeoutSecs: qs.config.timeoutSecs,
|
||||
};
|
||||
|
||||
const dataset: messages.Dataset = {
|
||||
dbDir: dbContents.datasetUri.fsPath,
|
||||
workingSet: "default",
|
||||
};
|
||||
if (
|
||||
generateEvalLog &&
|
||||
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
|
||||
) {
|
||||
await qs.sendRequest(messages.startLog, {
|
||||
db: dataset,
|
||||
logPath: outputDir.evalLogPath,
|
||||
});
|
||||
}
|
||||
const params: messages.EvaluateQueriesParams = {
|
||||
db: dataset,
|
||||
evaluateId: callbackId,
|
||||
queries: [queryToRun],
|
||||
stopOnError: false,
|
||||
useSequenceHint: false,
|
||||
};
|
||||
try {
|
||||
await qs.sendRequest(messages.runQueries, params, token, progress);
|
||||
} finally {
|
||||
qs.unRegisterCallback(callbackId);
|
||||
if (
|
||||
generateEvalLog &&
|
||||
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
|
||||
) {
|
||||
await qs.sendRequest(messages.endLog, {
|
||||
db: dataset,
|
||||
logPath: outputDir.evalLogPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
return (
|
||||
result || {
|
||||
evaluationTime: 0,
|
||||
message: "No result from server",
|
||||
queryId: -1,
|
||||
runId: callbackId,
|
||||
resultType: messages.QueryResultType.OTHER_ERROR,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of evaluation-time information about a query,
|
||||
@@ -40,7 +176,7 @@ export class QueryInProgress {
|
||||
readonly querySaveDir: string,
|
||||
readonly dbItemPath: string,
|
||||
databaseHasMetadataFile: boolean,
|
||||
readonly queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution
|
||||
readonly queryDbscheme: string, // the dbscheme file the query expects, ba`sed on library path resolution
|
||||
readonly quickEvalPosition?: messages.Position,
|
||||
readonly metadata?: QueryMetadata,
|
||||
readonly templates?: Record<string, string>,
|
||||
@@ -54,162 +190,6 @@ export class QueryInProgress {
|
||||
);
|
||||
/**/
|
||||
}
|
||||
|
||||
get compiledQueryPath() {
|
||||
return this.queryEvalInfo.compileQueryPath;
|
||||
}
|
||||
|
||||
async run(
|
||||
qs: qsClient.QueryServerClient,
|
||||
upgradeQlo: string | undefined,
|
||||
availableMlModels: cli.MlModelInfo[],
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: Logger,
|
||||
queryInfo: LocalQueryInfo | undefined,
|
||||
): Promise<messages.EvaluationResult> {
|
||||
if (!dbItem.contents || dbItem.error) {
|
||||
throw new Error("Can't run query on invalid database.");
|
||||
}
|
||||
|
||||
let result: messages.EvaluationResult | null = null;
|
||||
|
||||
const callbackId = qs.registerCallback((res) => {
|
||||
result = {
|
||||
...res,
|
||||
logFileLocation: this.queryEvalInfo.logPath,
|
||||
};
|
||||
});
|
||||
|
||||
const availableMlModelUris: messages.MlModel[] = availableMlModels.map(
|
||||
(model) => ({ uri: Uri.file(model.path).toString(true) }),
|
||||
);
|
||||
|
||||
const queryToRun: messages.QueryToRun = {
|
||||
resultsPath: this.queryEvalInfo.resultsPaths.resultsPath,
|
||||
qlo: Uri.file(this.compiledQueryPath).toString(),
|
||||
compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(),
|
||||
allowUnknownTemplates: true,
|
||||
templateValues: createSimpleTemplates(this.templates),
|
||||
availableMlModels: availableMlModelUris,
|
||||
id: callbackId,
|
||||
timeoutSecs: qs.config.timeoutSecs,
|
||||
};
|
||||
|
||||
const dataset: messages.Dataset = {
|
||||
dbDir: dbItem.contents.datasetUri.fsPath,
|
||||
workingSet: "default",
|
||||
};
|
||||
if (
|
||||
queryInfo &&
|
||||
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
|
||||
) {
|
||||
await qs.sendRequest(messages.startLog, {
|
||||
db: dataset,
|
||||
logPath: this.queryEvalInfo.evalLogPath,
|
||||
});
|
||||
}
|
||||
const params: messages.EvaluateQueriesParams = {
|
||||
db: dataset,
|
||||
evaluateId: callbackId,
|
||||
queries: [queryToRun],
|
||||
stopOnError: false,
|
||||
useSequenceHint: false,
|
||||
};
|
||||
try {
|
||||
await qs.sendRequest(messages.runQueries, params, token, progress);
|
||||
if (qs.config.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 ${this.queryEvalInfo.logPath}.`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
qs.unRegisterCallback(callbackId);
|
||||
if (
|
||||
queryInfo &&
|
||||
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
|
||||
) {
|
||||
await qs.sendRequest(messages.endLog, {
|
||||
db: dataset,
|
||||
logPath: this.queryEvalInfo.evalLogPath,
|
||||
});
|
||||
if (await this.queryEvalInfo.hasEvalLog()) {
|
||||
await this.queryEvalInfo.addQueryLogs(
|
||||
queryInfo,
|
||||
qs.cliServer,
|
||||
logger,
|
||||
);
|
||||
} else {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to write structured evaluator log to ${this.queryEvalInfo.evalLogPath}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
result || {
|
||||
evaluationTime: 0,
|
||||
message: "No result from server",
|
||||
queryId: -1,
|
||||
runId: callbackId,
|
||||
resultType: messages.QueryResultType.OTHER_ERROR,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async compile(
|
||||
qs: qsClient.QueryServerClient,
|
||||
program: messages.QlProgram,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: Logger,
|
||||
): Promise<messages.CompilationMessage[]> {
|
||||
let compiled: messages.CheckQueryResult | undefined;
|
||||
try {
|
||||
const target = this.quickEvalPosition
|
||||
? {
|
||||
quickEval: { quickEvalPos: this.quickEvalPosition },
|
||||
}
|
||||
: { query: {} };
|
||||
const params: messages.CompileQueryParams = {
|
||||
compilationOptions: {
|
||||
computeNoLocationUrls: true,
|
||||
failOnWarnings: false,
|
||||
fastCompilation: false,
|
||||
includeDilInQlo: true,
|
||||
localChecking: false,
|
||||
noComputeGetUrl: false,
|
||||
noComputeToString: false,
|
||||
computeDefaultStrings: true,
|
||||
emitDebugInfo: true,
|
||||
},
|
||||
extraOptions: {
|
||||
timeoutSecs: qs.config.timeoutSecs,
|
||||
},
|
||||
queryToCheck: program,
|
||||
resultPath: this.compiledQueryPath,
|
||||
target,
|
||||
};
|
||||
|
||||
// Update the active query logger every time there is a new request to compile.
|
||||
// This isn't ideal because in situations where there are queries running
|
||||
// in parallel, each query's log messages are interleaved. Fixing this
|
||||
// properly will require a change in the query server.
|
||||
qs.activeQueryLogger = logger;
|
||||
compiled = await qs.sendRequest(
|
||||
messages.compileQuery,
|
||||
params,
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
} finally {
|
||||
void logger.log(" - - - COMPILATION DONE - - - ");
|
||||
}
|
||||
return (compiled?.messages || []).filter(
|
||||
(msg) => msg.severity === messages.Severity.ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearCacheInDatabase(
|
||||
@@ -237,10 +217,10 @@ export async function clearCacheInDatabase(
|
||||
|
||||
function reportNoUpgradePath(
|
||||
qlProgram: messages.QlProgram,
|
||||
query: QueryInProgress,
|
||||
queryDbscheme: string,
|
||||
): void {
|
||||
throw new Error(
|
||||
`Query ${qlProgram.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`,
|
||||
`Query ${qlProgram.queryPath} expects database scheme ${queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -250,32 +230,27 @@ function reportNoUpgradePath(
|
||||
async function compileNonDestructiveUpgrade(
|
||||
qs: qsClient.QueryServerClient,
|
||||
upgradeTemp: tmp.DirectoryResult,
|
||||
query: QueryInProgress,
|
||||
queryDbscheme: string,
|
||||
qlProgram: messages.QlProgram,
|
||||
dbItem: DatabaseItem,
|
||||
dbContents: DatabaseContentsWithDbScheme,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<string> {
|
||||
if (!dbItem?.contents?.dbSchemeUri) {
|
||||
throw new Error("Database is invalid, and cannot be upgraded.");
|
||||
}
|
||||
|
||||
// Dependencies may exist outside of the workspace and they are always on the resolved search path.
|
||||
const upgradesPath = qlProgram.libraryPath;
|
||||
|
||||
const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades(
|
||||
dbItem.contents.dbSchemeUri.fsPath,
|
||||
dbContents.dbSchemeUri.fsPath,
|
||||
upgradesPath,
|
||||
true,
|
||||
query.queryDbscheme,
|
||||
queryDbscheme,
|
||||
);
|
||||
|
||||
if (!matchesTarget) {
|
||||
reportNoUpgradePath(qlProgram, query);
|
||||
reportNoUpgradePath(qlProgram, queryDbscheme);
|
||||
}
|
||||
const result = await compileDatabaseUpgradeSequence(
|
||||
qs,
|
||||
dbItem,
|
||||
scripts,
|
||||
upgradeTemp,
|
||||
progress,
|
||||
@@ -286,34 +261,67 @@ async function compileNonDestructiveUpgrade(
|
||||
throw new Error(error);
|
||||
}
|
||||
// We can upgrade to the actual target
|
||||
qlProgram.dbschemePath = query.queryDbscheme;
|
||||
qlProgram.dbschemePath = queryDbscheme;
|
||||
// We are new enough that we will always support single file upgrades.
|
||||
return result.compiledUpgrade;
|
||||
}
|
||||
|
||||
export async function compileAndRunQueryAgainstDatabase(
|
||||
cliServer: cli.CodeQLCliServer,
|
||||
qs: qsClient.QueryServerClient,
|
||||
dbItem: 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> {
|
||||
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
|
||||
throw new Error(
|
||||
`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`,
|
||||
);
|
||||
function translateLegacyResult(
|
||||
legacyResult: messages.EvaluationResult,
|
||||
): Omit<CoreQueryResults, "dispose"> {
|
||||
let newResultType: newMessages.QueryResultType;
|
||||
let newMessage = legacyResult.message;
|
||||
switch (legacyResult.resultType) {
|
||||
case messages.QueryResultType.SUCCESS:
|
||||
newResultType = newMessages.QueryResultType.SUCCESS;
|
||||
break;
|
||||
case messages.QueryResultType.CANCELLATION:
|
||||
newResultType = newMessages.QueryResultType.CANCELLATION;
|
||||
break;
|
||||
case messages.QueryResultType.OOM:
|
||||
newResultType = newMessages.QueryResultType.OOM;
|
||||
break;
|
||||
case messages.QueryResultType.TIMEOUT:
|
||||
// This is the only legacy result type that doesn't exist for the new query server. Format the
|
||||
// message here, and let the later code treat it as `OTHER_ERROR`.
|
||||
newResultType = newMessages.QueryResultType.OTHER_ERROR;
|
||||
newMessage = `timed out after ${Math.round(
|
||||
legacyResult.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
break;
|
||||
case messages.QueryResultType.OTHER_ERROR:
|
||||
default:
|
||||
newResultType = newMessages.QueryResultType.OTHER_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the workspace folder paths.
|
||||
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
|
||||
return {
|
||||
resultType: newResultType,
|
||||
message: newMessage,
|
||||
evaluationTime: legacyResult.evaluationTime,
|
||||
};
|
||||
}
|
||||
|
||||
export async function compileAndRunQueryAgainstDatabaseCore(
|
||||
qs: qsClient.QueryServerClient,
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
const dbContents = await DatabaseResolver.resolveDatabaseContents(
|
||||
Uri.file(dbPath),
|
||||
);
|
||||
|
||||
// Figure out the library path for the query.
|
||||
const packConfig = await cliServer.resolveLibraryPath(
|
||||
diskWorkspaceFolders,
|
||||
initialInfo.queryPath,
|
||||
const packConfig = await qs.cliServer.resolveLibraryPath(
|
||||
additionalPacks,
|
||||
query.queryPath,
|
||||
);
|
||||
|
||||
if (!packConfig.dbscheme) {
|
||||
@@ -327,17 +335,15 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
// won't trigger this check)
|
||||
// This test will produce confusing results if we ever change the name of the database schema files.
|
||||
const querySchemaName = basename(packConfig.dbscheme);
|
||||
const dbSchemaName = basename(dbItem.contents.dbSchemeUri.fsPath);
|
||||
const dbSchemaName = basename(dbContents.dbSchemeUri?.fsPath);
|
||||
if (querySchemaName !== dbSchemaName) {
|
||||
void extLogger.log(
|
||||
`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`,
|
||||
);
|
||||
throw new Error(
|
||||
`The query ${basename(
|
||||
initialInfo.queryPath,
|
||||
)} cannot be run against the selected database (${
|
||||
dbItem.name
|
||||
}): their target languages are different. Please select a different database and try again.`,
|
||||
query.queryPath,
|
||||
)} cannot be run against the selected database: their target languages are different. Please select a different database and try again.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -349,20 +355,14 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
// Since we are compiling and running a query against a database,
|
||||
// we use the database's DB scheme here instead of the DB scheme
|
||||
// from the current document's project.
|
||||
dbschemePath: dbItem.contents.dbSchemeUri.fsPath,
|
||||
queryPath: initialInfo.queryPath,
|
||||
dbschemePath: dbContents.dbSchemeUri.fsPath,
|
||||
queryPath: query.queryPath,
|
||||
};
|
||||
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
const metadata = await tryGetQueryMetadata(cliServer, qlProgram.queryPath);
|
||||
|
||||
let availableMlModels: cli.MlModelInfo[] = [];
|
||||
try {
|
||||
availableMlModels = (
|
||||
await cliServer.resolveMlModels(
|
||||
diskWorkspaceFolders,
|
||||
initialInfo.queryPath,
|
||||
)
|
||||
await qs.cliServer.resolveMlModels(additionalPacks, query.queryPath)
|
||||
).models;
|
||||
if (availableMlModels.length) {
|
||||
void extLogger.log(
|
||||
@@ -381,57 +381,53 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
);
|
||||
}
|
||||
|
||||
const hasMetadataFile = await dbItem.hasMetadataFile();
|
||||
const query = new QueryInProgress(
|
||||
join(queryStorageDir, initialInfo.id),
|
||||
dbItem.databaseUri.fsPath,
|
||||
hasMetadataFile,
|
||||
packConfig.dbscheme,
|
||||
initialInfo.quickEvalPosition,
|
||||
metadata,
|
||||
templates,
|
||||
);
|
||||
const logger = new TeeLogger(qs.logger, query.queryEvalInfo.logPath);
|
||||
|
||||
await query.queryEvalInfo.createTimestampFile();
|
||||
|
||||
let upgradeDir: tmp.DirectoryResult | undefined;
|
||||
try {
|
||||
upgradeDir = await tmp.dir({ dir: upgradesTmpDir, unsafeCleanup: true });
|
||||
const upgradeQlo = await compileNonDestructiveUpgrade(
|
||||
qs,
|
||||
upgradeDir,
|
||||
query,
|
||||
packConfig.dbscheme,
|
||||
qlProgram,
|
||||
dbItem,
|
||||
dbContents,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
let errors;
|
||||
try {
|
||||
errors = await query.compile(qs, qlProgram, progress, token, logger);
|
||||
errors = await compileQuery(
|
||||
qs,
|
||||
qlProgram,
|
||||
query.quickEvalPosition,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
logger,
|
||||
);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof ResponseError &&
|
||||
e.code === LSPErrorCodes.RequestCancelled
|
||||
) {
|
||||
return createSyntheticResult(query, "Query cancelled");
|
||||
return createSyntheticResult("Query cancelled");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length === 0) {
|
||||
const result = await query.run(
|
||||
const result = await runQuery(
|
||||
qs,
|
||||
upgradeQlo,
|
||||
availableMlModels,
|
||||
dbItem,
|
||||
dbContents,
|
||||
templates,
|
||||
generateEvalLog,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
logger,
|
||||
queryInfo,
|
||||
);
|
||||
|
||||
if (result.resultType !== messages.QueryResultType.SUCCESS) {
|
||||
const error = result.message
|
||||
? redactableError`${result.message}`
|
||||
@@ -439,22 +435,15 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
void extLogger.log(error.fullMessage);
|
||||
void showAndLogExceptionWithTelemetry(error);
|
||||
}
|
||||
const message = formatLegacyMessage(result);
|
||||
|
||||
return {
|
||||
query: query.queryEvalInfo,
|
||||
message,
|
||||
result,
|
||||
successful: result.resultType === messages.QueryResultType.SUCCESS,
|
||||
logFileLocation: result.logFileLocation,
|
||||
};
|
||||
return translateLegacyResult(result);
|
||||
} else {
|
||||
// Error dialogs are limited in size and scrollability,
|
||||
// so we include a general description of the problem,
|
||||
// and direct the user to the output window for the detailed compilation messages.
|
||||
// However we don't show quick eval errors there so we need to display them anyway.
|
||||
void logger.log(
|
||||
`Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
|
||||
`Failed to compile query ${query.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
|
||||
);
|
||||
|
||||
const formattedMessages: string[] = [];
|
||||
@@ -465,22 +454,12 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
formattedMessages.push(formatted);
|
||||
void logger.log(formatted);
|
||||
}
|
||||
if (initialInfo.isQuickEval && formattedMessages.length <= 2) {
|
||||
// If there are more than 2 error messages, they will not be displayed well in a popup
|
||||
// and will be trimmed by the function displaying the error popup. Accordingly, we only
|
||||
// try to show the errors if there are 2 or less, otherwise we direct the user to the log.
|
||||
void showAndLogErrorMessage(
|
||||
`Quick evaluation compilation failed: ${formattedMessages.join(
|
||||
"\n",
|
||||
)}`,
|
||||
);
|
||||
} else {
|
||||
void showAndLogErrorMessage(
|
||||
(initialInfo.isQuickEval ? "Quick evaluation" : "Query") +
|
||||
compilationFailedErrorTail,
|
||||
);
|
||||
}
|
||||
return createSyntheticResult(query, "Query had compilation errors");
|
||||
|
||||
return {
|
||||
evaluationTime: 0,
|
||||
resultType: newMessages.QueryResultType.COMPILATION_ERROR,
|
||||
message: formattedMessages[0],
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
@@ -493,11 +472,6 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
}
|
||||
}
|
||||
|
||||
const compilationFailedErrorTail =
|
||||
" compilation failed. Please make sure there are no errors in the query, the database is up to date," +
|
||||
" and the query and database use the same target language. For more details on the error, go to View > Output," +
|
||||
" and choose CodeQL Query Server from the dropdown.";
|
||||
|
||||
export function formatLegacyMessage(result: messages.EvaluationResult) {
|
||||
switch (result.resultType) {
|
||||
case messages.QueryResultType.CANCELLATION:
|
||||
@@ -521,21 +495,11 @@ export function formatLegacyMessage(result: messages.EvaluationResult) {
|
||||
/**
|
||||
* Create a synthetic result for a query that failed to compile.
|
||||
*/
|
||||
function createSyntheticResult(
|
||||
query: QueryInProgress,
|
||||
message: string,
|
||||
): QueryWithResults {
|
||||
function createSyntheticResult(message: string): CoreQueryResults {
|
||||
return {
|
||||
query: query.queryEvalInfo,
|
||||
evaluationTime: 0,
|
||||
resultType: newMessages.QueryResultType.OTHER_ERROR,
|
||||
message,
|
||||
result: {
|
||||
evaluationTime: 0,
|
||||
queryId: 0,
|
||||
resultType: messages.QueryResultType.OTHER_ERROR,
|
||||
message,
|
||||
runId: 0,
|
||||
},
|
||||
successful: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -27,18 +27,11 @@ const MAX_UPGRADE_MESSAGE_LINES = 10;
|
||||
*/
|
||||
export async function compileDatabaseUpgradeSequence(
|
||||
qs: qsClient.QueryServerClient,
|
||||
dbItem: DatabaseItem,
|
||||
resolvedSequence: string[],
|
||||
currentUpgradeTmp: tmp.DirectoryResult,
|
||||
progress: ProgressCallback,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<messages.CompileUpgradeSequenceResult> {
|
||||
if (
|
||||
dbItem.contents === undefined ||
|
||||
dbItem.contents.dbSchemeUri === undefined
|
||||
) {
|
||||
throw new Error("Database is invalid, and cannot be upgraded.");
|
||||
}
|
||||
// If possible just compile the upgrade sequence
|
||||
return await qs.sendRequest(
|
||||
messages.compileUpgradeSequence,
|
||||
|
||||
@@ -95,6 +95,10 @@ export interface DatabaseContents {
|
||||
dbSchemeUri?: vscode.Uri;
|
||||
}
|
||||
|
||||
export interface DatabaseContentsWithDbScheme extends DatabaseContents {
|
||||
dbSchemeUri: vscode.Uri; // Always present
|
||||
}
|
||||
|
||||
/**
|
||||
* An error thrown when we cannot find a valid database in a putative
|
||||
* database directory.
|
||||
@@ -165,7 +169,7 @@ async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> {
|
||||
export class DatabaseResolver {
|
||||
public static async resolveDatabaseContents(
|
||||
uri: vscode.Uri,
|
||||
): Promise<DatabaseContents> {
|
||||
): Promise<DatabaseContentsWithDbScheme> {
|
||||
if (uri.scheme !== "file") {
|
||||
throw new Error(
|
||||
`Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`,
|
||||
@@ -199,9 +203,12 @@ export class DatabaseResolver {
|
||||
`Database '${databasePath}' contains multiple CodeQL dbschemes under '${dbPath}'.`,
|
||||
);
|
||||
} else {
|
||||
contents.dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0]));
|
||||
const dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0]));
|
||||
return {
|
||||
...contents,
|
||||
dbSchemeUri,
|
||||
};
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
public static async resolveDatabase(
|
||||
|
||||
@@ -7,384 +7,575 @@ import {
|
||||
Uri,
|
||||
window,
|
||||
} from "vscode";
|
||||
import { extLogger } from "./common";
|
||||
import { BaseLogger, extLogger, Logger, 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 {
|
||||
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";
|
||||
import { createInitialQueryInfo } from "./run-queries-shared";
|
||||
import {
|
||||
createInitialQueryInfo,
|
||||
determineSelectedQuery,
|
||||
EvaluatorLogPaths,
|
||||
generateEvalLogSummaries,
|
||||
logEndSummary,
|
||||
QueryEvaluationInfo,
|
||||
QueryOutputDir,
|
||||
QueryWithResults,
|
||||
SelectedQuery,
|
||||
} 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";
|
||||
|
||||
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);
|
||||
/**
|
||||
* 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,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `LocalQueryRun` object to track a query evaluation. This creates a timestamp
|
||||
* file in the query's output directory, creates a `LocalQueryInfo` object, and registers that
|
||||
* object with the query history manager.
|
||||
*
|
||||
* Once the evaluation is complete, the client must call `complete()` on the `LocalQueryRun`
|
||||
* object to update the UI based on the results of the query.
|
||||
*/
|
||||
public async createLocalQueryRun(
|
||||
selectedQuery: SelectedQuery,
|
||||
dbItem: DatabaseItem,
|
||||
outputDir: QueryOutputDir,
|
||||
tokenSource: CancellationTokenSource,
|
||||
): Promise<LocalQueryRun> {
|
||||
await createTimestampFile(outputDir.querySaveDir);
|
||||
|
||||
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(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, outputDir.logPath);
|
||||
return new LocalQueryRun(
|
||||
outputDir,
|
||||
this,
|
||||
queryInfo,
|
||||
dbItem,
|
||||
logger,
|
||||
this.queryHistoryManager,
|
||||
this.cliServer,
|
||||
);
|
||||
}
|
||||
|
||||
public async compileAndRunQuery(
|
||||
quickEval: boolean,
|
||||
queryUri: Uri | undefined,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
range?: Range,
|
||||
): Promise<void> {
|
||||
await this.compileAndRunQueryInternal(
|
||||
quickEval,
|
||||
queryUri,
|
||||
progress,
|
||||
token,
|
||||
databaseItem,
|
||||
range,
|
||||
);
|
||||
}
|
||||
|
||||
/** Used by tests */
|
||||
public async compileAndRunQueryInternal(
|
||||
quickEval: boolean,
|
||||
queryUri: Uri | undefined,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
range?: Range,
|
||||
): Promise<CoreCompletedQuery> {
|
||||
const selectedQuery = await determineSelectedQuery(
|
||||
queryUri,
|
||||
quickEval,
|
||||
range,
|
||||
);
|
||||
|
||||
// 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 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();
|
||||
try {
|
||||
token.onCancellationRequested(() => source.cancel());
|
||||
|
||||
const localQueryRun = await this.createLocalQueryRun(
|
||||
selectedQuery,
|
||||
databaseItem,
|
||||
coreQueryRun.outputDir,
|
||||
source,
|
||||
);
|
||||
|
||||
try {
|
||||
const results = await coreQueryRun.evaluate(
|
||||
progress,
|
||||
source.token,
|
||||
localQueryRun.logger,
|
||||
);
|
||||
|
||||
await localQueryRun.complete(results);
|
||||
|
||||
return results;
|
||||
} catch (e) {
|
||||
// It's odd that we have two different ways for a query evaluation to fail: by throwing an
|
||||
// exception, and by returning a result with a failure code. This is how the code worked
|
||||
// before the refactoring, so it's been preserved, but we should probably figure out how
|
||||
// to unify both error handling paths.
|
||||
const err = asError(e);
|
||||
await localQueryRun.fail(err);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
source.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private 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.");
|
||||
}
|
||||
}
|
||||
|
||||
public async showResultsForCompletedQuery(
|
||||
query: CompletedLocalQueryInfo,
|
||||
forceReveal: WebviewReveal,
|
||||
): Promise<void> {
|
||||
await this.localQueryResultsView.showResults(query, forceReveal, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ import {
|
||||
DatabaseInfo,
|
||||
} from "./pure/interface-types";
|
||||
import { QueryStatus } from "./query-status";
|
||||
import { QueryEvaluationInfo, QueryWithResults } from "./run-queries-shared";
|
||||
import {
|
||||
EvaluatorLogPaths,
|
||||
QueryEvaluationInfo,
|
||||
QueryWithResults,
|
||||
} from "./run-queries-shared";
|
||||
import { formatLegacyMessage } from "./legacy-query-server/run-queries";
|
||||
import { sarifParser } from "./sarif-parser";
|
||||
|
||||
@@ -253,6 +257,14 @@ export class LocalQueryInfo {
|
||||
this.initialInfo.userSpecifiedLabel = label;
|
||||
}
|
||||
|
||||
/** Sets the paths to the various structured evaluator logs. */
|
||||
public setEvaluatorLogPaths(logPaths: EvaluatorLogPaths): void {
|
||||
this.evalLogLocation = logPaths.log;
|
||||
this.evalLogSummaryLocation = logPaths.humanReadableSummary;
|
||||
this.jsonEvalLogSummaryLocation = logPaths.jsonSummary;
|
||||
this.evalLogSummarySymbolsLocation = logPaths.summarySymbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* The query's file name, unless it is a quick eval.
|
||||
* Queries run through quick evaluation are not usually the entire query file.
|
||||
|
||||
@@ -9,22 +9,32 @@ import {
|
||||
registerDatabases,
|
||||
upgradeDatabase,
|
||||
} from "../pure/new-messages";
|
||||
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
|
||||
import { QueryServerClient } from "./queryserver-client";
|
||||
import { compileAndRunQueryAgainstDatabase } from "./run-queries";
|
||||
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
|
||||
import * as vscode from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../helpers";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { Logger } from "../common";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
|
||||
export class NewQueryRunner extends QueryRunner {
|
||||
constructor(public readonly qs: QueryServerClient) {
|
||||
super();
|
||||
}
|
||||
|
||||
get cliServer() {
|
||||
get cliServer(): CodeQLCliServer {
|
||||
return this.qs.cliServer;
|
||||
}
|
||||
|
||||
get customLogDirectory(): string | undefined {
|
||||
return this.qs.config.customLogDirectory;
|
||||
}
|
||||
|
||||
get logger(): Logger {
|
||||
return this.qs.logger;
|
||||
}
|
||||
|
||||
async restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
@@ -57,25 +67,29 @@ export class NewQueryRunner extends QueryRunner {
|
||||
};
|
||||
await this.qs.sendRequest(clearCache, params, token, progress);
|
||||
}
|
||||
async compileAndRunQueryAgainstDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
initialInfo: InitialQueryInfo,
|
||||
queryStorageDir: string,
|
||||
|
||||
public async compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates?: Record<string, string>,
|
||||
queryInfo?: LocalQueryInfo,
|
||||
): Promise<QueryWithResults> {
|
||||
return await compileAndRunQueryAgainstDatabase(
|
||||
this.qs.cliServer,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
return await compileAndRunQueryAgainstDatabaseCore(
|
||||
this.qs,
|
||||
dbItem,
|
||||
initialInfo,
|
||||
queryStorageDir,
|
||||
dbPath,
|
||||
query,
|
||||
generateEvalLog,
|
||||
additionalPacks,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
queryInfo,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import { join } from "path";
|
||||
import { CancellationToken } from "vscode";
|
||||
import * as cli from "../cli";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
tryGetQueryMetadata,
|
||||
} from "../helpers";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import * as messages from "../pure/new-messages";
|
||||
import { QueryResultType } from "../pure/legacy-messages";
|
||||
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
|
||||
import { Logger } from "../common";
|
||||
|
||||
/**
|
||||
* run-queries.ts
|
||||
@@ -31,140 +20,58 @@ import { redactableError } from "../pure/errors";
|
||||
* output and results.
|
||||
*/
|
||||
|
||||
export async function compileAndRunQueryAgainstDatabase(
|
||||
cliServer: cli.CodeQLCliServer,
|
||||
export async function compileAndRunQueryAgainstDatabaseCore(
|
||||
qs: qsClient.QueryServerClient,
|
||||
dbItem: DatabaseItem,
|
||||
initialInfo: InitialQueryInfo,
|
||||
queryStorageDir: string,
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
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> {
|
||||
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
|
||||
throw new Error(
|
||||
`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`,
|
||||
);
|
||||
}
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
const target =
|
||||
query.quickEvalPosition !== undefined
|
||||
? {
|
||||
quickEval: { quickEvalPos: query.quickEvalPosition },
|
||||
}
|
||||
: { query: {} };
|
||||
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
const metadata = await tryGetQueryMetadata(cliServer, initialInfo.queryPath);
|
||||
|
||||
const hasMetadataFile = await dbItem.hasMetadataFile();
|
||||
const query = new QueryEvaluationInfo(
|
||||
join(queryStorageDir, initialInfo.id),
|
||||
dbItem.databaseUri.fsPath,
|
||||
hasMetadataFile,
|
||||
initialInfo.quickEvalPosition,
|
||||
metadata,
|
||||
);
|
||||
|
||||
if (!dbItem.contents || dbItem.error) {
|
||||
throw new Error("Can't run query on invalid database.");
|
||||
}
|
||||
const target = query.quickEvalPosition
|
||||
? {
|
||||
quickEval: { quickEvalPos: query.quickEvalPosition },
|
||||
}
|
||||
: { query: {} };
|
||||
|
||||
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
|
||||
const extensionPacks = (await qs.cliServer.useExtensionPacks())
|
||||
? Object.keys(await qs.cliServer.resolveQlpacks(diskWorkspaceFolders, true))
|
||||
? Object.keys(await qs.cliServer.resolveQlpacks(additionalPacks, true))
|
||||
: undefined;
|
||||
|
||||
const db = dbItem.databaseUri.fsPath;
|
||||
const logPath = queryInfo ? query.evalLogPath : undefined;
|
||||
const evalLogPath = generateEvalLog ? outputDir.evalLogPath : undefined;
|
||||
const queryToRun: messages.RunQueryParams = {
|
||||
db,
|
||||
additionalPacks: diskWorkspaceFolders,
|
||||
db: dbPath,
|
||||
additionalPacks,
|
||||
externalInputs: {},
|
||||
singletonExternalInputs: templates || {},
|
||||
outputPath: query.resultsPaths.resultsPath,
|
||||
queryPath: initialInfo.queryPath,
|
||||
dilPath: query.dilPath,
|
||||
logPath,
|
||||
outputPath: outputDir.bqrsPath,
|
||||
queryPath: query.queryPath,
|
||||
dilPath: outputDir.dilPath,
|
||||
logPath: evalLogPath,
|
||||
target,
|
||||
extensionPacks,
|
||||
};
|
||||
const logger = new TeeLogger(qs.logger, query.logPath);
|
||||
await query.createTimestampFile();
|
||||
let result: messages.RunQueryResult | undefined;
|
||||
try {
|
||||
// Update the active query logger every time there is a new request to compile.
|
||||
// This isn't ideal because in situations where there are queries running
|
||||
// in parallel, each query's log messages are interleaved. Fixing this
|
||||
// properly will require a change in the query server.
|
||||
qs.activeQueryLogger = logger;
|
||||
result = await qs.sendRequest(
|
||||
messages.runQuery,
|
||||
queryToRun,
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
if (qs.config.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 ${query.logPath}.`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (queryInfo) {
|
||||
if (await query.hasEvalLog()) {
|
||||
await query.addQueryLogs(queryInfo, qs.cliServer, logger);
|
||||
} else {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to write structured evaluator log to ${query.evalLogPath}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.resultType !== messages.QueryResultType.SUCCESS) {
|
||||
const message = result?.message
|
||||
? redactableError`${result.message}`
|
||||
: redactableError`Failed to run query`;
|
||||
void extLogger.log(message.fullMessage);
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError`Failed to run query: ${message}`,
|
||||
);
|
||||
}
|
||||
let message;
|
||||
switch (result.resultType) {
|
||||
case messages.QueryResultType.CANCELLATION:
|
||||
message = `cancelled after ${Math.round(
|
||||
result.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
break;
|
||||
case messages.QueryResultType.OOM:
|
||||
message = "out of memory";
|
||||
break;
|
||||
case messages.QueryResultType.SUCCESS:
|
||||
message = `finished in ${Math.round(
|
||||
result.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
break;
|
||||
case messages.QueryResultType.COMPILATION_ERROR:
|
||||
message = `compilation failed: ${result.message}`;
|
||||
break;
|
||||
case messages.QueryResultType.OTHER_ERROR:
|
||||
default:
|
||||
message = result.message ? `failed: ${result.message}` : "failed";
|
||||
break;
|
||||
}
|
||||
const successful = result.resultType === messages.QueryResultType.SUCCESS;
|
||||
// Update the active query logger every time there is a new request to compile.
|
||||
// This isn't ideal because in situations where there are queries running
|
||||
// in parallel, each query's log messages are interleaved. Fixing this
|
||||
// properly will require a change in the query server.
|
||||
qs.activeQueryLogger = logger;
|
||||
const result = await qs.sendRequest(
|
||||
messages.runQuery,
|
||||
queryToRun,
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
|
||||
return {
|
||||
query,
|
||||
result: {
|
||||
evaluationTime: result.evaluationTime,
|
||||
queryId: 0,
|
||||
resultType: successful
|
||||
? QueryResultType.SUCCESS
|
||||
: QueryResultType.OTHER_ERROR,
|
||||
runId: 0,
|
||||
message,
|
||||
},
|
||||
message,
|
||||
successful,
|
||||
resultType: result.resultType,
|
||||
message: result.message,
|
||||
evaluationTime: result.evaluationTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,44 @@ import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { ProgressCallback } from "./progress";
|
||||
import { DatabaseItem } from "./local-databases";
|
||||
import { InitialQueryInfo, LocalQueryInfo } from "./query-results";
|
||||
import { QueryWithResults } from "./run-queries-shared";
|
||||
import { QueryOutputDir } from "./run-queries-shared";
|
||||
import { Position, QueryResultType } from "./pure/new-messages";
|
||||
import { BaseLogger, Logger } from "./common";
|
||||
import { basename, join } from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface CoreQueryTarget {
|
||||
/** The full path to the query. */
|
||||
queryPath: string;
|
||||
/**
|
||||
* Optional position of text to be used as QuickEval target. This need not be in the same file as
|
||||
* `query`.
|
||||
*/
|
||||
quickEvalPosition?: Position;
|
||||
}
|
||||
|
||||
export interface CoreQueryResults {
|
||||
readonly resultType: QueryResultType;
|
||||
readonly message: string | undefined;
|
||||
readonly evaluationTime: number;
|
||||
}
|
||||
|
||||
export interface CoreQueryRun {
|
||||
readonly queryTarget: CoreQueryTarget;
|
||||
readonly dbPath: string;
|
||||
readonly id: string;
|
||||
readonly outputDir: QueryOutputDir;
|
||||
|
||||
evaluate(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery>;
|
||||
}
|
||||
|
||||
/** Includes both the results of the query and the initial information from `CoreQueryRun`. */
|
||||
export type CoreCompletedQuery = CoreQueryResults &
|
||||
Omit<CoreQueryRun, "evaluate">;
|
||||
|
||||
export abstract class QueryRunner {
|
||||
abstract restartQueryServer(
|
||||
@@ -12,6 +48,8 @@ export abstract class QueryRunner {
|
||||
): Promise<void>;
|
||||
|
||||
abstract cliServer: CodeQLCliServer;
|
||||
abstract customLogDirectory: string | undefined;
|
||||
abstract logger: Logger;
|
||||
|
||||
abstract onStart(
|
||||
arg0: (
|
||||
@@ -25,15 +63,20 @@ export abstract class QueryRunner {
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract compileAndRunQueryAgainstDatabase(
|
||||
dbItem: 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>;
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreQueryResults>;
|
||||
|
||||
abstract deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
@@ -54,4 +97,52 @@ export abstract class QueryRunner {
|
||||
): Promise<void>;
|
||||
|
||||
abstract clearPackCache(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
|
||||
* called to actually evaluate the query. The returned object also contains information about the
|
||||
* query evaluation that is known even before evaluation starts, including the unique ID of the
|
||||
* evaluation and the path to its output directory.
|
||||
*/
|
||||
public createQueryRun(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
queryStorageDir: string,
|
||||
id = `${basename(query.queryPath)}-${nanoid()}`,
|
||||
templates: Record<string, string> | undefined,
|
||||
): CoreQueryRun {
|
||||
const outputDir = new QueryOutputDir(join(queryStorageDir, id));
|
||||
|
||||
return {
|
||||
queryTarget: query,
|
||||
dbPath,
|
||||
id,
|
||||
outputDir,
|
||||
evaluate: async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery> => {
|
||||
return {
|
||||
id,
|
||||
outputDir,
|
||||
dbPath,
|
||||
queryTarget: query,
|
||||
...(await this.compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath,
|
||||
query,
|
||||
additionalPacks,
|
||||
generateEvalLog,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
)),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,14 @@ import {
|
||||
remove,
|
||||
readdir,
|
||||
} from "fs-extra";
|
||||
import {
|
||||
ensureMetadataIsComplete,
|
||||
InitialQueryInfo,
|
||||
LocalQueryInfo,
|
||||
} from "./query-results";
|
||||
import { ensureMetadataIsComplete, InitialQueryInfo } from "./query-results";
|
||||
import { isQuickQueryPath } from "./quick-query";
|
||||
import { nanoid } from "nanoid";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { SELECT_QUERY_NAME } from "./contextual/locationFinder";
|
||||
import { DatabaseManager } from "./local-databases";
|
||||
import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types";
|
||||
import { extLogger, Logger } from "./common";
|
||||
import { BaseLogger, extLogger } from "./common";
|
||||
import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
|
||||
@@ -42,7 +38,18 @@ import { getErrorMessage } from "./pure/helpers-pure";
|
||||
* Compiling and running QL queries.
|
||||
*/
|
||||
|
||||
export function findQueryLogFile(resultPath: string): string {
|
||||
/**
|
||||
* Holds the paths to the various structured log summary files generated for a query evaluation.
|
||||
*/
|
||||
export interface EvaluatorLogPaths {
|
||||
log: string;
|
||||
humanReadableSummary: string | undefined;
|
||||
endSummary: string | undefined;
|
||||
jsonSummary: string | undefined;
|
||||
summarySymbols: string | undefined;
|
||||
}
|
||||
|
||||
function findQueryLogFile(resultPath: string): string {
|
||||
return join(resultPath, "query.log");
|
||||
}
|
||||
|
||||
@@ -66,20 +73,11 @@ function findQueryEvalLogEndSummaryFile(resultPath: string): string {
|
||||
return join(resultPath, "evaluator-log-end.summary");
|
||||
}
|
||||
|
||||
export class QueryEvaluationInfo {
|
||||
/**
|
||||
* Note that in the {@link readQueryHistoryFromFile} method, we create a QueryEvaluationInfo instance
|
||||
* by explicitly setting the prototype in order to avoid calling this constructor.
|
||||
*/
|
||||
constructor(
|
||||
public readonly querySaveDir: string,
|
||||
public readonly dbItemPath: string,
|
||||
private readonly databaseHasMetadataFile: boolean,
|
||||
public readonly quickEvalPosition?: messages.Position,
|
||||
public readonly metadata?: QueryMetadata,
|
||||
) {
|
||||
/**/
|
||||
}
|
||||
/**
|
||||
* Provides paths to the files that can be generated in the output directory for a query evaluation.
|
||||
*/
|
||||
export class QueryOutputDir {
|
||||
constructor(public readonly querySaveDir: string) {}
|
||||
|
||||
get dilPath() {
|
||||
return join(this.querySaveDir, "results.dil");
|
||||
@@ -120,9 +118,35 @@ export class QueryEvaluationInfo {
|
||||
return findQueryEvalLogEndSummaryFile(this.querySaveDir);
|
||||
}
|
||||
|
||||
get bqrsPath() {
|
||||
return join(this.querySaveDir, "results.bqrs");
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryEvaluationInfo extends QueryOutputDir {
|
||||
// We extend `QueryOutputDir`, rather than having it as a property, because we need
|
||||
// `QueryOutputDir`'s `querySaveDir` property to be a property of `QueryEvaluationInfo`. This is
|
||||
// because `QueryEvaluationInfo` is serialized directly as JSON, and before we hoisted
|
||||
// `QueryOutputDir` out into a base class, `querySaveDir` was a property on `QueryEvaluationInfo`
|
||||
// itself.
|
||||
|
||||
/**
|
||||
* Note that in the {@link readQueryHistoryFromFile} method, we create a QueryEvaluationInfo instance
|
||||
* by explicitly setting the prototype in order to avoid calling this constructor.
|
||||
*/
|
||||
constructor(
|
||||
querySaveDir: string,
|
||||
public readonly dbItemPath: string,
|
||||
private readonly databaseHasMetadataFile: boolean,
|
||||
public readonly quickEvalPosition?: messages.Position,
|
||||
public readonly metadata?: QueryMetadata,
|
||||
) {
|
||||
super(querySaveDir);
|
||||
}
|
||||
|
||||
get resultsPaths() {
|
||||
return {
|
||||
resultsPath: join(this.querySaveDir, "results.bqrs"),
|
||||
resultsPath: this.bqrsPath,
|
||||
interpretedResultsPath: join(
|
||||
this.querySaveDir,
|
||||
this.metadata?.kind === "graph"
|
||||
@@ -228,85 +252,6 @@ export class QueryEvaluationInfo {
|
||||
return pathExists(this.evalLogPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the structured evaluator log to the query evaluation info.
|
||||
*/
|
||||
async addQueryLogs(
|
||||
queryInfo: LocalQueryInfo,
|
||||
cliServer: CodeQLCliServer,
|
||||
logger: Logger,
|
||||
) {
|
||||
queryInfo.evalLogLocation = this.evalLogPath;
|
||||
queryInfo.evalLogSummaryLocation =
|
||||
await this.generateHumanReadableLogSummary(cliServer);
|
||||
void this.logEndSummary(queryInfo.evalLogSummaryLocation, logger); // Logged asynchrnously
|
||||
if (isCanary()) {
|
||||
// Generate JSON summary for viewer.
|
||||
await cliServer.generateJsonLogSummary(
|
||||
this.evalLogPath,
|
||||
this.jsonEvalLogSummaryPath,
|
||||
);
|
||||
queryInfo.jsonEvalLogSummaryLocation = this.jsonEvalLogSummaryPath;
|
||||
await generateSummarySymbolsFile(
|
||||
this.evalLogSummaryPath,
|
||||
this.evalLogSummarySymbolsPath,
|
||||
);
|
||||
queryInfo.evalLogSummarySymbolsLocation = this.evalLogSummarySymbolsPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the appropriate CLI command to generate a human-readable log summary.
|
||||
* @param qs The query server client.
|
||||
* @returns The path to the log summary, or `undefined` if the summary could not be generated. */
|
||||
private async generateHumanReadableLogSummary(
|
||||
cliServer: CodeQLCliServer,
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
await cliServer.generateLogSummary(
|
||||
this.evalLogPath,
|
||||
this.evalLogSummaryPath,
|
||||
this.evalLogEndSummaryPath,
|
||||
);
|
||||
return this.evalLogSummaryPath;
|
||||
} catch (e) {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to generate human-readable structured evaluator log summary. Reason: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the end summary to the Output window and log file.
|
||||
* @param logSummaryPath Path to the human-readable log summary
|
||||
* @param qs The query server client.
|
||||
*/
|
||||
private async logEndSummary(
|
||||
logSummaryPath: string | undefined,
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
if (logSummaryPath === undefined) {
|
||||
// Failed to generate the log, so we don't expect an end summary either.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const endSummaryContent = await readFile(
|
||||
this.evalLogEndSummaryPath,
|
||||
"utf-8",
|
||||
);
|
||||
void logger.log(" --- Evaluator Log Summary --- ");
|
||||
void logger.log(endSummaryContent);
|
||||
} catch (e) {
|
||||
void showAndLogWarningMessage(
|
||||
`Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the CSV file containing the results of this query. This will only be called if the query
|
||||
* does not have interpreted results and the CSV file does not already exist.
|
||||
@@ -447,7 +392,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;
|
||||
@@ -642,36 +587,113 @@ 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 selectedQuery 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"),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateEvalLogSummaries(
|
||||
cliServer: CodeQLCliServer,
|
||||
outputDir: QueryOutputDir,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const log = outputDir.evalLogPath;
|
||||
if (!(await pathExists(log))) {
|
||||
// No raw JSON log, so we can't generate any summaries.
|
||||
return undefined;
|
||||
}
|
||||
let humanReadableSummary: string | undefined = undefined;
|
||||
let endSummary: string | undefined = undefined;
|
||||
if (await generateHumanReadableLogSummary(cliServer, outputDir)) {
|
||||
humanReadableSummary = outputDir.evalLogSummaryPath;
|
||||
endSummary = outputDir.evalLogEndSummaryPath;
|
||||
}
|
||||
let jsonSummary: string | undefined = undefined;
|
||||
let summarySymbols: string | undefined = undefined;
|
||||
if (isCanary()) {
|
||||
// Generate JSON summary for viewer.
|
||||
jsonSummary = outputDir.jsonEvalLogSummaryPath;
|
||||
await cliServer.generateJsonLogSummary(log, jsonSummary);
|
||||
|
||||
if (humanReadableSummary !== undefined) {
|
||||
summarySymbols = outputDir.evalLogSummarySymbolsPath;
|
||||
await generateSummarySymbolsFile(humanReadableSummary, summarySymbols);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
log,
|
||||
humanReadableSummary,
|
||||
endSummary,
|
||||
jsonSummary,
|
||||
summarySymbols,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the appropriate CLI command to generate a human-readable log summary.
|
||||
* @param cliServer The cli server client.
|
||||
* @param outputDir The query's output directory, where all of the logs are located.
|
||||
* @returns True if the summary and end summary were generated, or false if not.
|
||||
*/
|
||||
async function generateHumanReadableLogSummary(
|
||||
cliServer: CodeQLCliServer,
|
||||
outputDir: QueryOutputDir,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await cliServer.generateLogSummary(
|
||||
outputDir.evalLogPath,
|
||||
outputDir.evalLogSummaryPath,
|
||||
outputDir.evalLogEndSummaryPath,
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to generate human-readable structured evaluator log summary. Reason: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the end summary to the Output window and log file.
|
||||
* @param logSummaryPath Path to the human-readable log summary
|
||||
* @param qs The query server client.
|
||||
*/
|
||||
export async function logEndSummary(
|
||||
endSummary: string,
|
||||
logger: BaseLogger,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const endSummaryContent = await readFile(endSummary, "utf-8");
|
||||
void logger.log(" --- Evaluator Log Summary --- ");
|
||||
void logger.log(endSummaryContent);
|
||||
} catch (e) {
|
||||
void showAndLogWarningMessage(
|
||||
`Could not read structured evaluator log end of summary file at ${endSummary}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { readdirSync, readFileSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import * as tmp from "tmp";
|
||||
import { Logger, OutputChannelLogger, TeeLogger } from "../../../src/common";
|
||||
import {
|
||||
BaseLogger,
|
||||
Logger,
|
||||
OutputChannelLogger,
|
||||
TeeLogger,
|
||||
} from "../../../src/common";
|
||||
|
||||
jest.setTimeout(999999);
|
||||
|
||||
@@ -88,7 +93,7 @@ describe("OutputChannelLogger tests", function () {
|
||||
function createSideLogger(
|
||||
logger: Logger,
|
||||
additionalLogLocation: string,
|
||||
): Logger {
|
||||
): BaseLogger {
|
||||
return new TeeLogger(
|
||||
logger,
|
||||
join(tempFolders.storagePath.name, additionalLogLocation),
|
||||
|
||||
@@ -19,12 +19,11 @@ import {
|
||||
import { importArchiveDatabase } from "../../../src/databaseFetcher";
|
||||
import { CliVersionConstraint, CodeQLCliServer } from "../../../src/cli";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { tmpDir } from "../../../src/helpers";
|
||||
import { createInitialQueryInfo } from "../../../src/run-queries-shared";
|
||||
import { QueryRunner } from "../../../src/queryRunner";
|
||||
import { CompletedQueryInfo } from "../../../src/query-results";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
|
||||
import { createMockCommandManager } from "../../__mocks__/commandsMock";
|
||||
import { LocalQueries } from "../../../src/local-queries";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
||||
import { AllCommands, QueryServerCommands } from "../../../src/common/commands";
|
||||
|
||||
@@ -38,6 +37,7 @@ describeWithCodeQL()("Queries", () => {
|
||||
let databaseManager: DatabaseManager;
|
||||
let cli: CodeQLCliServer;
|
||||
let qs: QueryRunner;
|
||||
let localQueries: LocalQueries;
|
||||
const progress = jest.fn();
|
||||
let token: CancellationToken;
|
||||
let ctx: ExtensionContext;
|
||||
@@ -55,6 +55,7 @@ describeWithCodeQL()("Queries", () => {
|
||||
databaseManager = extension.databaseManager;
|
||||
cli = extension.cliServer;
|
||||
qs = extension.qs;
|
||||
localQueries = extension.localQueries;
|
||||
cli.quiet = true;
|
||||
ctx = extension.ctx;
|
||||
qlpackFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.yml`;
|
||||
@@ -66,7 +67,11 @@ describeWithCodeQL()("Queries", () => {
|
||||
safeDel(qlFile);
|
||||
safeDel(qlpackFile);
|
||||
|
||||
token = {} as CancellationToken;
|
||||
token = {
|
||||
onCancellationRequested: (_) => {
|
||||
void _;
|
||||
},
|
||||
} as CancellationToken;
|
||||
|
||||
// Add a database, but make sure the database manager is empty first
|
||||
await cleanDatabases(databaseManager);
|
||||
@@ -136,22 +141,21 @@ describeWithCodeQL()("Queries", () => {
|
||||
}
|
||||
|
||||
async function runQueryWithExtensions() {
|
||||
const result = new CompletedQueryInfo(
|
||||
await qs.compileAndRunQueryAgainstDatabase(
|
||||
dbItem,
|
||||
await mockInitialQueryInfo(queryUsingExtensionPath),
|
||||
join(tmpDir.name, "mock-storage-path"),
|
||||
progress,
|
||||
token,
|
||||
),
|
||||
const result = await localQueries.compileAndRunQueryInternal(
|
||||
false,
|
||||
Uri.file(queryUsingExtensionPath),
|
||||
progress,
|
||||
token,
|
||||
dbItem,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Check that query was successful
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.resultType).toBe(QueryResultType.SUCCESS);
|
||||
|
||||
// Load query results
|
||||
const chunk = await qs.cliServer.bqrsDecode(
|
||||
result.getResultsPath(SELECT_QUERY_NAME, true),
|
||||
result.outputDir.bqrsPath,
|
||||
SELECT_QUERY_NAME,
|
||||
{
|
||||
// there should only be one result
|
||||
@@ -167,31 +171,33 @@ describeWithCodeQL()("Queries", () => {
|
||||
|
||||
it("should run a query", async () => {
|
||||
const queryPath = join(__dirname, "data", "simple-query.ql");
|
||||
const result = qs.compileAndRunQueryAgainstDatabase(
|
||||
dbItem,
|
||||
await mockInitialQueryInfo(queryPath),
|
||||
join(tmpDir.name, "mock-storage-path"),
|
||||
const result = await localQueries.compileAndRunQueryInternal(
|
||||
false,
|
||||
Uri.file(queryPath),
|
||||
progress,
|
||||
token,
|
||||
dbItem,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// just check that the query was successful
|
||||
expect((await result).successful).toBe(true);
|
||||
expect(result.resultType).toBe(QueryResultType.SUCCESS);
|
||||
});
|
||||
|
||||
// Asserts a fix for bug https://github.com/github/vscode-codeql/issues/733
|
||||
it("should restart the database and run a query", async () => {
|
||||
await appCommandManager.execute("codeQL.restartQueryServer");
|
||||
const queryPath = join(__dirname, "data", "simple-query.ql");
|
||||
const result = await qs.compileAndRunQueryAgainstDatabase(
|
||||
dbItem,
|
||||
await mockInitialQueryInfo(queryPath),
|
||||
join(tmpDir.name, "mock-storage-path"),
|
||||
const result = await localQueries.compileAndRunQueryInternal(
|
||||
false,
|
||||
Uri.file(queryPath),
|
||||
progress,
|
||||
token,
|
||||
dbItem,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.resultType).toBe(QueryResultType.SUCCESS);
|
||||
});
|
||||
|
||||
it("should create a quick query", async () => {
|
||||
@@ -241,15 +247,4 @@ describeWithCodeQL()("Queries", () => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function mockInitialQueryInfo(queryPath: string) {
|
||||
return await createInitialQueryInfo(
|
||||
Uri.file(queryPath),
|
||||
{
|
||||
name: dbItem.name,
|
||||
databaseUri: dbItem.databaseUri.toString(),
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CancellationToken, ExtensionContext, Uri, workspace } from "vscode";
|
||||
|
||||
import {
|
||||
DatabaseContents,
|
||||
DatabaseContentsWithDbScheme,
|
||||
DatabaseEventKind,
|
||||
DatabaseItemImpl,
|
||||
DatabaseManager,
|
||||
@@ -687,7 +688,7 @@ describe("local databases", () => {
|
||||
|
||||
resolveDatabaseContentsSpy = jest
|
||||
.spyOn(DatabaseResolver, "resolveDatabaseContents")
|
||||
.mockResolvedValue({} as DatabaseContents);
|
||||
.mockResolvedValue({} as DatabaseContentsWithDbScheme);
|
||||
|
||||
addDatabaseSourceArchiveFolderSpy = jest.spyOn(
|
||||
databaseManager,
|
||||
|
||||
@@ -3,8 +3,9 @@ import { readFileSync } from "fs-extra";
|
||||
import AstBuilder from "../../../../src/contextual/astBuilder";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { Uri } from "vscode";
|
||||
import { QueryWithResults } from "../../../../src/run-queries-shared";
|
||||
import { QueryOutputDir } from "../../../../src/run-queries-shared";
|
||||
import { mockDatabaseItem, mockedObject } from "../../utils/mocking.helpers";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -52,11 +53,12 @@ describe("AstBuilder", () => {
|
||||
const astBuilder = createAstBuilder();
|
||||
const roots = await astBuilder.getRoots();
|
||||
|
||||
const bqrsPath = path.normalize("/a/b/c/results.bqrs");
|
||||
const options = { entities: ["id", "url", "string"] };
|
||||
expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "nodes", options);
|
||||
expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "edges", options);
|
||||
expect(mockCli.bqrsDecode).toBeCalledWith(bqrsPath, "nodes", options);
|
||||
expect(mockCli.bqrsDecode).toBeCalledWith(bqrsPath, "edges", options);
|
||||
expect(mockCli.bqrsDecode).toBeCalledWith(
|
||||
"/a/b/c",
|
||||
bqrsPath,
|
||||
"graphProperties",
|
||||
options,
|
||||
);
|
||||
@@ -137,13 +139,7 @@ describe("AstBuilder", () => {
|
||||
|
||||
function createAstBuilder() {
|
||||
return new AstBuilder(
|
||||
{
|
||||
query: {
|
||||
resultsPaths: {
|
||||
resultsPath: "/a/b/c",
|
||||
},
|
||||
},
|
||||
} as QueryWithResults,
|
||||
new QueryOutputDir("/a/b/c"),
|
||||
mockCli,
|
||||
mockDatabaseItem({
|
||||
resolveSourceFile: undefined,
|
||||
|
||||
@@ -13,7 +13,10 @@ import { tmpDir } from "../../../src/helpers";
|
||||
import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client";
|
||||
import { CodeQLCliServer } from "../../../src/cli";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
|
||||
import { QueryInProgress } from "../../../src/legacy-query-server/run-queries";
|
||||
import {
|
||||
QueryInProgress,
|
||||
compileQuery as compileQueryLegacy,
|
||||
} from "../../../src/legacy-query-server/run-queries";
|
||||
import { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner";
|
||||
import { DatabaseItem } from "../../../src/local-databases";
|
||||
import { DeepPartial, mockedObject } from "../utils/mocking.helpers";
|
||||
@@ -30,7 +33,6 @@ describe("run-queries", () => {
|
||||
const saveDir = "query-save-dir";
|
||||
const info = createMockQueryInfo(true, saveDir);
|
||||
|
||||
expect(info.compiledQueryPath).toBe(join(saveDir, "compiledQuery.qlo"));
|
||||
expect(info.queryEvalInfo.dilPath).toBe(join(saveDir, "results.dil"));
|
||||
expect(info.queryEvalInfo.resultsPaths.resultsPath).toBe(
|
||||
join(saveDir, "results.bqrs"),
|
||||
@@ -185,14 +187,15 @@ describe("run-queries", () => {
|
||||
queryPath: "",
|
||||
};
|
||||
|
||||
const results = await info.compile(
|
||||
const results = await compileQueryLegacy(
|
||||
qs as any,
|
||||
mockQlProgram,
|
||||
undefined,
|
||||
info.queryEvalInfo,
|
||||
mockProgress as any,
|
||||
mockCancel as any,
|
||||
qs.logger,
|
||||
);
|
||||
|
||||
expect(results).toEqual([{ message: "err", severity: Severity.ERROR }]);
|
||||
|
||||
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
|
||||
@@ -214,7 +217,7 @@ describe("run-queries", () => {
|
||||
timeoutSecs: 5,
|
||||
},
|
||||
queryToCheck: mockQlProgram,
|
||||
resultPath: info.compiledQueryPath,
|
||||
resultPath: info.queryEvalInfo.compileQueryPath,
|
||||
target: { query: {} },
|
||||
},
|
||||
mockCancel,
|
||||
|
||||
Reference in New Issue
Block a user