Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions src/components/editor/SQLEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
import { useSchemaCompletion } from "../../hooks/useSchemaCompletion";
import { statementRange } from "../../lib/error-marker";
import { monacoThemeData, monacoThemeName } from "../../lib/monaco-theme";
import { runConnectionId } from "../../lib/run-connection";
import { liveConnectionIds, runConnectionId } from "../../lib/run-connection";
import { createCompletionProvider } from "../../lib/schema-completion-provider";
import { formatSql } from "../../lib/sql-format";
import { sqlAtCursor } from "../../lib/statement-at-cursor";
Expand All @@ -13,7 +13,6 @@ import { setPendingContentFlush, useEditorStore } from "../../stores/editorStore
import { useResultStore } from "../../stores/resultStore";
import { useSchemaStore } from "../../stores/schemaStore";
import { useSettingsStore } from "../../stores/settingsStore";
import { useSqliteStore } from "../../stores/sqliteStore";
import { useThemeStore } from "../../stores/themeStore";

/**
Expand All @@ -27,14 +26,10 @@ import { useThemeStore } from "../../stores/themeStore";
function activeTabContext(): { connectionId?: string; database?: string; session?: string } {
const { tabs, activeTabId } = useEditorStore.getState();
const tab = tabs.find((t) => t.id === activeTabId);
const { selectedConnectionId, activeConnections } = useConnectionStore.getState();
const live = [
...activeConnections.map((c) => c.id),
...useSqliteStore.getState().sessions.map((s) => s.id),
];
const { selectedConnectionId } = useConnectionStore.getState();
return {
// The server the tab is marked with, not whichever is selected (#788).
connectionId: runConnectionId(tab, selectedConnectionId, live),
connectionId: runConnectionId(tab, selectedConnectionId, liveConnectionIds()),
database: tab?.database,
session: tab?.type === "query" ? tab.id : undefined,
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/useStartupReconnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function useStartupReconnect() {
const { tabs, setTabConnection: setTabConn } = useEditorStore.getState();
const profileIds = [
...new Set(
tabs.filter((t) => t.profileId).map((t) => t.profileId!),
tabs.flatMap((t) => (t.profileId ? [t.profileId] : [])),
),
];

Expand Down
51 changes: 51 additions & 0 deletions src/hooks/__tests__/useLiveConnectionIds.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { act, renderHook } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { liveConnectionIds } from "../../lib/run-connection";
import { useConnectionStore } from "../../stores/connectionStore";
import { type SqliteSession, useSqliteStore } from "../../stores/sqliteStore";
import type { ConnectionInfo } from "../../types";
import { useLiveConnectionIds } from "../useLiveConnectionIds";

vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn() }));

const server = (id: string) => ({ id }) as unknown as ConnectionInfo;
const file = (id: string) => ({ id }) as unknown as SqliteSession;

beforeEach(() => {
useConnectionStore.setState({ activeConnections: [] });
useSqliteStore.setState({ sessions: [] });
});

describe("useLiveConnectionIds", () => {
it("lists connected servers and open SQLite files", () => {
useConnectionStore.setState({ activeConnections: [server("dev")] });
useSqliteStore.setState({ sessions: [file("sqlite-1")] });

const { result } = renderHook(() => useLiveConnectionIds());

expect(result.current).toEqual(["dev", "sqlite-1"]);
});

it("follows servers and files as they come and go", () => {
const { result } = renderHook(() => useLiveConnectionIds());
expect(result.current).toEqual([]);

act(() => useConnectionStore.setState({ activeConnections: [server("dev"), server("prod")] }));
act(() => useSqliteStore.setState({ sessions: [file("sqlite-1")] }));
expect(result.current).toEqual(["dev", "prod", "sqlite-1"]);

act(() => useConnectionStore.setState({ activeConnections: [server("prod")] }));
expect(result.current).toEqual(["prod", "sqlite-1"]);
});

// Where a run goes and whether a tab's connection still counts (#944) read
// one list each; they must not drift apart.
it("is the same list as liveConnectionIds", () => {
useConnectionStore.setState({ activeConnections: [server("dev")] });
useSqliteStore.setState({ sessions: [file("sqlite-1"), file("sqlite-2")] });

const { result } = renderHook(() => useLiveConnectionIds());

expect(result.current).toEqual(liveConnectionIds());
});
});
22 changes: 22 additions & 0 deletions src/hooks/useLiveConnectionIds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useMemo } from "react";
import { useConnectionStore } from "../stores/connectionStore";
import { useSqliteStore } from "../stores/sqliteStore";

/**
* Every connection that can run a statement right now, for a component: the
* connected servers and the open SQLite files.
*
* `liveConnectionIds` (lib/run-connection) is the same list read once, for
* code outside a component. This one re-renders its caller when a server
* connects or disconnects or a file opens or closes. Both have to stay the
* same list: where a run goes and whether a tab's connection still counts
* (#944) depend on it.
*/
export function useLiveConnectionIds(): string[] {
const activeConnections = useConnectionStore((s) => s.activeConnections);
const sqliteSessions = useSqliteStore((s) => s.sessions);
return useMemo(
() => [...activeConnections.map((c) => c.id), ...sqliteSessions.map((s) => s.id)],
[activeConnections, sqliteSessions],
);
}
10 changes: 3 additions & 7 deletions src/hooks/useQueryExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { runConnectionId } from "../lib/run-connection";
import { useConnectionStore } from "../stores/connectionStore";
import { useEditorStore } from "../stores/editorStore";
import { useResultStore } from "../stores/resultStore";
import { useSqliteStore } from "../stores/sqliteStore";
import { useLiveConnectionIds } from "./useLiveConnectionIds";

/**
* The editor toolbar's execution context, pre-bound.
Expand All @@ -27,8 +27,7 @@ export function useQueryExecution() {
const selectedConnectionId = useConnectionStore(
(s) => s.selectedConnectionId,
);
const activeConnections = useConnectionStore((s) => s.activeConnections);
const sqliteSessions = useSqliteStore((s) => s.sessions);
const liveConnectionIds = useLiveConnectionIds();
const activeTabId = useEditorStore((s) => s.activeTabId);
const tabs = useEditorStore((s) => s.tabs);
const activeTab = tabs.find((t) => t.id === activeTabId);
Expand All @@ -41,10 +40,7 @@ export function useQueryExecution() {
const isExecuting = useResultStore((s) => s.isExecuting);

// The server the tab is marked with, not whichever is selected (#788).
const connectionId = runConnectionId(activeTab, selectedConnectionId, [
...activeConnections.map((c) => c.id),
...sqliteSessions.map((s) => s.id),
]);
const connectionId = runConnectionId(activeTab, selectedConnectionId, liveConnectionIds);
const database = activeTab?.database;
// The tab's own server session, so what one run sets up is there for the
// next (#731). Only a query tab has one; the others are views of an object.
Expand Down
10 changes: 3 additions & 7 deletions src/hooks/useResultsFollowActiveTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { runConnectionId } from "../lib/run-connection";
import { useConnectionStore } from "../stores/connectionStore";
import { useEditorStore } from "../stores/editorStore";
import { useResultStore } from "../stores/resultStore";
import { useSqliteStore } from "../stores/sqliteStore";
import { useLiveConnectionIds } from "./useLiveConnectionIds";

/**
* Keep a grid read on one server from being shown under another (#842).
Expand All @@ -19,16 +19,12 @@ export function useResultsFollowActiveTab() {
const activeTabId = useEditorStore((s) => s.activeTabId);
const tabs = useEditorStore((s) => s.tabs);
const selectedConnectionId = useConnectionStore((s) => s.selectedConnectionId);
const activeConnections = useConnectionStore((s) => s.activeConnections);
const sqliteSessions = useSqliteStore((s) => s.sessions);
const liveConnectionIds = useLiveConnectionIds();
const dropForeignResults = useResultStore((s) => s.dropForeignResults);

const activeTab = tabs.find((t) => t.id === activeTabId);
// The tab's own server, as Run would pick it (#788).
const tabConnectionId = runConnectionId(activeTab, selectedConnectionId, [
...activeConnections.map((c) => c.id),
...sqliteSessions.map((s) => s.id),
]);
const tabConnectionId = runConnectionId(activeTab, selectedConnectionId, liveConnectionIds);

useEffect(() => {
dropForeignResults(tabConnectionId);
Expand Down
Loading