Skip to content

Commit 162f302

Browse files
authored
Merge pull request #360 from codefori/fix/lateral_issues
Fix lateral use and improve performance with caching
2 parents 959df0b + 47f1c0b commit 162f302

File tree

3 files changed

+89
-72
lines changed

3 files changed

+89
-72
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/language/sql/statement.ts

Lines changed: 63 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Tokenizer from "sql-formatter/lib/src/lexer/Tokenizer";
12
import SQLTokeniser, { NameTypes } from "./tokens";
23
import { CTEReference, CallableReference, ClauseType, ClauseTypeWord, IRange, ObjectRef, QualifiedObject, StatementType, StatementTypeWord, Token } from "./types";
34

@@ -8,6 +9,15 @@ const tokenIs = (token: Token|undefined, type: string, value?: string) => {
89
export default class Statement {
910
public type: StatementType = StatementType.Unknown;
1011
private label: string|undefined;
12+
private _blockTokens: Token[]|undefined;
13+
14+
private get blockTokens() {
15+
if (!this._blockTokens) {
16+
this._blockTokens = SQLTokeniser.createBlocks(this.tokens.slice(0));
17+
}
18+
19+
return this._blockTokens;
20+
}
1121

1222
constructor(public tokens: Token[], public range: IRange) {
1323
this.tokens = this.tokens.filter(newToken => newToken.type !== `newline`);
@@ -101,62 +111,34 @@ export default class Statement {
101111
return currentClause;
102112
}
103113

104-
getBlockRangeAt(offset: number) {
105-
let start = -1;
106-
let end = -1;
107-
108-
// Get the current token for the provided offset
109-
let i = this.tokens.findIndex((token, i) => (offset >= token.range.start && offset <= token.range.end) || (offset > token.range.end && this.tokens[i+1] && offset < this.tokens[i+1].range.start));
110-
111-
let depth = 0;
112-
113-
if (tokenIs(this.tokens[i], `closebracket`)) {
114-
i--;
115-
}
116-
117-
if (tokenIs(this.tokens[i], `openbracket`)) {
118-
start = i+1;
119-
i++;
120-
} else {
121-
for (let x = i; x >= 0; x--) {
122-
if (tokenIs(this.tokens[x], `openbracket`)) {
123-
if (depth === 0) {
124-
start = x+1;
125-
break;
114+
getBlockRangeAt(offset: number): IRange|undefined {
115+
const blockContainsOffset = (cOffset: number, block: Token[]): IRange|undefined => {
116+
const tokenInOffset = block.find(token => cOffset >= token.range.start && cOffset <= token.range.end);
117+
if (tokenInOffset) {
118+
if (tokenInOffset.type === `block`) {
119+
if (tokenInOffset.block!.length > 0) {
120+
return blockContainsOffset(cOffset, tokenInOffset.block!);
126121
} else {
127-
depth--;
122+
const rawEnd = this.tokens.findIndex(token => token.range.end === tokenInOffset.range.end);
123+
return {
124+
start: rawEnd,
125+
end: rawEnd
126+
}
128127
}
129-
} else
130-
if (tokenIs(this.tokens[x], `closebracket`)) {
131-
depth++;
132-
}
133-
}
134-
}
135-
136-
depth = 0;
137-
138-
for (let x = i; x <= this.tokens.length; x++) {
139-
if (tokenIs(this.tokens[x], `openbracket`)) {
140-
depth++;
141-
} else
142-
if (tokenIs(this.tokens[x], `closebracket`)) {
143-
if (depth === 0) {
144-
end = x;
145-
break;
146128
} else {
147-
depth--;
129+
const rawStart = this.tokens.findIndex(token => token.range.start === block[0].range.start);
130+
const rawEnd = this.tokens.findIndex(token => token.range.end === block[block.length-1].range.end);
131+
return {
132+
start: rawStart,
133+
end: rawEnd+1
134+
};
148135
}
149136
}
150-
}
151137

152-
if (start === -1 || end === -1) {
153138
return undefined;
154-
} else {
155-
return {
156-
start,
157-
end
158-
}
159139
}
140+
141+
return blockContainsOffset(offset, this.blockTokens);
160142
}
161143

162144
getCallableDetail(offset: number, withBlocks = false): CallableReference {
@@ -176,13 +158,22 @@ export default class Statement {
176158
}
177159

178160
getBlockAt(offset: number): Token[] {
179-
const range = this.getBlockRangeAt(offset);
161+
const expandBlock = (tokens: Token[]): Token[] => {
162+
const block = tokens.filter(token => token.type === `block`);
163+
if (block.length > 0) {
164+
return block.reduce((acc, token) => acc.concat(expandBlock(token.block!)), tokens);
165+
}
180166

181-
if (range) {
182-
return this.tokens.slice(range.start, range.end)
183-
} else {
184-
return []
167+
return tokens;
168+
}
169+
170+
let blockRange = this.getBlockRangeAt(offset);
171+
172+
if (blockRange) {
173+
return expandBlock(this.tokens.slice(blockRange.start, blockRange.end));
185174
}
175+
176+
return [];
186177
}
187178

188179
getReferenceByOffset(offset: number) {
@@ -227,7 +218,7 @@ export default class Statement {
227218
getCTEReferences(): CTEReference[] {
228219
if (this.type !== StatementType.With) return [];
229220

230-
const withBlocks = SQLTokeniser.createBlocks(this.tokens.slice(0));
221+
const withBlocks = this.blockTokens;
231222

232223
let cteList: CTEReference[] = [];
233224

@@ -324,11 +315,16 @@ export default class Statement {
324315

325316
}
326317

318+
private static readonly BANNED_NAMES = [`VALUES`];
327319
getObjectReferences(): ObjectRef[] {
328320
let list: ObjectRef[] = [];
329321

330322
const doAdd = (ref?: ObjectRef) => {
331-
if (ref) list.push(ref);
323+
if (ref) {
324+
if (ref.object.name && !Statement.BANNED_NAMES.includes(ref.object.name.toUpperCase())) {
325+
list.push(ref);
326+
}
327+
}
332328
}
333329

334330
const basicQueryFinder = (startIndex: number): void => {
@@ -365,6 +361,14 @@ export default class Statement {
365361
i += 3; //For the brackets
366362
}
367363
}
364+
} else if (currentClause === `from` && tokenIs(this.tokens[i], `function`)) {
365+
const sqlObj = this.getRefAtToken(i, {includeParameters: true});
366+
if (sqlObj) {
367+
i += sqlObj.tokens.length;
368+
if (sqlObj.isUDTF || sqlObj.fromLateral) {
369+
i += 3; //For the brackets
370+
}
371+
}
368372
}
369373
}
370374
}
@@ -538,18 +542,17 @@ export default class Statement {
538542
return list;
539543
}
540544

541-
private getRefAtToken(i: number, options: {withSystemName?: boolean} = {}): ObjectRef|undefined {
545+
private getRefAtToken(i: number, options: {withSystemName?: boolean, includeParameters?: boolean} = {}): ObjectRef|undefined {
542546
let sqlObj: ObjectRef;
543547

544548
let nextIndex = i;
545549
let nextToken = this.tokens[i];
546550

547551
let endIndex = i;
548552

549-
const isUDTF = tokenIs(nextToken, `function`, `TABLE`);
550-
const isLateral = tokenIs(nextToken, `function`, `LATERAL`);
553+
const isSubSelect = tokenIs(nextToken, `function`, `TABLE`) || tokenIs(nextToken, `function`, `LATERAL`) || (options.includeParameters && tokenIs(nextToken, `function`));
551554

552-
if (isUDTF) {
555+
if (isSubSelect) {
553556
sqlObj = this.getRefAtToken(i+2);
554557
if (sqlObj) {
555558
sqlObj.isUDTF = true;
@@ -563,14 +566,6 @@ export default class Statement {
563566
nextIndex = -1;
564567
nextToken = undefined;
565568
}
566-
} else if (isLateral) {
567-
const blockTokens = this.getBlockAt(nextToken.range.end+1);
568-
const newStatement = new Statement(blockTokens, {start: nextToken.range.start, end: blockTokens[blockTokens.length-1].range.end});
569-
[sqlObj] = newStatement.getObjectReferences();
570-
571-
sqlObj.fromLateral = true;
572-
nextIndex = i + 2 + blockTokens.length;
573-
nextToken = this.tokens[nextIndex];
574569

575570
} else {
576571
if (nextToken && NameTypes.includes(nextToken.type)) {
@@ -618,7 +613,7 @@ export default class Statement {
618613
}
619614
}
620615

621-
if (!isUDTF) {
616+
if (!isSubSelect && !sqlObj.isUDTF) {
622617
sqlObj.tokens = this.tokens.slice(i, endIndex+1);
623618
}
624619

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,29 @@ parserScenarios(`Object references`, ({newDoc}) => {
11051105

11061106
expect(refs[2].object.name).toBe(`object_statistics`);
11071107
expect(refs[2].object.schema).toBe(`qsys2`);
1108-
expect(refs[2].alias).toBe(`z`);
1108+
expect(refs[2].alias).toBe(undefined);
1109+
});
1110+
1111+
test('SELECT FROM LATERAL', () => {
1112+
const lines = [
1113+
`SELECT id, id_phone, t.phone_number`,
1114+
` FROM testlateral AS s,`,
1115+
` LATERAL(VALUES (1, S.phone1),`,
1116+
` (2, S.phone2),`,
1117+
` (3, S.phone3)) AS T(id_phone, phone_number)`,
1118+
].join(`\n`);
1119+
1120+
const document = new Document(lines);
1121+
1122+
expect(document.statements.length).toBe(1);
1123+
1124+
const statement = document.statements[0];
1125+
1126+
expect(statement.type).toBe(StatementType.Select);
1127+
1128+
const refs = statement.getObjectReferences();
1129+
console.log(refs);
1130+
expect(refs.length).toBe(1);
11091131
});
11101132

11111133
test(`Multiple UDTFs`, () => {
@@ -1407,7 +1429,7 @@ parserScenarios(`PL body tests`, ({newDoc}) => {
14071429

14081430
const refs = statement.getObjectReferences();
14091431
const ctes = statement.getCTEReferences();
1410-
1432+
14111433
expect(refs.length).toBe(10);
14121434
expect(refs[0].object.name).toBe(`shipments`);
14131435
expect(refs[0].alias).toBe(`s`);

0 commit comments

Comments
 (0)