Skip to content

Commit a3a0513

Browse files
committed
Handle quote escaping in csv export
1 parent aa270e5 commit a3a0513

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,12 @@ export class QueryEvaluationInfo {
372372
pageSize: 100,
373373
offset: nextOffset,
374374
});
375-
const quotes = chunk.columns.map(col => col.kind === 'String' ? '"' : '');
376375
chunk.tuples.forEach((tuple) => {
377-
out.write(tuple.map((v, i) => {
378-
return `${quotes[i]}${v}${quotes[i]}`;
379-
}).join(',') + '\n');
376+
out.write(tuple.map((v, i) =>
377+
chunk.columns[i].kind === 'String'
378+
? `"${typeof v === 'string' ? v.replaceAll('"', '""') : v}"`
379+
: v
380+
).join(',') + '\n');
380381
});
381382
nextOffset = chunk.next;
382383
} while (nextOffset && !stopDecoding);

extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,40 @@ describe('run-queries', () => {
9191
});
9292
});
9393

94+
it('should export csv results with characters that need to be escaped', async () => {
95+
const csvLocation = path.join(tmpDir.name, 'test.csv');
96+
const qs = createMockQueryServerClient(
97+
createMockCliServer({
98+
bqrsInfo: [{ 'result-sets': [{ name: SELECT_QUERY_NAME }, { name: 'hucairz' }] }],
99+
bqrsDecode: [{
100+
columns: [{ kind: 'NotString' }, { kind: 'String' }],
101+
// We only escape string columns. In practice, we will only see quotes in strings, but
102+
// it is a good test anyway.
103+
tuples: [
104+
['"a"', '"b"'],
105+
['c,xxx', 'd,yyy'],
106+
['aaa " bbb', 'ccc " ddd'],
107+
[true, false],
108+
[123, 456],
109+
[123.98, 456.99],
110+
],
111+
}]
112+
})
113+
);
114+
const info = createMockQueryInfo();
115+
const promise = info.exportCsvResults(qs, csvLocation);
116+
117+
const result = await promise;
118+
expect(result).to.eq(true);
119+
120+
const csv = fs.readFileSync(csvLocation, 'utf8');
121+
expect(csv).to.eq('"a","""b"""\nc,xxx,"d,yyy"\naaa " bbb,"ccc "" ddd"\ntrue,"false"\n123,"456"\n123.98,"456.99"\n');
122+
123+
// now verify that we are using the expected result set
124+
expect((qs.cliServer.bqrsDecode as sinon.SinonStub).callCount).to.eq(1);
125+
expect((qs.cliServer.bqrsDecode as sinon.SinonStub).getCall(0).args[1]).to.eq(SELECT_QUERY_NAME);
126+
});
127+
94128
it('should handle csv exports for a query with no result sets', async () => {
95129
const csvLocation = path.join(tmpDir.name, 'test.csv');
96130
const qs = createMockQueryServerClient(

0 commit comments

Comments
 (0)