Skip to content

Commit 95bffdc

Browse files
authored
Merge pull request #153 from codefori/feature/runner_parameter_ux
Host parameters treated like snippets
2 parents f998873 + 5765160 commit 95bffdc

File tree

5 files changed

+190
-76
lines changed

5 files changed

+190
-76
lines changed

src/language/sql/document.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import Statement from "./statement";
22
import SQLTokeniser from "./tokens";
3-
import { Definition, IRange, StatementGroup, StatementType, StatementTypeWord, Token } from "./types";
3+
import { Definition, IRange, ParsedEmbeddedStatement, StatementGroup, StatementType, StatementTypeWord, Token } from "./types";
44

55
export default class Document {
6-
private content: string;
6+
content: string;
77
statements: Statement[];
88

99
constructor(content: string) {
@@ -167,23 +167,27 @@ export default class Document {
167167
})
168168
}
169169

170-
removeEmbeddedAreas(statement: Statement) {
171-
const ranges = statement.getEmbeddedStatementAreas();
170+
removeEmbeddedAreas(statement: Statement, snippetString?: boolean): ParsedEmbeddedStatement {
171+
const areas = statement.getEmbeddedStatementAreas();
172172

173+
const totalParameters = areas.filter(a => a.type === `marker`).length;
173174
let newContent = this.content.substring(statement.range.start, statement.range.end);
174175
let parameterCount = 0;
175176

176177
const startRange = statement.range.start;
177178

178179
// We do it in reverse so the substring ranges doesn't change
179-
for (let x = ranges.length-1; x >= 0; x--) {
180-
const area = ranges[x];
180+
for (let x = areas.length-1; x >= 0; x--) {
181+
const area = areas[x];
181182

182183
let start = area.range.start - startRange, end = area.range.end - startRange;
183184

184185
switch (area.type) {
185186
case `marker`:
186-
newContent = newContent.substring(0, start) + `?` + newContent.substring(end);
187+
const markerContent = newContent.substring(start, end);
188+
189+
newContent = newContent.substring(0, start) + (snippetString ? `\${${totalParameters-parameterCount}:${markerContent}}` : `?`) + newContent.substring(end) + (snippetString ? `$0` : ``);
190+
187191
parameterCount++;
188192
break;
189193

@@ -194,6 +198,7 @@ export default class Document {
194198
}
195199

196200
return {
201+
changed: areas.length > 0,
197202
content: newContent,
198203
parameterCount
199204
};

src/language/sql/statement.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,19 +460,33 @@ export default class Statement {
460460

461461
/**
462462
* Gets areas of statement that are likely from embedded statements
463+
* EXEC SQL
463464
* INTO area
464465
* Host variables
465-
* EXEC SQL
466+
* DECLARE .. CURSOR FOR
466467
*/
467468
getEmbeddedStatementAreas() {
468469
let ranges: {type: "remove"|"marker", range: IRange}[] = [];
469470
let intoClause: Token|undefined;
471+
let declareStmt: Token|undefined;
470472

471473
for (let i = 0; i < this.tokens.length; i++) {
472474
const currentToken = this.tokens[i];
473475

474476
switch (currentToken.type) {
477+
case `statementType`:
478+
if (declareStmt) continue;
479+
480+
// If we're in a DECLARE, it's likely a cursor definition
481+
if (currentToken.value.toLowerCase() === `declare`) {
482+
declareStmt = currentToken;
483+
}
484+
break;
485+
475486
case `clause`:
487+
if (declareStmt) continue;
488+
489+
// We need to remove the INTO clause completely.
476490
if (currentToken.value.toLowerCase() === `into`) {
477491
intoClause = currentToken;
478492
} else if (intoClause) {
@@ -492,6 +506,7 @@ export default class Statement {
492506

493507
case `questionmark`:
494508
if (intoClause) continue;
509+
if (declareStmt) continue;
495510

496511
ranges.push({
497512
type: `marker`,
@@ -501,11 +516,16 @@ export default class Statement {
501516

502517
case `colon`:
503518
if (intoClause) continue;
519+
if (declareStmt) continue;
504520

505521
let nextMustBe: "word"|"dot" = `word`;
506522
let followingTokenI = i+1;
507523
let endToken: Token;
508524

525+
// Handles when we have a host variable
526+
// This logic supports qualified host variables
527+
// i.e. :myvar or :mystruct.subf
528+
509529
let followingToken = this.tokens[followingTokenI];
510530
while (followingToken && followingToken.type === nextMustBe) {
511531
switch (followingToken.type) {
@@ -535,6 +555,7 @@ export default class Statement {
535555

536556
default:
537557
if (i === 0 && tokenIs(currentToken, `word`, `EXEC`)) {
558+
// We check and remove the starting `EXEC SQL`
538559
if (tokenIs(this.tokens[i+1], `word`, `SQL`)) {
539560
ranges.push({
540561
type: `remove`,
@@ -544,6 +565,19 @@ export default class Statement {
544565
}
545566
});
546567
}
568+
} else
569+
if (declareStmt && tokenIs(currentToken, `keyword`, `FOR`)) {
570+
// If we're a DECLARE, and we found the FOR keyword, the next
571+
// set of tokens should be the select.
572+
ranges.push({
573+
type: `remove`,
574+
range: {
575+
start: declareStmt.range.start,
576+
end: currentToken.range.end
577+
}
578+
});
579+
580+
declareStmt = undefined;
547581
}
548582
break;
549583
}

src/language/sql/tests/statements.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,4 +1267,51 @@ describe(`Parameter statement tests`, () => {
12671267
const result = document.removeEmbeddedAreas(statement);
12681268
expect(result.content).toBe(`select x,y from sample where x = ?`);
12691269
});
1270+
1271+
test(`Exec with basic DECLARE`, () => {
1272+
const lines = [
1273+
`EXEC SQL DECLARE empCur CURSOR FOR`,
1274+
` SELECT EMPNO, FIRSTNME, LASTNAME, JOB`,
1275+
` FROM EMPLOYEE`,
1276+
` WHERE WORKDEPT = :DEPTNO`,
1277+
].join(`\n`);
1278+
1279+
const document = new Document(lines);
1280+
const statements = document.statements;
1281+
expect(statements.length).toBe(1);
1282+
1283+
const statement = statements[0];
1284+
1285+
const result = document.removeEmbeddedAreas(statement);
1286+
expect(result.parameterCount).toBe(1);
1287+
expect(result.content).toBe([
1288+
` SELECT EMPNO, FIRSTNME, LASTNAME, JOB`,
1289+
` FROM EMPLOYEE`,
1290+
` WHERE WORKDEPT = ?`
1291+
].join(`\n`));
1292+
});
1293+
1294+
test(`Exec with basic DECLARE`, () => {
1295+
const lines = [
1296+
`EXEC SQL`,
1297+
`DECLARE cursor-name SCROLL CURSOR FOR`,
1298+
`SELECT column-1, column-2`,
1299+
` FROM table-name`,
1300+
` WHERE column-1 = expression`,
1301+
].join(`\n`);
1302+
1303+
const document = new Document(lines);
1304+
const statements = document.statements;
1305+
expect(statements.length).toBe(1);
1306+
1307+
const statement = statements[0];
1308+
1309+
const result = document.removeEmbeddedAreas(statement);
1310+
expect(result.parameterCount).toBe(0);
1311+
expect(result.content).toBe([
1312+
`SELECT column-1, column-2`,
1313+
` FROM table-name`,
1314+
` WHERE column-1 = expression`,
1315+
].join(`\n`));
1316+
});
12701317
});

src/language/sql/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,10 @@ export interface StatementGroup {
9797
export interface Definition extends ObjectRef {
9898
range: IRange;
9999
children: Definition[];
100+
}
101+
102+
export interface ParsedEmbeddedStatement {
103+
changed: boolean;
104+
content: string;
105+
parameterCount: number;
100106
}

0 commit comments

Comments
 (0)