7 files changed
+117
-41
No files matched your search
@@ -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