Skip to content

Commit 4bb25a7

Browse files
authored
Merge pull request #3093 from github/koesie10/separate-bqrs-types
Create separate types for raw results sets
2 parents 0d38073 + bc1c08c commit 4bb25a7

64 files changed

Lines changed: 1552 additions & 821 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

extensions/ql-vscode/src/codeql-cli/cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { promisify } from "util";
1111
import { CancellationToken, Disposable, Uri } from "vscode";
1212

1313
import {
14-
BQRSInfo,
14+
BqrsInfo,
1515
DecodedBqrs,
1616
DecodedBqrsChunk,
1717
} from "../common/bqrs-cli-types";
@@ -928,11 +928,11 @@ export class CodeQLCliServer implements Disposable {
928928
* @param bqrsPath The path to the bqrs.
929929
* @param pageSize The page size to precompute offsets into the binary file for.
930930
*/
931-
async bqrsInfo(bqrsPath: string, pageSize?: number): Promise<BQRSInfo> {
931+
async bqrsInfo(bqrsPath: string, pageSize?: number): Promise<BqrsInfo> {
932932
const subcommandArgs = (
933933
pageSize ? ["--paginate-rows", pageSize.toString()] : []
934934
).concat(bqrsPath);
935-
return await this.runJsonCodeQlCliCommand<BQRSInfo>(
935+
return await this.runJsonCodeQlCliCommand<BqrsInfo>(
936936
["bqrs", "info"],
937937
subcommandArgs,
938938
"Reading bqrs header",

extensions/ql-vscode/src/common/bqrs-cli-types.ts

Lines changed: 28 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* the "for the sake of extensibility" comment in messages.ts.
55
*/
66
// eslint-disable-next-line @typescript-eslint/no-namespace
7-
export namespace ColumnKindCode {
7+
export namespace BqrsColumnKindCode {
88
export const FLOAT = "f";
99
export const INTEGER = "i";
1010
export const STRING = "s";
@@ -13,111 +13,81 @@ export namespace ColumnKindCode {
1313
export const ENTITY = "e";
1414
}
1515

16-
type ColumnKind =
17-
| typeof ColumnKindCode.FLOAT
18-
| typeof ColumnKindCode.INTEGER
19-
| typeof ColumnKindCode.STRING
20-
| typeof ColumnKindCode.BOOLEAN
21-
| typeof ColumnKindCode.DATE
22-
| typeof ColumnKindCode.ENTITY;
16+
export type BqrsColumnKind =
17+
| typeof BqrsColumnKindCode.FLOAT
18+
| typeof BqrsColumnKindCode.INTEGER
19+
| typeof BqrsColumnKindCode.STRING
20+
| typeof BqrsColumnKindCode.BOOLEAN
21+
| typeof BqrsColumnKindCode.DATE
22+
| typeof BqrsColumnKindCode.ENTITY;
2323

24-
interface Column {
24+
export interface BqrsSchemaColumn {
2525
name?: string;
26-
kind: ColumnKind;
26+
kind: BqrsColumnKind;
2727
}
2828

29-
export interface ResultSetSchema {
29+
export interface BqrsResultSetSchema {
3030
name: string;
3131
rows: number;
32-
columns: Column[];
33-
pagination?: PaginationInfo;
32+
columns: BqrsSchemaColumn[];
33+
pagination?: BqrsPaginationInfo;
3434
}
3535

36-
export function getResultSetSchema(
37-
resultSetName: string,
38-
resultSets: BQRSInfo,
39-
): ResultSetSchema | undefined {
40-
for (const schema of resultSets["result-sets"]) {
41-
if (schema.name === resultSetName) {
42-
return schema;
43-
}
44-
}
45-
return undefined;
46-
}
47-
interface PaginationInfo {
36+
interface BqrsPaginationInfo {
4837
"step-size": number;
4938
offsets: number[];
5039
}
5140

52-
export interface BQRSInfo {
53-
"result-sets": ResultSetSchema[];
41+
export interface BqrsInfo {
42+
"result-sets": BqrsResultSetSchema[];
5443
}
5544

5645
export type BqrsId = number;
5746

58-
export interface EntityValue {
59-
url?: UrlValue;
47+
export interface BqrsEntityValue {
48+
url?: BqrsUrlValue;
6049
label?: string;
6150
id?: BqrsId;
6251
}
6352

64-
export interface LineColumnLocation {
53+
export interface BqrsLineColumnLocation {
6554
uri: string;
6655
startLine: number;
6756
startColumn: number;
6857
endLine: number;
6958
endColumn: number;
7059
}
7160

72-
export interface WholeFileLocation {
61+
export interface BqrsWholeFileLocation {
7362
uri: string;
7463
startLine: never;
7564
startColumn: never;
7665
endLine: never;
7766
endColumn: never;
7867
}
7968

80-
export type ResolvableLocationValue = WholeFileLocation | LineColumnLocation;
81-
82-
export type UrlValue = ResolvableLocationValue | string;
83-
84-
export type CellValue = EntityValue | number | string | boolean;
69+
export type BqrsUrlValue =
70+
| BqrsWholeFileLocation
71+
| BqrsLineColumnLocation
72+
| string;
8573

86-
export type ResultRow = CellValue[];
87-
88-
export interface RawResultSet {
89-
readonly schema: ResultSetSchema;
90-
readonly rows: readonly ResultRow[];
91-
}
92-
93-
// TODO: This function is not necessary. It generates a tuple that is slightly easier
94-
// to handle than the ResultSetSchema and DecodedBqrsChunk. But perhaps it is unnecessary
95-
// boilerplate.
96-
export function transformBqrsResultSet(
97-
schema: ResultSetSchema,
98-
page: DecodedBqrsChunk,
99-
): RawResultSet {
100-
return {
101-
schema,
102-
rows: Array.from(page.tuples),
103-
};
104-
}
74+
export type BqrsCellValue = BqrsEntityValue | number | string | boolean;
10575

10676
export type BqrsKind =
10777
| "String"
10878
| "Float"
10979
| "Integer"
110-
| "String"
11180
| "Boolean"
11281
| "Date"
11382
| "Entity";
11483

115-
export interface BqrsColumn {
84+
interface BqrsColumn {
11685
name?: string;
11786
kind: BqrsKind;
11887
}
88+
11989
export interface DecodedBqrsChunk {
120-
tuples: CellValue[][];
90+
tuples: BqrsCellValue[][];
12191
next?: number;
12292
columns: BqrsColumn[];
12393
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import {
2+
BqrsCellValue as BqrsCellValue,
3+
BqrsColumnKind as BqrsColumnKind,
4+
BqrsColumnKindCode,
5+
DecodedBqrsChunk,
6+
BqrsEntityValue as BqrsEntityValue,
7+
BqrsLineColumnLocation,
8+
BqrsResultSetSchema,
9+
BqrsUrlValue as BqrsUrlValue,
10+
BqrsWholeFileLocation,
11+
BqrsSchemaColumn,
12+
} from "./bqrs-cli-types";
13+
import {
14+
CellValue,
15+
Column,
16+
ColumnKind,
17+
EntityValue,
18+
RawResultSet,
19+
Row,
20+
UrlValue,
21+
UrlValueResolvable,
22+
} from "./raw-result-types";
23+
import { assertNever } from "./helpers-pure";
24+
import { isEmptyPath } from "./bqrs-utils";
25+
26+
export function bqrsToResultSet(
27+
schema: BqrsResultSetSchema,
28+
chunk: DecodedBqrsChunk,
29+
): RawResultSet {
30+
const name = schema.name;
31+
const totalRowCount = schema.rows;
32+
33+
const columns = schema.columns.map(mapColumn);
34+
35+
const rows = chunk.tuples.map(
36+
(tuple): Row => tuple.map((cell): CellValue => mapCellValue(cell)),
37+
);
38+
39+
const resultSet: RawResultSet = {
40+
name,
41+
totalRowCount,
42+
columns,
43+
rows,
44+
};
45+
46+
if (chunk.next) {
47+
resultSet.nextPageOffset = chunk.next;
48+
}
49+
50+
return resultSet;
51+
}
52+
53+
function mapColumn(column: BqrsSchemaColumn): Column {
54+
const result: Column = {
55+
kind: mapColumnKind(column.kind),
56+
};
57+
58+
if (column.name) {
59+
result.name = column.name;
60+
}
61+
62+
return result;
63+
}
64+
65+
function mapColumnKind(kind: BqrsColumnKind): ColumnKind {
66+
switch (kind) {
67+
case BqrsColumnKindCode.STRING:
68+
return ColumnKind.String;
69+
case BqrsColumnKindCode.FLOAT:
70+
return ColumnKind.Float;
71+
case BqrsColumnKindCode.INTEGER:
72+
return ColumnKind.Integer;
73+
case BqrsColumnKindCode.BOOLEAN:
74+
return ColumnKind.Boolean;
75+
case BqrsColumnKindCode.DATE:
76+
return ColumnKind.Date;
77+
case BqrsColumnKindCode.ENTITY:
78+
return ColumnKind.Entity;
79+
default:
80+
assertNever(kind);
81+
}
82+
}
83+
84+
function mapCellValue(cellValue: BqrsCellValue): CellValue {
85+
switch (typeof cellValue) {
86+
case "string":
87+
return {
88+
type: "string",
89+
value: cellValue,
90+
};
91+
case "number":
92+
return {
93+
type: "number",
94+
value: cellValue,
95+
};
96+
case "boolean":
97+
return {
98+
type: "boolean",
99+
value: cellValue,
100+
};
101+
case "object":
102+
return {
103+
type: "entity",
104+
value: mapEntityValue(cellValue),
105+
};
106+
}
107+
}
108+
109+
function mapEntityValue(cellValue: BqrsEntityValue): EntityValue {
110+
const result: EntityValue = {};
111+
112+
if (cellValue.id) {
113+
result.id = cellValue.id;
114+
}
115+
if (cellValue.label) {
116+
result.label = cellValue.label;
117+
}
118+
if (cellValue.url) {
119+
result.url = mapUrlValue(cellValue.url);
120+
}
121+
122+
return result;
123+
}
124+
125+
export function mapUrlValue(urlValue: BqrsUrlValue): UrlValue | undefined {
126+
if (typeof urlValue === "string") {
127+
const location = tryGetLocationFromString(urlValue);
128+
if (location !== undefined) {
129+
return location;
130+
}
131+
132+
return {
133+
type: "string",
134+
value: urlValue,
135+
};
136+
}
137+
138+
if (isWholeFileLoc(urlValue)) {
139+
return {
140+
type: "wholeFileLocation",
141+
uri: urlValue.uri,
142+
};
143+
}
144+
145+
if (isLineColumnLoc(urlValue)) {
146+
return {
147+
type: "lineColumnLocation",
148+
uri: urlValue.uri,
149+
startLine: urlValue.startLine,
150+
startColumn: urlValue.startColumn,
151+
endLine: urlValue.endLine,
152+
endColumn: urlValue.endColumn,
153+
};
154+
}
155+
156+
return undefined;
157+
}
158+
159+
function isLineColumnLoc(loc: BqrsUrlValue): loc is BqrsLineColumnLocation {
160+
return (
161+
typeof loc !== "string" &&
162+
!isEmptyPath(loc.uri) &&
163+
"startLine" in loc &&
164+
"startColumn" in loc &&
165+
"endLine" in loc &&
166+
"endColumn" in loc
167+
);
168+
}
169+
170+
function isWholeFileLoc(loc: BqrsUrlValue): loc is BqrsWholeFileLocation {
171+
return (
172+
typeof loc !== "string" && !isEmptyPath(loc.uri) && !isLineColumnLoc(loc)
173+
);
174+
}
175+
176+
/**
177+
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates
178+
* to describe the location of an entire filesystem resource.
179+
* Such locations appear as `StringLocation`s instead of `FivePartLocation`s.
180+
*
181+
* Folder resources also get similar URLs, but with the `folder` scheme.
182+
* They are deliberately ignored here, since there is no suitable location to show the user.
183+
*/
184+
const FILE_LOCATION_REGEX = /file:\/\/(.+):([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
185+
186+
function tryGetLocationFromString(loc: string): UrlValueResolvable | undefined {
187+
const matches = FILE_LOCATION_REGEX.exec(loc);
188+
if (matches && matches.length > 1 && matches[1]) {
189+
if (isWholeFileMatch(matches)) {
190+
return {
191+
type: "wholeFileLocation",
192+
uri: matches[1],
193+
};
194+
} else {
195+
return {
196+
type: "lineColumnLocation",
197+
uri: matches[1],
198+
startLine: Number(matches[2]),
199+
startColumn: Number(matches[3]),
200+
endLine: Number(matches[4]),
201+
endColumn: Number(matches[5]),
202+
};
203+
}
204+
}
205+
206+
return undefined;
207+
}
208+
209+
function isWholeFileMatch(matches: RegExpExecArray): boolean {
210+
return (
211+
matches[2] === "0" &&
212+
matches[3] === "0" &&
213+
matches[4] === "0" &&
214+
matches[5] === "0"
215+
);
216+
}

0 commit comments

Comments
 (0)