7 files changed
+117
-41
No files matched your search
@@ -4,6 +4,10 @@ export function currentStatementFrameRangeTo(nextChar: string, range: SqlTextRan
|
||||
return nextChar === ";" ? range.to + 1 : range.to;
|
||||
}
|
||||
|
||||
export function shouldRebuildCurrentStatementFrame(update: { docChanged: boolean; selectionSet: boolean; configurationChanged: boolean }): boolean {
|
||||
return update.docChanged || update.selectionSet || update.configurationChanged;
|
||||
}
|
||||
|
||||
export function visualSqlColumns(text: string): number {
|
||||
let columns = 0;
|
||||
for (const ch of text) {
|
||||
|
||||
@@ -33,18 +33,21 @@ function significantTokens(tokens: readonly SqlSemanticToken[]): SqlSemanticToke
|
||||
return tokens.filter((item) => item.kind !== "comment");
|
||||
}
|
||||
|
||||
function statementSpans(sql: string, tokens: readonly SqlSemanticToken[]): SqlSemanticSpan[] {
|
||||
const spans: SqlSemanticSpan[] = [];
|
||||
function statementTokenGroups(sql: string, tokens: readonly SqlSemanticToken[]): Array<{ span: SqlSemanticSpan; tokens: SqlSemanticToken[] }> {
|
||||
const groups: Array<{ span: SqlSemanticSpan; tokens: SqlSemanticToken[] }> = [];
|
||||
let start = 0;
|
||||
for (const item of tokens) {
|
||||
let tokenStart = 0;
|
||||
for (let index = 0; index < tokens.length; index += 1) {
|
||||
const item = tokens[index]!;
|
||||
if (item.kind !== "punctuation" || item.text !== ";" || item.depth !== 0) continue;
|
||||
const span = trimSpan(sql, start, item.span.start);
|
||||
if (span.end > span.start) spans.push(span);
|
||||
if (span.end > span.start) groups.push({ span, tokens: significantTokens(tokens.slice(tokenStart, index)) });
|
||||
start = item.span.end;
|
||||
tokenStart = index + 1;
|
||||
}
|
||||
const last = trimSpan(sql, start, sql.length);
|
||||
if (last.end > last.start) spans.push(last);
|
||||
return spans;
|
||||
if (last.end > last.start) groups.push({ span: last, tokens: significantTokens(tokens.slice(tokenStart)) });
|
||||
return groups;
|
||||
}
|
||||
|
||||
function trimSpan(sql: string, start: number, end: number): SqlSemanticSpan {
|
||||
@@ -444,10 +447,8 @@ export function parseInsertValuesClausesInRanges(sql: string, ranges: readonly T
|
||||
export function parseInsertValuesClauses(sql: string): InsertValuesClause[] {
|
||||
if (!sql.trim()) return [];
|
||||
const allTokens = tokenizeSqlSemantic(sql);
|
||||
const spans = statementSpans(sql, allTokens);
|
||||
const clauses: InsertValuesClause[] = [];
|
||||
for (const span of spans) {
|
||||
const tokens = significantTokens(tokensInSpan(allTokens, span));
|
||||
for (const { span, tokens } of statementTokenGroups(sql, allTokens)) {
|
||||
const clause = parseInsertClause(tokens, span);
|
||||
if (clause) clauses.push(clause);
|
||||
}
|
||||
|
||||
@@ -300,6 +300,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
let state: QuoteState = "none";
|
||||
let dollarTag = "";
|
||||
let postgresDollarQuotedRoutine = false;
|
||||
let oraclePlSqlStatementEnd: number | null | undefined;
|
||||
let i = 0;
|
||||
|
||||
const isWhitespace = (ch: string) => ch === " " || ch === "\t" || ch === "\r" || ch === "\n";
|
||||
@@ -317,6 +318,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
statementEnd = -1;
|
||||
pendingHintStart = -1;
|
||||
postgresDollarQuotedRoutine = false;
|
||||
oraclePlSqlStatementEnd = undefined;
|
||||
return;
|
||||
}
|
||||
const trimmedTo = trimRangeEnd(sql, statementStart, to);
|
||||
@@ -327,6 +329,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
statementEnd = -1;
|
||||
pendingHintStart = -1;
|
||||
postgresDollarQuotedRoutine = false;
|
||||
oraclePlSqlStatementEnd = undefined;
|
||||
};
|
||||
|
||||
while (i < len) {
|
||||
@@ -512,12 +515,16 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
// Internal semicolons remain part of the routine body.
|
||||
flush();
|
||||
} else {
|
||||
const statementSoFar = statementStart === -1 ? "" : sql.slice(statementStart, i);
|
||||
const isOraclePlSql = isOracleLikeDatabase(databaseType) && !postgresDollarQuotedRoutine && statementStart !== -1 && startsWithOraclePlSqlBlock(statementSoFar);
|
||||
const isSapHanaScriptBlock = isSapHanaScriptBlockDatabase(databaseType) && statementStart !== -1 && startsWithSapHanaScriptBlock(statementSoFar);
|
||||
if (oraclePlSqlStatementEnd === undefined && isOracleLikeDatabase(databaseType) && !postgresDollarQuotedRoutine && statementStart !== -1) {
|
||||
const statementSoFar = sql.slice(statementStart, i);
|
||||
oraclePlSqlStatementEnd = startsWithOraclePlSqlBlock(statementSoFar) ? statementStart + (oraclePlSqlBlockEnd(sql.slice(statementStart)) ?? sql.length - statementStart) : null;
|
||||
}
|
||||
const resolvedOraclePlSqlStatementEnd = oraclePlSqlStatementEnd;
|
||||
const isOraclePlSql = typeof resolvedOraclePlSqlStatementEnd === "number";
|
||||
const isSapHanaScriptBlock = isSapHanaScriptBlockDatabase(databaseType) && statementStart !== -1 && startsWithSapHanaScriptBlock(sql.slice(statementStart, i));
|
||||
if (isOraclePlSql || isSapHanaScriptBlock) {
|
||||
markContent(i);
|
||||
if (isOraclePlSql && !oraclePlSqlBlockIsComplete(sql.slice(statementStart, i + 1))) {
|
||||
if (isOraclePlSql && i + 1 < resolvedOraclePlSqlStatementEnd) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
@@ -1638,7 +1645,10 @@ function mysqlRoutineTokens(sql: string, parameterOptions?: SqlParameterOptions)
|
||||
}
|
||||
|
||||
function startsWithOraclePlSqlBlock(sql: string): boolean {
|
||||
const words = oraclePlSqlWords(sql);
|
||||
return startsWithOraclePlSqlBlockWords(oraclePlSqlWords(sql));
|
||||
}
|
||||
|
||||
function startsWithOraclePlSqlBlockWords(words: readonly string[]): boolean {
|
||||
const first = words[0];
|
||||
if (!first) return false;
|
||||
if (ORACLE_PL_SQL_BLOCK_STARTERS.has(first)) return first !== "BEGIN" || words[1] !== "TRANSACTION";
|
||||
@@ -1713,17 +1723,23 @@ function sapHanaScriptBlockIsComplete(sql: string): boolean {
|
||||
return sawBegin && stack.length === 0 && tokens[tokens.length - 1]?.kind === "semicolon";
|
||||
}
|
||||
|
||||
function oraclePlSqlBlockIsComplete(sql: string): boolean {
|
||||
function oraclePlSqlBlockEnd(sql: string): number | null {
|
||||
const tokens = oraclePlSqlTokens(sql);
|
||||
if (!startsWithOraclePlSqlBlock(sql)) return false;
|
||||
const words = oraclePlSqlWordValues(tokens);
|
||||
if (!startsWithOraclePlSqlBlockWords(words)) return null;
|
||||
|
||||
// Package/type specifications have no BEGIN — only declarations closed by
|
||||
// END [name];. Bodies also own an outer END beyond nested routine END pairs.
|
||||
const objectKind = oraclePlSqlCreateObjectKind(sql);
|
||||
const objectKind = oraclePlSqlCreateObjectKind(words);
|
||||
const stack: string[] = objectKind === "body" ? ["OBJECT_BODY"] : objectKind === "spec" ? ["OBJECT_SPEC"] : [];
|
||||
let sawBegin = false;
|
||||
for (let index = 0; index < tokens.length; index += 1) {
|
||||
const token = tokens[index];
|
||||
if (token.kind === "semicolon") {
|
||||
const complete = objectKind === "spec" ? stack.length === 0 : sawBegin && stack.length === 0;
|
||||
if (complete) return token.to;
|
||||
continue;
|
||||
}
|
||||
if (token.kind !== "word") continue;
|
||||
|
||||
if (token.value === "DECLARE") {
|
||||
@@ -1769,12 +1785,7 @@ function oraclePlSqlBlockIsComplete(sql: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
const endsWithSemicolon = tokens[tokens.length - 1]?.kind === "semicolon";
|
||||
if (objectKind === "spec") {
|
||||
// Specs complete on outer END [name]; without requiring a BEGIN block.
|
||||
return stack.length === 0 && endsWithSemicolon;
|
||||
}
|
||||
return sawBegin && stack.length === 0 && endsWithSemicolon;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1783,8 +1794,7 @@ function oraclePlSqlBlockIsComplete(sql: string): boolean {
|
||||
* - spec: PACKAGE specification only (declarations + END, no BEGIN)
|
||||
* - null: ordinary SQL / other objects (including plain CREATE TYPE ... AS OBJECT)
|
||||
*/
|
||||
function oraclePlSqlCreateObjectKind(sql: string): "body" | "spec" | null {
|
||||
const words = oraclePlSqlWords(sql);
|
||||
function oraclePlSqlCreateObjectKind(words: readonly string[]): "body" | "spec" | null {
|
||||
if (words[0] !== "CREATE") return null;
|
||||
|
||||
const index = skipOraclePlSqlCreateModifiers(words, 1);
|
||||
@@ -1799,13 +1809,22 @@ function oraclePlSqlCreateObjectKind(sql: string): "body" | "spec" | null {
|
||||
}
|
||||
|
||||
function oraclePlSqlWords(sql: string): string[] {
|
||||
return oraclePlSqlTokens(sql)
|
||||
.filter((token): token is { kind: "word"; value: string } => token.kind === "word")
|
||||
.map((token) => token.value);
|
||||
return oraclePlSqlWordValues(oraclePlSqlTokens(sql));
|
||||
}
|
||||
|
||||
function oraclePlSqlTokens(sql: string): Array<{ kind: "word" | "semicolon"; value: string }> {
|
||||
const tokens: Array<{ kind: "word" | "semicolon"; value: string }> = [];
|
||||
interface OraclePlSqlToken {
|
||||
kind: "word" | "semicolon";
|
||||
value: string;
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
function oraclePlSqlWordValues(tokens: readonly OraclePlSqlToken[]): string[] {
|
||||
return tokens.filter((token) => token.kind === "word").map((token) => token.value);
|
||||
}
|
||||
|
||||
function oraclePlSqlTokens(sql: string): OraclePlSqlToken[] {
|
||||
const tokens: OraclePlSqlToken[] = [];
|
||||
let state: QuoteState | "lineComment" | "blockComment" = "none";
|
||||
let i = 0;
|
||||
|
||||
@@ -1867,15 +1886,16 @@ function oraclePlSqlTokens(sql: string): Array<{ kind: "word" | "semicolon"; val
|
||||
continue;
|
||||
}
|
||||
if (ch === ";") {
|
||||
tokens.push({ kind: "semicolon", value: ";" });
|
||||
tokens.push({ kind: "semicolon", value: ";", from: i, to: i + 1 });
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const word = /^[A-Za-z_][\w$]*/.exec(sql.slice(i))?.[0];
|
||||
if (word) {
|
||||
tokens.push({ kind: "word", value: word.toUpperCase() });
|
||||
i += word.length;
|
||||
if (/[A-Za-z_]/.test(ch)) {
|
||||
const from = i;
|
||||
i += 1;
|
||||
while (i < sql.length && /[A-Za-z0-9_$]/.test(sql[i] ?? "")) i += 1;
|
||||
tokens.push({ kind: "word", value: sql.slice(from, i).toUpperCase(), from, to: i });
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
|
||||
Reference in new issue
Block a user