fix(sql): split GaussDB dollar-quoted routines

This commit is contained in:
zipg authored and GitHub committed 2026-07-28 00:51:34 +08:00
1 parent 59f9d5b2a1
commit 09f794fd97
4 files changed
+112 -4

No files matched your search

@@ -80,6 +80,18 @@ BEGIN
NULL;
END;`;
const gaussDbDollarQuotedFunctionScript = `DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_md5_uuid;
CREATE OR REPLACE FUNCTION dbx_issue_4572_tmp_md5_uuid (v_str IN TEXT) RETURNS varchar(36) LANGUAGE PLPGSQL IMMUTABLE AS $function$
DECLARE
str1 TEXT;
BEGIN
str1 := md5(v_str);
RETURN CAST(str1 AS varchar(36));
END$function$;
DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_missing;`;
const xuguProgrammableObjectFixtures = [
`CREATE OR REPLACE PROCEDURE dbx_xugu_procedure AS
v_value INTEGER;
@@ -292,6 +304,15 @@ describe("splitSqlStatementRanges", () => {
expect(rangeSqlTexts(splitSqlStatementRanges(gaussDbNestedProcedure, "gaussdb"))).toEqual([gaussDbNestedProcedure]);
});
it("separates GaussDB dollar-quoted functions from surrounding statements", () => {
const ranges = splitSqlStatementRanges(gaussDbDollarQuotedFunctionScript, "gaussdb");
expect(rangeSqlTexts(ranges)).toEqual([
"DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_md5_uuid",
gaussDbDollarQuotedFunctionScript.slice(gaussDbDollarQuotedFunctionScript.indexOf("CREATE"), gaussDbDollarQuotedFunctionScript.lastIndexOf(";\n\nDROP")),
"DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_missing",
]);
});
it("keeps Xugu programmable object DDL together and retains its terminator", () => {
for (const sql of xuguProgrammableObjectFixtures) {
const ranges = splitSqlStatementRanges(`${sql}\nSELECT 1;`, "xugu");
+14 -1
View File
@@ -289,6 +289,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
let customDelimiter: string | null = null;
let state: QuoteState = "none";
let dollarTag = "";
let postgresDollarQuotedRoutine = false;
let i = 0;
const isWhitespace = (ch: string) => ch === " " || ch === "\t" || ch === "\r" || ch === "\n";
@@ -305,6 +306,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
if (statementStart === -1) {
statementEnd = -1;
pendingHintStart = -1;
postgresDollarQuotedRoutine = false;
return;
}
const trimmedTo = trimRangeEnd(sql, statementStart, to);
@@ -314,6 +316,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
statementStart = -1;
statementEnd = -1;
pendingHintStart = -1;
postgresDollarQuotedRoutine = false;
};
while (i < len) {
@@ -470,6 +473,9 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
const tagMatch = /^\$[A-Za-z_0-9]*\$/.exec(sql.slice(i));
if (tagMatch) {
markContent(i);
if (databaseType === "gaussdb" && statementStart !== -1 && startsWithPostgresDollarQuotedRoutinePrefix(sql.slice(statementStart, i))) {
postgresDollarQuotedRoutine = true;
}
dollarTag = tagMatch[0].slice(1, -1);
i += tagMatch[0].length;
state = "dollar";
@@ -497,7 +503,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
flush();
} else {
const statementSoFar = statementStart === -1 ? "" : sql.slice(statementStart, i);
const isOraclePlSql = isOracleLikeDatabase(databaseType) && statementStart !== -1 && startsWithOraclePlSqlBlock(statementSoFar);
const isOraclePlSql = isOracleLikeDatabase(databaseType) && !postgresDollarQuotedRoutine && statementStart !== -1 && startsWithOraclePlSqlBlock(statementSoFar);
const isSapHanaScriptBlock = isSapHanaScriptBlockDatabase(databaseType) && statementStart !== -1 && startsWithSapHanaScriptBlock(statementSoFar);
if (isOraclePlSql || isSapHanaScriptBlock) {
markContent(i);
@@ -1625,6 +1631,13 @@ function startsWithOraclePlSqlBlock(sql: string): boolean {
return ORACLE_PL_SQL_CREATE_OBJECT_TYPES.has(words[index] ?? "");
}
function startsWithPostgresDollarQuotedRoutinePrefix(sql: string): boolean {
const words = oraclePlSqlWords(sql);
if (words[0] !== "CREATE") return false;
const objectIndex = skipOraclePlSqlCreateModifiers(words, 1);
return (words[objectIndex] === "FUNCTION" || words[objectIndex] === "PROCEDURE") && words[words.length - 1] === "AS";
}
/** Skip OR REPLACE / FORCE / NOFORCE / EDITIONABLE modifiers after CREATE. */
function skipOraclePlSqlCreateModifiers(words: readonly string[], startIndex: number): number {
let index = startIndex;
@@ -93,6 +93,7 @@ describe("connectionStore metadata loading", () => {
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth,
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
listDatabases,
loadSchemaCache: vi.fn().mockResolvedValue(null),
saveSchemaCache: vi.fn().mockResolvedValue(undefined),