fix(sql): preserve Oracle database link references

This commit is contained in:
t8y2 2026-08-02 16:03:41 +08:00
parent 7206900313
commit 2220eed285
No known key found for this signature in database
3 changed files with 40 additions and 1 deletions

View File

@ -245,6 +245,31 @@ describe("useSqlExecution", () => {
expect(executeCurrentSql).toHaveBeenCalledWith(resolvedSql, { openInNewResultTab: true });
});
it("executes Oracle database-link queries without opening the parameter dialog", async () => {
const sql = "SELECT 1 FROM DUAL@WDHIS160;";
const activeTab = ref<QueryTab | undefined>(queryTab("ORCL"));
const activeConnection = ref<ConnectionConfig | undefined>(connection("oracle"));
const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result");
const queryStore = useQueryStore();
const executeCurrentSql = vi.spyOn(queryStore, "executeCurrentSql").mockImplementation(async () => {
if (activeTab.value) activeTab.value.result = { columns: ["1"], rows: [[1]], affected_rows: 0, execution_time_ms: 1 };
});
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
const execution = useSqlExecution({
activeTab: computed(() => activeTab.value),
activeConnection: computed(() => activeConnection.value),
executableSql: computed(() => sql),
activeOutputView,
});
await execution.tryExecute();
expect(execution.showSqlParameterDialog.value).toBe(false);
expect(execution.sqlParameterNames.value).toEqual([]);
expect(executeCurrentSql).toHaveBeenCalledWith(sql, {});
});
it("sends native SET variables without client-side expansion", async () => {
const activeTab = ref<QueryTab | undefined>(queryTab("app"));
const activeConnection = ref<ConnectionConfig | undefined>(connection("mysql"));

View File

@ -53,6 +53,14 @@ describe("extractSqlParameters", () => {
expect(extractSqlParameters("select @amount/2, @total / 4")).toEqual(["amount", "total"]);
});
it("ignores Oracle database links while preserving standalone at-sign placeholders", () => {
const sql = 'SELECT * FROM HR.EMPLOYEES@REMOTE_DB, "AUDIT_LOG"@ARCHIVE_DB WHERE tenant_id = @tenant_id';
expect(extractSqlParameters("SELECT 1 FROM DUAL@WDHIS160;", { databaseType: "oracle" })).toEqual([]);
expect(extractSqlParameters(sql, { databaseType: "oracle" })).toEqual(["tenant_id"]);
expect(substituteSqlParameters(sql, { tenant_id: { kind: "number", value: "7" } }, { databaseType: "oracle" })).toBe('SELECT * FROM HR.EMPLOYEES@REMOTE_DB, "AUDIT_LOG"@ARCHIVE_DB WHERE tenant_id = 7');
expect(extractSqlParameters("SELECT * FROM EMPLOYEES@REMOTE_DB", { databaseType: "postgres" })).toEqual(["REMOTE_DB"]);
});
it("describes each placeholder syntax for the parameter dialog", () => {
const sql = "select ? as a, :named as b, ${shell_name} as c, #{mybatis_name} as d, @sql_server_name as e";
expect(extractSqlParameterDescriptors(sql)).toEqual([

View File

@ -203,7 +203,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
}
if (ch === "@" && isSyntaxEnabled("sqlserver")) {
const name = readParameterName(sql, i + 1);
if (name && next !== "@" && sql[i - 1] !== "@" && !isJdbcxMcpScopedPackage(sql, i, i + 1 + name.length) && !nativeSqlServerParameters.declared.has(name.toLowerCase()) && !nativeSqlServerParameters.ignoredStarts.has(i)) {
if (name && next !== "@" && sql[i - 1] !== "@" && !isOracleDatabaseLinkMarker(sql, i, options?.databaseType) && !isJdbcxMcpScopedPackage(sql, i, i + 1 + name.length) && !nativeSqlServerParameters.declared.has(name.toLowerCase()) && !nativeSqlServerParameters.ignoredStarts.has(i)) {
occurrences.push({
key: name,
name,
@ -230,6 +230,12 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
return occurrences;
}
function isOracleDatabaseLinkMarker(sql: string, index: number, databaseType: DatabaseType | undefined): boolean {
if (databaseType !== "oracle" || index === 0) return false;
const previous = sql[index - 1];
return PARAMETER_NAME_CHAR_RE.test(previous) || previous === "$" || previous === "#" || previous === '"';
}
function collectDuckDbStructFieldSeparators(sql: string): Set<number> {
const separators = new Set<number>();
const contexts: DuckDbStructLiteralContext[] = [];