fix(editor): optimize large PL/SQL source rendering

Closes #5171
This commit is contained in:
t8y2 committed 2026-08-03 14:25:45 +08:00
1 parent 16920188ee
commit 043ecfeafa
7 files changed
+117 -41

No files matched your search

+46 -26
View File
@@ -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;