fix(sql): split GaussDB dollar-quoted routines
This commit is contained in:
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");
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user