@@ -14,16 +14,17 @@ import { ErrorCodes, ResponseError } from 'vscode-languageclient';
1414
1515import * as cli from './cli' ;
1616import * as config from './config' ;
17- import { DatabaseItem , getUpgradesDirectories } from './databases' ;
17+ import { DatabaseItem } from './databases' ;
1818import { getOnDiskWorkspaceFolders , showAndLogErrorMessage } from './helpers' ;
1919import { ProgressCallback , UserCancellationException } from './commandRunner' ;
20+ import * as helpers from './helpers' ;
2021import { DatabaseInfo , QueryMetadata , ResultsPaths } from './pure/interface-types' ;
2122import { logger } from './logging' ;
2223import * as messages from './pure/messages' ;
2324import { QueryHistoryItemOptions } from './query-history' ;
2425import * as qsClient from './queryserver-client' ;
2526import { isQuickQueryPath } from './quick-query' ;
26- import { upgradeDatabase } from './upgrades' ;
27+ import { compileDatabaseUpgradeSequence , hasNondestructiveUpgradeCapabilities , upgradeDatabaseExplicit } from './upgrades' ;
2728
2829/**
2930 * run-queries.ts
@@ -80,6 +81,7 @@ export class QueryInfo {
8081
8182 async run (
8283 qs : qsClient . QueryServerClient ,
84+ upgradeQlo : string | undefined ,
8385 progress : ProgressCallback ,
8486 token : CancellationToken ,
8587 ) : Promise < messages . EvaluationResult > {
@@ -90,6 +92,7 @@ export class QueryInfo {
9092 const queryToRun : messages . QueryToRun = {
9193 resultsPath : this . resultsPaths . resultsPath ,
9294 qlo : Uri . file ( this . compiledQueryPath ) . toString ( ) ,
95+ compiledUpgrade : upgradeQlo && Uri . file ( upgradeQlo ) . toString ( ) ,
9396 allowUnknownTemplates : true ,
9497 templateValues : this . templates ,
9598 id : callbackId ,
@@ -292,7 +295,7 @@ async function checkDbschemeCompatibility(
292295 const searchPath = getOnDiskWorkspaceFolders ( ) ;
293296
294297 if ( query . dbItem . contents !== undefined && query . dbItem . contents . dbSchemeUri !== undefined ) {
295- const { scripts , finalDbscheme } = await cliServer . resolveUpgrades ( query . dbItem . contents . dbSchemeUri . fsPath , searchPath ) ;
298+ const { finalDbscheme } = await cliServer . resolveUpgrades ( query . dbItem . contents . dbSchemeUri . fsPath , searchPath ) ;
296299 const hash = async function ( filename : string ) : Promise < string > {
297300 return crypto . createHash ( 'sha256' ) . update ( await fs . readFile ( filename ) ) . digest ( 'hex' ) ;
298301 } ;
@@ -311,25 +314,60 @@ async function checkDbschemeCompatibility(
311314 const upgradableTo = await hash ( finalDbscheme ) ;
312315
313316 if ( upgradableTo != dbschemeOfLib ) {
314- logger . log ( `Query ${ query . program . queryPath } expects database scheme ${ query . queryDbscheme } , but database has scheme ${ query . program . dbschemePath } , and no upgrade path found` ) ;
315- throw new Error ( `Query ${ query . program . 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. Please try using a newer version of the query libraries.` ) ;
317+ reportNoUpgradePath ( query ) ;
316318 }
317319
318320 if ( upgradableTo == dbschemeOfLib &&
319321 dbschemeOfDb != dbschemeOfLib ) {
320322 // Try to upgrade the database
321- await upgradeDatabase (
323+ await upgradeDatabaseExplicit (
322324 qs ,
323325 query . dbItem ,
324- Uri . file ( finalDbscheme ) ,
325- getUpgradesDirectories ( scripts ) ,
326326 progress ,
327327 token
328328 ) ;
329329 }
330330 }
331331}
332332
333+
334+ function reportNoUpgradePath ( query : QueryInfo ) {
335+ logger . log ( `Query ${ query . program . queryPath } expects database scheme ${ query . queryDbscheme } , but database has scheme ${ query . program . dbschemePath } , and no upgrade path found` ) ;
336+ throw new Error ( `Query ${ query . program . 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. Please try using a newer version of the query libraries.` ) ;
337+ }
338+
339+ /**
340+ * Compile a non-destructive upgrade.
341+ */
342+ async function compileNonDestructiveUpgrade (
343+ qs : qsClient . QueryServerClient ,
344+ upgradeTemp : tmp . DirResult ,
345+ query : QueryInfo ,
346+ progress : ProgressCallback ,
347+ token : CancellationToken ,
348+ ) : Promise < string > {
349+ const searchPath = helpers . getOnDiskWorkspaceFolders ( ) ;
350+
351+ if ( query . dbItem . contents === undefined || query . dbItem . contents . dbSchemeUri === undefined ) {
352+ throw new Error ( 'Database is invalid, and cannot be upgraded.' ) ;
353+ }
354+ const { scripts, matchesTarget } = await qs . cliServer . resolveUpgrades ( query . dbItem . contents . dbSchemeUri . fsPath , searchPath , query . queryDbscheme ) ;
355+
356+ if ( ! matchesTarget ) {
357+ reportNoUpgradePath ( query ) ;
358+ }
359+ const result = await compileDatabaseUpgradeSequence ( qs , query . dbItem , query . queryDbscheme , scripts , upgradeTemp , progress , token ) ;
360+ if ( result . compiledUpgrades === undefined ) {
361+ const error = result . error || '[no error message available]' ;
362+ throw new Error ( error ) ;
363+ }
364+ // We can upgrade to the actual target
365+ query . program . dbschemePath = query . queryDbscheme ;
366+ // We are new enough that we will always support single file upgrades.
367+ return result . compiledUpgrades . compiledUpgradeFile ! ;
368+
369+ }
370+
333371/**
334372 * Prompts the user to save `document` if it has unsaved changes.
335373 *
@@ -516,64 +554,73 @@ export async function compileAndRunQueryAgainstDatabase(
516554 }
517555
518556 const query = new QueryInfo ( qlProgram , db , packConfig . dbscheme , quickEvalPosition , metadata , templates ) ;
519- await checkDbschemeCompatibility ( cliServer , qs , query , progress , token ) ;
520557
521- let errors ;
558+ const upgradeDir = tmp . dirSync ( { dir : upgradesTmpDir . name } ) ;
522559 try {
523- errors = await query . compile ( qs , progress , token ) ;
524- } catch ( e ) {
525- if ( e instanceof ResponseError && e . code == ErrorCodes . RequestCancelled ) {
526- return createSyntheticResult ( query , db , historyItemOptions , 'Query cancelled' , messages . QueryResultType . CANCELLATION ) ;
560+ let upgradeQlo ;
561+ if ( await hasNondestructiveUpgradeCapabilities ( qs ) ) {
562+ upgradeQlo = await compileNonDestructiveUpgrade ( qs , upgradeDir , query , progress , token ) ;
527563 } else {
528- throw e ;
529- }
530- }
531-
532- if ( errors . length == 0 ) {
533- const result = await query . run ( qs , progress , token ) ;
534- if ( result . resultType !== messages . QueryResultType . SUCCESS ) {
535- const message = result . message || 'Failed to run query' ;
536- logger . log ( message ) ;
537- showAndLogErrorMessage ( message ) ;
564+ await checkDbschemeCompatibility ( cliServer , qs , query , progress , token ) ;
538565 }
539- return {
540- query,
541- result,
542- database : {
543- name : db . name ,
544- databaseUri : db . databaseUri . toString ( true )
545- } ,
546- options : historyItemOptions ,
547- logFileLocation : result . logFileLocation ,
548- dispose : ( ) => {
549- qs . logger . removeAdditionalLogLocation ( result . logFileLocation ) ;
566+ let errors ;
567+ try {
568+ errors = await query . compile ( qs , progress , token ) ;
569+ } catch ( e ) {
570+ if ( e instanceof ResponseError && e . code == ErrorCodes . RequestCancelled ) {
571+ return createSyntheticResult ( query , db , historyItemOptions , 'Query cancelled' , messages . QueryResultType . CANCELLATION ) ;
572+ } else {
573+ throw e ;
550574 }
551- } ;
552- } else {
553- // Error dialogs are limited in size and scrollability,
554- // so we include a general description of the problem,
555- // and direct the user to the output window for the detailed compilation messages.
556- // However we don't show quick eval errors there so we need to display them anyway.
557- qs . logger . log ( `Failed to compile query ${ query . program . queryPath } against database scheme ${ query . program . dbschemePath } :` ) ;
558-
559- const formattedMessages : string [ ] = [ ] ;
560-
561- for ( const error of errors ) {
562- const message = error . message || '[no error message available]' ;
563- const formatted = `ERROR: ${ message } (${ error . position . fileName } :${ error . position . line } :${ error . position . column } :${ error . position . endLine } :${ error . position . endColumn } )` ;
564- formattedMessages . push ( formatted ) ;
565- qs . logger . log ( formatted ) ;
566575 }
567- if ( quickEval && formattedMessages . length <= 3 ) {
568- showAndLogErrorMessage ( 'Quick evaluation compilation failed: \n' + formattedMessages . join ( '\n' ) ) ;
576+ if ( errors . length == 0 ) {
577+ const result = await query . run ( qs , upgradeQlo , progress , token ) ;
578+ if ( result . resultType !== messages . QueryResultType . SUCCESS ) {
579+ const message = result . message || 'Failed to run query' ;
580+ logger . log ( message ) ;
581+ helpers . showAndLogErrorMessage ( message ) ;
582+ }
583+ return {
584+ query,
585+ result,
586+ database : {
587+ name : db . name ,
588+ databaseUri : db . databaseUri . toString ( true )
589+ } ,
590+ options : historyItemOptions ,
591+ logFileLocation : result . logFileLocation ,
592+ dispose : ( ) => {
593+ qs . logger . removeAdditionalLogLocation ( result . logFileLocation ) ;
594+ }
595+ } ;
569596 } else {
570- showAndLogErrorMessage ( ( quickEval ? 'Quick evaluation' : 'Query' ) +
571- ' compilation failed. Please make sure there are no errors in the query, the database is up to date,' +
572- ' and the query and database use the same target language. For more details on the error, go to View > Output,' +
573- ' and choose CodeQL Query Server from the dropdown.' ) ;
574- }
597+ // Error dialogs are limited in size and scrollability,
598+ // so we include a general description of the problem,
599+ // and direct the user to the output window for the detailed compilation messages.
600+ // However we don't show quick eval errors there so we need to display them anyway.
601+ qs . logger . log ( `Failed to compile query ${ query . program . queryPath } against database scheme ${ query . program . dbschemePath } :` ) ;
602+
603+ const formattedMessages : string [ ] = [ ] ;
604+
605+ for ( const error of errors ) {
606+ const message = error . message || '[no error message available]' ;
607+ const formatted = `ERROR: ${ message } (${ error . position . fileName } :${ error . position . line } :${ error . position . column } :${ error . position . endLine } :${ error . position . endColumn } )` ;
608+ formattedMessages . push ( formatted ) ;
609+ qs . logger . log ( formatted ) ;
610+ }
611+ if ( quickEval && formattedMessages . length <= 3 ) {
612+ showAndLogErrorMessage ( 'Quick evaluation compilation failed: \n' + formattedMessages . join ( '\n' ) ) ;
613+ } else {
614+ showAndLogErrorMessage ( ( quickEval ? 'Quick evaluation' : 'Query' ) +
615+ ' compilation failed. Please make sure there are no errors in the query, the database is up to date,' +
616+ ' and the query and database use the same target language. For more details on the error, go to View > Output,' +
617+ ' and choose CodeQL Query Server from the dropdown.' ) ;
618+ }
575619
576- return createSyntheticResult ( query , db , historyItemOptions , 'Query had compilation errors' , messages . QueryResultType . OTHER_ERROR ) ;
620+ return createSyntheticResult ( query , db , historyItemOptions , 'Query had compilation errors' , messages . QueryResultType . OTHER_ERROR ) ;
621+ }
622+ } finally {
623+ upgradeDir . removeCallback ( ) ;
577624 }
578625}
579626
0 commit comments