Only qualify unqualified SQL Server names with schemas explicitly selected for the query tab. Keep connection defaults, saved SQL, external files, and restored tab schemas available for browsing without changing SQL execution semantics. Use the same execution schema policy for queries, EXPLAIN, counts, and exports. Preserve the runtime schema scope when duplicating tabs, while excluding it from persisted open-tab state. Add regression coverage for explicit and implicit schemas, including duplicated UPDATE statements. Fixes #12818. Validation: 211 related tests, frontend type checking and formatting, and PR CI.
195 lines
5.9 KiB
TypeScript
Vendored
195 lines
5.9 KiB
TypeScript
Vendored
import assert from "node:assert/strict";
|
|
import { test } from "vitest";
|
|
import { resolveNewQueryTarget } from "../../apps/desktop/src/lib/sql/newQueryContext.ts";
|
|
import type { ConnectionConfig, QueryTab, TreeNode } from "../../apps/desktop/src/types/database.ts";
|
|
|
|
function connection(id: string, database = ""): ConnectionConfig {
|
|
return {
|
|
id,
|
|
name: id,
|
|
db_type: "postgres",
|
|
host: "localhost",
|
|
port: 5432,
|
|
username: "postgres",
|
|
password: "",
|
|
database,
|
|
};
|
|
}
|
|
|
|
function sqliteConnection(id: string, database = ""): ConnectionConfig {
|
|
return {
|
|
...connection(id, database),
|
|
db_type: "sqlite",
|
|
host: "/tmp/real.sqlite",
|
|
port: 0,
|
|
};
|
|
}
|
|
|
|
function queryTab(connectionId: string, database: string, mode: QueryTab["mode"] = "data"): QueryTab {
|
|
return {
|
|
id: `${connectionId}-${database}`,
|
|
title: "users",
|
|
connectionId,
|
|
database,
|
|
sql: "",
|
|
isExecuting: false,
|
|
isCancelling: false,
|
|
isExplaining: false,
|
|
mode,
|
|
};
|
|
}
|
|
|
|
test("new query target prefers the active data tab context", () => {
|
|
const target = resolveNewQueryTarget({
|
|
activeTab: queryTab("conn-data", "analytics", "data"),
|
|
selectedTreeNode: {
|
|
id: "conn-tree:reporting",
|
|
label: "reporting",
|
|
type: "database",
|
|
connectionId: "conn-tree",
|
|
database: "reporting",
|
|
},
|
|
activeConnectionId: "conn-active",
|
|
connections: [connection("conn-active"), connection("conn-data"), connection("conn-tree")],
|
|
});
|
|
|
|
assert.deepEqual(target, {
|
|
connectionId: "conn-data",
|
|
database: "analytics",
|
|
schema: undefined,
|
|
catalog: undefined,
|
|
shouldRefreshDefaultDatabase: false,
|
|
});
|
|
});
|
|
|
|
test("new query target uses the selected sidebar node when there is no active tab", () => {
|
|
const selectedTreeNode: TreeNode = {
|
|
id: "conn-tree:reporting:public:users",
|
|
label: "users",
|
|
type: "table",
|
|
connectionId: "conn-tree",
|
|
database: "reporting",
|
|
schema: "public",
|
|
};
|
|
|
|
const target = resolveNewQueryTarget({
|
|
activeTab: undefined,
|
|
selectedTreeNode,
|
|
activeConnectionId: "conn-active",
|
|
connections: [connection("conn-active"), connection("conn-tree")],
|
|
});
|
|
|
|
assert.deepEqual(target, {
|
|
connectionId: "conn-tree",
|
|
database: "reporting",
|
|
schema: "public",
|
|
schemaQualifiesSql: true,
|
|
catalog: undefined,
|
|
shouldRefreshDefaultDatabase: false,
|
|
});
|
|
});
|
|
|
|
test("new query target prefers the selected sidebar node after sidebar focus", () => {
|
|
const target = resolveNewQueryTarget({
|
|
activeTab: queryTab("conn-data", "analytics", "data"),
|
|
selectedTreeNode: {
|
|
id: "conn-tree:reporting:public:users",
|
|
label: "users",
|
|
type: "table",
|
|
connectionId: "conn-tree",
|
|
database: "reporting",
|
|
},
|
|
activeConnectionId: "conn-active",
|
|
connections: [connection("conn-active"), connection("conn-data"), connection("conn-tree")],
|
|
preferredSource: "sidebar",
|
|
});
|
|
|
|
assert.deepEqual(target, {
|
|
connectionId: "conn-tree",
|
|
database: "reporting",
|
|
schema: undefined,
|
|
catalog: undefined,
|
|
shouldRefreshDefaultDatabase: false,
|
|
});
|
|
});
|
|
|
|
test("new query target refreshes default database for connection-only sidebar nodes", () => {
|
|
const target = resolveNewQueryTarget({
|
|
activeTab: undefined,
|
|
selectedTreeNode: { id: "conn-tree", label: "conn-tree", type: "connection", connectionId: "conn-tree" },
|
|
activeConnectionId: "conn-active",
|
|
connections: [connection("conn-active"), connection("conn-tree", "saved_default")],
|
|
});
|
|
|
|
assert.deepEqual(target, {
|
|
connectionId: "conn-tree",
|
|
database: "saved_default",
|
|
schema: undefined,
|
|
catalog: undefined,
|
|
shouldRefreshDefaultDatabase: true,
|
|
});
|
|
});
|
|
|
|
test("new query target repairs stale SQLite file paths without changing attached aliases", () => {
|
|
const stalePathTarget = resolveNewQueryTarget({
|
|
activeTab: queryTab("sqlite", "/tmp/stale.sqlite"),
|
|
connections: [sqliteConnection("sqlite", "/tmp/stale.sqlite")],
|
|
});
|
|
const attachedAliasTarget = resolveNewQueryTarget({
|
|
activeTab: queryTab("sqlite", "analytics"),
|
|
connections: [sqliteConnection("sqlite")],
|
|
});
|
|
|
|
assert.equal(stalePathTarget?.database, "main");
|
|
assert.equal(attachedAliasTarget?.database, "analytics");
|
|
});
|
|
|
|
// Issue #11517: without a tab or sidebar context the fallback must not treat the
|
|
// first *configured* connection as if it were connected, because that silently
|
|
// binds the new editor to a database the user may never have opened.
|
|
test("new query target falls back to the single online connection, not the first configured one", () => {
|
|
const target = resolveNewQueryTarget({
|
|
connections: [connection("conn-unused", "unused_db"), connection("conn-online", "online_db")],
|
|
connectedSqlConnectionIds: new Set(["conn-online"]),
|
|
});
|
|
|
|
assert.deepEqual(target, {
|
|
connectionId: "conn-online",
|
|
database: "online_db",
|
|
schema: undefined,
|
|
shouldRefreshDefaultDatabase: true,
|
|
});
|
|
});
|
|
|
|
test("new query target remains unselected when several configured connections are offline", () => {
|
|
const target = resolveNewQueryTarget({
|
|
connections: [connection("conn-first", "first_db"), connection("conn-second", "second_db")],
|
|
connectedSqlConnectionIds: new Set<string>(),
|
|
});
|
|
|
|
assert.equal(target, null);
|
|
});
|
|
|
|
test("new query target remains unselected when several SQL connections are online", () => {
|
|
const target = resolveNewQueryTarget({
|
|
connections: [connection("conn-first", "first_db"), connection("conn-second", "second_db"), connection("conn-third", "third_db")],
|
|
connectedSqlConnectionIds: new Set(["conn-second", "conn-third"]),
|
|
});
|
|
|
|
assert.equal(target, null);
|
|
});
|
|
|
|
test("new query target keeps the sole configured connection as the legacy fallback", () => {
|
|
const target = resolveNewQueryTarget({
|
|
connections: [connection("conn-only", "only_db")],
|
|
connectedSqlConnectionIds: new Set<string>(),
|
|
});
|
|
|
|
assert.deepEqual(target, {
|
|
connectionId: "conn-only",
|
|
database: "only_db",
|
|
schema: undefined,
|
|
shouldRefreshDefaultDatabase: true,
|
|
});
|
|
});
|