@@ -45,6 +45,14 @@ interface ComparePair {
4545 commonResultSetNames : readonly string [ ] ;
4646}
4747
48+ function findSchema ( info : BqrsInfo , name : string ) {
49+ const schema = info [ "result-sets" ] . find ( ( schema ) => schema . name === name ) ;
50+ if ( schema === undefined ) {
51+ throw new Error ( `Schema ${ name } not found.` ) ;
52+ }
53+ return schema ;
54+ }
55+
4856export class CompareView extends AbstractWebview <
4957 ToCompareViewMessage ,
5058 FromCompareViewMessage
@@ -370,12 +378,7 @@ export class CompareView extends AbstractWebview<
370378 resultSetName : string ,
371379 resultsPath : string ,
372380 ) : Promise < RawResultSet > {
373- const schema = bqrsInfo [ "result-sets" ] . find (
374- ( schema ) => schema . name === resultSetName ,
375- ) ;
376- if ( ! schema ) {
377- throw new Error ( `Schema ${ resultSetName } not found.` ) ;
378- }
381+ const schema = findSchema ( bqrsInfo , resultSetName ) ;
379382 const chunk = await this . cliServer . bqrsDecode ( resultsPath , resultSetName ) ;
380383 return bqrsToResultSet ( schema , chunk ) ;
381384 }
@@ -385,20 +388,66 @@ export class CompareView extends AbstractWebview<
385388 fromResultSetName : string ,
386389 toResultSetName : string ,
387390 ) : Promise < RawQueryCompareResult > {
388- const [ fromResultSet , toResultSet ] = await Promise . all ( [
389- this . getResultSet (
390- fromInfo . schemas ,
391- fromResultSetName ,
392- from . completedQuery . query . resultsPath ,
393- ) ,
394- this . getResultSet (
395- toInfo . schemas ,
396- toResultSetName ,
397- to . completedQuery . query . resultsPath ,
398- ) ,
399- ] ) ;
391+ const fromPath = from . completedQuery . query . resultsPath ;
392+ const toPath = to . completedQuery . query . resultsPath ;
393+
394+ const fromSchema = findSchema ( fromInfo . schemas , fromResultSetName ) ;
395+ const toSchema = findSchema ( toInfo . schemas , toResultSetName ) ;
400396
401- return resultsDiff ( fromResultSet , toResultSet ) ;
397+ if ( fromSchema . columns . length !== toSchema . columns . length ) {
398+ throw new Error ( "CodeQL Compare: Columns do not match." ) ;
399+ }
400+ if ( fromSchema . rows === 0 ) {
401+ throw new Error ( "CodeQL Compare: Source query has no results." ) ;
402+ }
403+ if ( toSchema . rows === 0 ) {
404+ throw new Error ( "CodeQL Compare: Target query has no results." ) ;
405+ }
406+
407+ // If the result set names are the same, we use `bqrs diff`. This is more
408+ // efficient, but we can't use it in general as it does not support
409+ // comparing different result sets.
410+ if ( fromResultSetName === toResultSetName ) {
411+ const { uniquePath1, uniquePath2, cleanup } =
412+ await this . cliServer . bqrsDiff ( fromPath , toPath ) ;
413+ try {
414+ const info1 = await this . cliServer . bqrsInfo ( uniquePath1 ) ;
415+ const info2 = await this . cliServer . bqrsInfo ( uniquePath2 ) ;
416+
417+ // We avoid decoding the results sets if there is no overlap
418+ if (
419+ fromSchema . rows === findSchema ( info1 , fromResultSetName ) . rows &&
420+ toSchema . rows === findSchema ( info2 , toResultSetName ) . rows
421+ ) {
422+ throw new Error (
423+ "CodeQL Compare: No overlap between the selected queries." ,
424+ ) ;
425+ }
426+
427+ const fromUniqueResults = bqrsToResultSet (
428+ fromSchema ,
429+ await this . cliServer . bqrsDecode ( uniquePath1 , fromResultSetName ) ,
430+ ) ;
431+ const toUniqueResults = bqrsToResultSet (
432+ fromSchema ,
433+ await this . cliServer . bqrsDecode ( uniquePath2 , toResultSetName ) ,
434+ ) ;
435+ return {
436+ kind : "raw" ,
437+ columns : fromUniqueResults . columns ,
438+ from : fromUniqueResults . rows ,
439+ to : toUniqueResults . rows ,
440+ } ;
441+ } finally {
442+ await cleanup ( ) ;
443+ }
444+ } else {
445+ const [ fromResultSet , toResultSet ] = await Promise . all ( [
446+ this . getResultSet ( fromInfo . schemas , fromResultSetName , fromPath ) ,
447+ this . getResultSet ( toInfo . schemas , toResultSetName , toPath ) ,
448+ ] ) ;
449+ return resultsDiff ( fromResultSet , toResultSet ) ;
450+ }
402451 }
403452
404453 private async compareInterpretedResults ( {
0 commit comments