fix(oracle): format byte and code-point function variants as functions

Closes #11251
This commit is contained in:
Luis Araya
2026-10-09 00:00:11 +08:00
committed by GitHub
parent 89ed17d645
commit c243261125
2 changed files with 41 additions and 1 deletions
+23 -1
View File
@@ -28,6 +28,28 @@ function resolveClickHouseIdentifierSafeDialect(sqlFormatter: SqlFormatterModule
return clickHouseIdentifierSafeDialect;
}
// sql-formatter's plsql dialect only registers a minimal subset of character
// functions (e.g. SUBSTR, INSTR, LENGTH) and omits Oracle's byte/multibyte/code-point
// variants. Register them as reserved function names so functionCase normalizes them.
const ORACLE_EXTENDED_FUNCTION_NAMES = ["SUBSTRB", "SUBSTR2", "SUBSTR4", "SUBSTRC", "INSTRB", "INSTR2", "INSTR4", "INSTRC", "LENGTHB", "LENGTH2", "LENGTH4", "LENGTHC"];
let oracleExtendedDialect: SqlFormatterModule["plsql"] | null = null;
function resolveOracleExtendedDialect(sqlFormatter: SqlFormatterModule): SqlFormatterModule["plsql"] {
if (oracleExtendedDialect) return oracleExtendedDialect;
const existingFunctions = new Set(sqlFormatter.plsql.tokenizerOptions.reservedFunctionNames);
const extraFunctions = ORACLE_EXTENDED_FUNCTION_NAMES.filter((name) => !existingFunctions.has(name));
oracleExtendedDialect = {
...sqlFormatter.plsql,
tokenizerOptions: {
...sqlFormatter.plsql.tokenizerOptions,
reservedFunctionNames: [...sqlFormatter.plsql.tokenizerOptions.reservedFunctionNames, ...extraFunctions],
},
};
return oracleExtendedDialect;
}
export function canFormatSqlForDatabaseType(dbType: string | null | undefined): boolean {
return dbType !== "redis" && dbType !== "victoriametrics";
}
@@ -367,7 +389,7 @@ export async function formatSqlText(sql: string, dialect: SqlFormatDialect = "ge
},
}
: options;
const resolvedDialect = dialect === "clickhouse" ? resolveClickHouseIdentifierSafeDialect(sqlFormatter) : undefined;
const resolvedDialect = dialect === "clickhouse" ? resolveClickHouseIdentifierSafeDialect(sqlFormatter) : dialect === "oracle" ? resolveOracleExtendedDialect(sqlFormatter) : undefined;
const formatWithFallback = (input: string): string => {
try {
if (resolvedDialect) {
+18
View File
@@ -301,3 +301,21 @@ test("compressSqlText preserves unterminated block comments", () => {
const sql = "DELETE FROM users /* unfinished";
assert.equal(compressSqlText(sql), sql);
});
test("Oracle: formats byte and code-point function variants with functionCase", async () => {
const formattedLower = await formatSqlText("SELECT SUBSTR(a, 1), SUBSTRB(b, 1), INSTRB(c, 'x'), LENGTHB(d) FROM dual", "oracle", {
functionCase: "lower",
});
assert.match(formattedLower, /\bsubstr\(/);
assert.match(formattedLower, /\bsubstrb\(/);
assert.match(formattedLower, /\binstrb\(/);
assert.match(formattedLower, /\blengthb\(/);
const formattedUpper = await formatSqlText("select substr(a, 1), substrb(b, 1), instrb(c, 'x'), lengthb(d) from dual", "oracle", {
functionCase: "upper",
});
assert.match(formattedUpper, /\bSUBSTR\(/);
assert.match(formattedUpper, /\bSUBSTRB\(/);
assert.match(formattedUpper, /\bINSTRB\(/);
assert.match(formattedUpper, /\bLENGTHB\(/);
});