Skip to content

Commit 2c75a5c

Browse files
committed
Ensure database upgrade request happens only once
When a user runs multiple queries on a non-upgraded database, ensure that only one dialog appears for upgrade. This commit also migrates the upgrades.ts file to using the passed-in cancellation token and progress monitor. This ensures that cancelling a database upgrade command will also cancel out of any wrapper operations. Fixes #534
1 parent 7f472ac commit 2c75a5c

5 files changed

Lines changed: 119 additions & 56 deletions

File tree

extensions/ql-vscode/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
- Add friendly welcome message when the databases view is empty.
66
- Add open query, open results, and remove query commands in the query history view title bar.
77
- Max number of simultaneous queries launchable by runQueries command is now configurable by changing the `codeQL.runningQueries.maxQueries` setting.
8+
- Allow simultaneously run queries to be canceled in a single-click.
9+
- Prevent multiple upgrade dialogs from appearing when running simultaneous queries on upgradeable databases.
10+
- Max number of simultaneous queries launchable by runQueries command is now configurable by changing the codeQL.runningQueries.maxQueries setting.
811
- Fix sorting of results. Some pages of results would have the wrong sort order and columns.
912
- Remember previous sort order when reloading query results.
1013
- Fix proper escaping of backslashes in SARIF message strings.

extensions/ql-vscode/src/databases-ui.ts

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,12 @@ export class DatabaseUI extends DisposableObject {
246246
ctx.subscriptions.push(
247247
commandRunner(
248248
'codeQL.upgradeCurrentDatabase',
249-
this.handleUpgradeCurrentDatabase
249+
this.handleUpgradeCurrentDatabase,
250+
{
251+
location: ProgressLocation.Notification,
252+
title: 'Upgrading current database',
253+
cancellable: true,
254+
}
250255
)
251256
);
252257
ctx.subscriptions.push(
@@ -263,13 +268,23 @@ export class DatabaseUI extends DisposableObject {
263268
ctx.subscriptions.push(
264269
commandRunner(
265270
'codeQLDatabases.chooseDatabaseFolder',
266-
this.handleChooseDatabaseFolder
271+
this.handleChooseDatabaseFolder,
272+
{
273+
location: ProgressLocation.Notification,
274+
title: 'Adding database from folder',
275+
cancellable: false,
276+
}
267277
)
268278
);
269279
ctx.subscriptions.push(
270280
commandRunner(
271281
'codeQLDatabases.chooseDatabaseArchive',
272-
this.handleChooseDatabaseArchive
282+
this.handleChooseDatabaseArchive,
283+
{
284+
location: ProgressLocation.Notification,
285+
title: 'Adding database from archive',
286+
cancellable: false,
287+
}
273288
)
274289
);
275290
ctx.subscriptions.push(
@@ -320,7 +335,12 @@ export class DatabaseUI extends DisposableObject {
320335
ctx.subscriptions.push(
321336
commandRunner(
322337
'codeQLDatabases.upgradeDatabase',
323-
this.handleUpgradeDatabase
338+
this.handleUpgradeDatabase,
339+
{
340+
location: ProgressLocation.Notification,
341+
title: 'Upgrading database',
342+
cancellable: true,
343+
}
324344
)
325345
);
326346
ctx.subscriptions.push(
@@ -393,6 +413,13 @@ export class DatabaseUI extends DisposableObject {
393413
);
394414
};
395415

416+
async tryUpgradeCurrentDatabase(
417+
progress: ProgressCallback,
418+
token: CancellationToken
419+
) {
420+
await this.handleUpgradeCurrentDatabase(progress, token);
421+
}
422+
396423
private handleSortByName = async () => {
397424
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc) {
398425
this.treeDataProvider.sortOrder = SortOrder.NameDesc;
@@ -409,45 +436,47 @@ export class DatabaseUI extends DisposableObject {
409436
}
410437
};
411438

412-
private handleUpgradeCurrentDatabase = async (): Promise<void> => {
439+
private handleUpgradeCurrentDatabase = async (
440+
progress: ProgressCallback,
441+
token: CancellationToken,
442+
): Promise<void> => {
413443
await this.handleUpgradeDatabase(
444+
progress, token,
414445
this.databaseManager.currentDatabaseItem,
415446
[]
416447
);
417448
};
418449

419450
private handleUpgradeDatabase = async (
451+
progress: ProgressCallback,
452+
token: CancellationToken,
420453
databaseItem: DatabaseItem | undefined,
421-
multiSelect: DatabaseItem[] | undefined
454+
multiSelect: DatabaseItem[] | undefined,
422455
): Promise<void> => {
423456
if (multiSelect?.length) {
424457
await Promise.all(
425-
multiSelect.map((dbItem) => this.handleUpgradeDatabase(dbItem, []))
458+
multiSelect.map((dbItem) => this.handleUpgradeDatabase(progress, token, dbItem, []))
426459
);
427460
}
428461
if (this.queryServer === undefined) {
429-
logger.log(
462+
throw new Error(
430463
'Received request to upgrade database, but there is no running query server.'
431464
);
432-
return;
433465
}
434466
if (databaseItem === undefined) {
435-
logger.log(
467+
throw new Error(
436468
'Received request to upgrade database, but no database was provided.'
437469
);
438-
return;
439470
}
440471
if (databaseItem.contents === undefined) {
441-
logger.log(
472+
throw new Error(
442473
'Received request to upgrade database, but database contents could not be found.'
443474
);
444-
return;
445475
}
446476
if (databaseItem.contents.dbSchemeUri === undefined) {
447-
logger.log(
477+
throw new Error(
448478
'Received request to upgrade database, but database has no schema.'
449479
);
450-
return;
451480
}
452481

453482
// Search for upgrade scripts in any workspace folders available
@@ -461,16 +490,17 @@ export class DatabaseUI extends DisposableObject {
461490
const { scripts, finalDbscheme } = upgradeInfo;
462491

463492
if (finalDbscheme === undefined) {
464-
logger.log('Could not determine target dbscheme to upgrade to.');
465-
return;
493+
throw new Error('Could not determine target dbscheme to upgrade to.');
466494
}
467495
const targetDbSchemeUri = Uri.file(finalDbscheme);
468496

469497
await upgradeDatabase(
470498
this.queryServer,
471499
databaseItem,
472500
targetDbSchemeUri,
473-
getUpgradesDirectories(scripts)
501+
getUpgradesDirectories(scripts),
502+
progress,
503+
token
474504
);
475505
};
476506

extensions/ql-vscode/src/extension.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
170170
cancellable: false,
171171
};
172172

173-
// Avoid using commandRunner here because this function might be called upon extension activation
173+
// Avoid using commandRunner here because this function is called upon extension activation
174174
await helpers.withProgress(progressOptions, progress =>
175175
distributionManager.installExtensionManagedDistributionRelease(result.updatedRelease, progress));
176176

@@ -512,11 +512,21 @@ async function activateWithInstalledDistribution(
512512
});
513513
}
514514

515+
if (queryUris.length > 1) {
516+
// Try to upgrade the current database before running any queries
517+
// so that the user isn't confronted with multiple upgrade
518+
// requests for each query to run.
519+
// Only do it if running multiple queries since this check is
520+
// performed on each query run anyway.
521+
await databaseUI.tryUpgradeCurrentDatabase(progress, token);
522+
}
523+
515524
wrappedProgress({
516525
maxStep: queryUris.length,
517526
step: queryUris.length - queriesRemaining,
518527
message: ''
519528
});
529+
520530
await Promise.all(queryUris.map(async uri =>
521531
compileAndRunQuery(false, uri, wrappedProgress, token)
522532
.then(() => queriesRemaining--)
@@ -550,6 +560,7 @@ async function activateWithInstalledDistribution(
550560
displayQuickQuery(ctx, cliServer, databaseUI, progress, token)
551561
)
552562
);
563+
553564
ctx.subscriptions.push(
554565
helpers.commandRunner('codeQL.restartQueryServer', async () => {
555566
await qs.restartQueryServer();

extensions/ql-vscode/src/run-queries.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,9 @@ async function getSelectedPosition(editor: TextEditor): Promise<messages.Positio
258258
async function checkDbschemeCompatibility(
259259
cliServer: cli.CodeQLCliServer,
260260
qs: qsClient.QueryServerClient,
261-
query: QueryInfo
261+
query: QueryInfo,
262+
progress: helpers.ProgressCallback,
263+
token: CancellationToken,
262264
): Promise<void> {
263265
const searchPath = helpers.getOnDiskWorkspaceFolders();
264266

@@ -293,7 +295,9 @@ async function checkDbschemeCompatibility(
293295
qs,
294296
query.dbItem,
295297
Uri.file(finalDbscheme),
296-
getUpgradesDirectories(scripts)
298+
getUpgradesDirectories(scripts),
299+
progress,
300+
token
297301
);
298302
}
299303
}
@@ -481,7 +485,7 @@ export async function compileAndRunQueryAgainstDatabase(
481485
}
482486

483487
const query = new QueryInfo(qlProgram, db, packConfig.dbscheme, quickEvalPosition, metadata, templates);
484-
await checkDbschemeCompatibility(cliServer, qs, query);
488+
await checkDbschemeCompatibility(cliServer, qs, query, progress, token);
485489

486490
let errors;
487491
try {

extensions/ql-vscode/src/upgrades.ts

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ const MAX_UPGRADE_MESSAGE_LINES = 10;
2020
* @returns the `UpgradeParams` needed to start the upgrade, if the upgrade is possible and was confirmed by the user, or `undefined` otherwise.
2121
*/
2222
async function checkAndConfirmDatabaseUpgrade(
23-
qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]
23+
qs: qsClient.QueryServerClient,
24+
db: DatabaseItem,
25+
targetDbScheme: vscode.Uri,
26+
upgradesDirectories: vscode.Uri[],
27+
progress: helpers.ProgressCallback,
28+
token: vscode.CancellationToken,
2429
): Promise<messages.UpgradeParams | undefined> {
2530
if (db.contents === undefined || db.contents.dbSchemeUri === undefined) {
26-
helpers.showAndLogErrorMessage('Database is invalid, and cannot be upgraded.');
27-
return;
31+
throw new Error('Database is invalid, and cannot be upgraded.');
2832
}
2933
const params: messages.UpgradeParams = {
3034
fromDbscheme: db.contents.dbSchemeUri.fsPath,
@@ -35,11 +39,10 @@ async function checkAndConfirmDatabaseUpgrade(
3539
let checkUpgradeResult: messages.CheckUpgradeResult;
3640
try {
3741
qs.logger.log('Checking database upgrade...');
38-
checkUpgradeResult = await checkDatabaseUpgrade(qs, params);
42+
checkUpgradeResult = await checkDatabaseUpgrade(qs, params, progress, token);
3943
}
4044
catch (e) {
41-
helpers.showAndLogErrorMessage(`Database cannot be upgraded: ${e}`);
42-
return;
45+
throw new Error(`Database cannot be upgraded: ${e}`);
4346
}
4447
finally {
4548
qs.logger.log('Done checking database upgrade.');
@@ -48,12 +51,15 @@ async function checkAndConfirmDatabaseUpgrade(
4851
const checkedUpgrades = checkUpgradeResult.checkedUpgrades;
4952
if (checkedUpgrades === undefined) {
5053
const error = checkUpgradeResult.upgradeError || '[no error message available]';
51-
await helpers.showAndLogErrorMessage(`Database cannot be upgraded: ${error}`);
52-
return;
54+
throw new Error(`Database cannot be upgraded: ${error}`);
5355
}
5456

5557
if (checkedUpgrades.scripts.length === 0) {
56-
await helpers.showAndLogInformationMessage('Database is already up to date; nothing to do.');
58+
progress({
59+
step: 3,
60+
maxStep: 3,
61+
message: 'Database is already up to date; nothing to do.'
62+
});
5763
return;
5864
}
5965

@@ -114,17 +120,19 @@ async function checkAndConfirmDatabaseUpgrade(
114120
export async function upgradeDatabase(
115121
qs: qsClient.QueryServerClient,
116122
db: DatabaseItem, targetDbScheme: vscode.Uri,
117-
upgradesDirectories: vscode.Uri[]
123+
upgradesDirectories: vscode.Uri[],
124+
progress: helpers.ProgressCallback,
125+
token: vscode.CancellationToken,
118126
): Promise<messages.RunUpgradeResult | undefined> {
119-
const upgradeParams = await checkAndConfirmDatabaseUpgrade(qs, db, targetDbScheme, upgradesDirectories);
127+
const upgradeParams = await checkAndConfirmDatabaseUpgrade(qs, db, targetDbScheme, upgradesDirectories, progress, token);
120128

121129
if (upgradeParams === undefined) {
122130
return;
123131
}
124132

125133
let compileUpgradeResult: messages.CompileUpgradeResult;
126134
try {
127-
compileUpgradeResult = await compileDatabaseUpgrade(qs, upgradeParams);
135+
compileUpgradeResult = await compileDatabaseUpgrade(qs, upgradeParams, progress, token);
128136
}
129137
catch (e) {
130138
helpers.showAndLogErrorMessage(`Compilation of database upgrades failed: ${e}`);
@@ -143,7 +151,7 @@ export async function upgradeDatabase(
143151
try {
144152
qs.logger.log('Running the following database upgrade:');
145153
qs.logger.log(compileUpgradeResult.compiledUpgrades.scripts.map(s => s.description.description).join('\n'));
146-
return await runDatabaseUpgrade(qs, db, compileUpgradeResult.compiledUpgrades);
154+
return await runDatabaseUpgrade(qs, db, compileUpgradeResult.compiledUpgrades, progress, token);
147155
}
148156
catch (e) {
149157
helpers.showAndLogErrorMessage(`Database upgrade failed: ${e}`);
@@ -155,34 +163,46 @@ export async function upgradeDatabase(
155163
}
156164

157165
async function checkDatabaseUpgrade(
158-
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
166+
qs: qsClient.QueryServerClient,
167+
upgradeParams: messages.UpgradeParams,
168+
progress: helpers.ProgressCallback,
169+
token: vscode.CancellationToken,
159170
): Promise<messages.CheckUpgradeResult> {
160-
// Avoid using commandRunner here because this function might be called upon extension activation
161-
return helpers.withProgress({
162-
location: vscode.ProgressLocation.Notification,
163-
title: 'Checking for database upgrades',
164-
cancellable: true,
165-
}, (progress, token) => qs.sendRequest(messages.checkUpgrade, upgradeParams, token, progress));
171+
progress({
172+
step: 1,
173+
maxStep: 3,
174+
message: 'Checking for database upgrades'
175+
});
176+
177+
return qs.sendRequest(messages.checkUpgrade, upgradeParams, token, progress);
166178
}
167179

168180
async function compileDatabaseUpgrade(
169-
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
181+
qs: qsClient.QueryServerClient,
182+
upgradeParams: messages.UpgradeParams,
183+
progress: helpers.ProgressCallback,
184+
token: vscode.CancellationToken,
170185
): Promise<messages.CompileUpgradeResult> {
171186
const params: messages.CompileUpgradeParams = {
172187
upgrade: upgradeParams,
173188
upgradeTempDir: upgradesTmpDir.name
174189
};
175190

176-
// Avoid using commandRunner here because this function might be called upon extension activation
177-
return helpers.withProgress({
178-
location: vscode.ProgressLocation.Notification,
179-
title: 'Compiling database upgrades',
180-
cancellable: true,
181-
}, (progress, token) => qs.sendRequest(messages.compileUpgrade, params, token, progress));
191+
progress({
192+
step: 2,
193+
maxStep: 3,
194+
message: 'Compiling database upgrades'
195+
});
196+
197+
return qs.sendRequest(messages.compileUpgrade, params, token, progress);
182198
}
183199

184200
async function runDatabaseUpgrade(
185-
qs: qsClient.QueryServerClient, db: DatabaseItem, upgrades: messages.CompiledUpgrades
201+
qs: qsClient.QueryServerClient,
202+
db: DatabaseItem,
203+
upgrades: messages.CompiledUpgrades,
204+
progress: helpers.ProgressCallback,
205+
token: vscode.CancellationToken,
186206
): Promise<messages.RunUpgradeResult> {
187207

188208
if (db.contents === undefined || db.contents.datasetUri === undefined) {
@@ -199,10 +219,5 @@ async function runDatabaseUpgrade(
199219
toRun: upgrades
200220
};
201221

202-
// Avoid using commandRunner here because this function might be called upon extension activation
203-
return helpers.withProgress({
204-
location: vscode.ProgressLocation.Notification,
205-
title: 'Running database upgrades',
206-
cancellable: true,
207-
}, (progress, token) => qs.sendRequest(messages.runUpgrade, params, token, progress));
222+
return qs.sendRequest(messages.runUpgrade, params, token, progress);
208223
}

0 commit comments

Comments
 (0)