fix(saphana): keep DO blocks together

This commit is contained in:
t8y2 committed 2026-07-07 13:23:53 +08:00
1 parent f41c7dd6a0
commit 6e7f0578b6
3 files changed
+223 -3

No files matched your search

+56 -3
View File
@@ -80,6 +80,7 @@ const DATABASE_SOFT_STATEMENT_KEYWORDS: Partial<Record<DatabaseType, readonly st
duckdb: ["ATTACH", "DETACH", "EXPORT", "IMPORT", "INSTALL", "LOAD"],
clickhouse: ["ATTACH", "CHECK", "DETACH", "EXCHANGE", "KILL", "OPTIMIZE", "SYSTEM"],
sqlserver: ["BACKUP", "DBCC", "DENY", "RESTORE"],
saphana: ["DO"],
oracle: ["FLASHBACK", "LOCK", "PURGE"],
dameng: ["FLASHBACK", "LOCK", "PURGE"],
gaussdb: ["DO", "LOCK"],
@@ -111,6 +112,7 @@ const MYSQL_CONTROL_BLOCK_SUFFIXES = new Set(["IF", "LOOP", "CASE", "REPEAT", "W
const ORACLE_PL_SQL_BLOCK_STARTERS = new Set(["DECLARE", "BEGIN"]);
const ORACLE_PL_SQL_CREATE_OBJECT_TYPES = new Set(["FUNCTION", "PROCEDURE", "TRIGGER", "PACKAGE", "PACKAGE BODY", "TYPE", "TYPE BODY"]);
const ORACLE_PL_SQL_TERMINATORS = new Set(["IF", "LOOP", "CASE"]);
const SAP_HANA_SCRIPT_BLOCK_TERMINATORS = new Set(["IF", "FOR", "WHILE"]);
/**
* Parse the SQL document into top-level statement ranges delimited by `;`.
@@ -334,10 +336,16 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
// Internal semicolons remain part of the routine body.
flush();
} else {
const isOraclePlSql = isOracleLikeDatabase(databaseType) && statementStart !== -1 && startsWithOraclePlSqlBlock(sql.slice(statementStart, i));
if (isOraclePlSql) {
const statementSoFar = statementStart === -1 ? "" : sql.slice(statementStart, i);
const isOraclePlSql = isOracleLikeDatabase(databaseType) && statementStart !== -1 && startsWithOraclePlSqlBlock(statementSoFar);
const isSapHanaScriptBlock = isSapHanaScriptBlockDatabase(databaseType) && statementStart !== -1 && startsWithSapHanaScriptBlock(statementSoFar);
if (isOraclePlSql || isSapHanaScriptBlock) {
markContent(i);
if (!oraclePlSqlBlockIsComplete(sql.slice(statementStart, i + 1))) {
if (isOraclePlSql && !oraclePlSqlBlockIsComplete(sql.slice(statementStart, i + 1))) {
i += 1;
continue;
}
if (isSapHanaScriptBlock && !sapHanaScriptBlockIsComplete(sql.slice(statementStart, i + 1))) {
i += 1;
continue;
}
@@ -459,6 +467,7 @@ function rangeForCursorInSoftRanges(sql: string, ranges: RawStatement[], pos: nu
function splitStatementRangeAtSoftStarts(sql: string, statement: RawStatement, databaseType?: DatabaseType): RawStatement[] {
if (isOraclePlSqlStatement(statement.sql, databaseType)) return [statement];
if (isSapHanaScriptBlockStatement(statement.sql, databaseType)) return [statement];
const lineStarts = topLevelSoftStatementLineStarts(sql, statement, databaseType);
if (lineStarts.length <= 1) return [statement];
@@ -1082,6 +1091,14 @@ export function isOraclePlSqlStatement(sql: string, databaseType?: DatabaseType)
return isOracleLikeDatabase(databaseType) && startsWithOraclePlSqlBlock(sql);
}
function isSapHanaScriptBlockDatabase(databaseType?: DatabaseType): boolean {
return databaseType === "saphana";
}
function isSapHanaScriptBlockStatement(sql: string, databaseType?: DatabaseType): boolean {
return isSapHanaScriptBlockDatabase(databaseType) && startsWithSapHanaScriptBlock(sql);
}
function isMysqlRoutineBlockDatabase(databaseType?: DatabaseType): boolean {
return !!databaseType && MYSQL_ROUTINE_BLOCK_DATABASES.has(databaseType);
}
@@ -1255,6 +1272,42 @@ function startsWithOraclePlSqlBlock(sql: string): boolean {
return ORACLE_PL_SQL_CREATE_OBJECT_TYPES.has(words[index] ?? "");
}
function startsWithSapHanaScriptBlock(sql: string): boolean {
return oraclePlSqlWords(sql)[0] === "DO";
}
function sapHanaScriptBlockIsComplete(sql: string): boolean {
if (!startsWithSapHanaScriptBlock(sql)) return false;
const tokens = oraclePlSqlTokens(sql);
const stack: string[] = [];
let sawBegin = false;
for (let index = 0; index < tokens.length; index += 1) {
const token = tokens[index];
if (token.kind !== "word") continue;
if (token.value === "BEGIN") {
if (previousWordToken(tokens, index) === "END") continue;
stack.push("BLOCK");
sawBegin = true;
continue;
}
if (token.value === "IF" || token.value === "FOR" || token.value === "WHILE" || token.value === "CASE") {
if (previousWordToken(tokens, index) !== "END") stack.push(token.value);
continue;
}
if (token.value === "END") {
const next = nextWordToken(tokens, index);
const top = stack[stack.length - 1];
const target = SAP_HANA_SCRIPT_BLOCK_TERMINATORS.has(next ?? "") ? next : top === "CASE" ? "CASE" : "BLOCK";
if (top === target) stack.pop();
}
}
return sawBegin && stack.length === 0 && tokens[tokens.length - 1]?.kind === "semicolon";
}
function oraclePlSqlBlockIsComplete(sql: string): boolean {
const tokens = oraclePlSqlTokens(sql);
if (!startsWithOraclePlSqlBlock(sql)) return false;