feat: add cross-database entity lookup for searching column values across all databases

Enables searching for a specific column value (e.g. carrier_id=123) across all databases on a PostgreSQL server. The backend creates temporary connection pools per database (semaphore-limited to 5), queries information_schema for matching columns, and executes read-only SELECTs with real-time progress events. Results are grouped by database/table in a new "Entity Lookup" tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 17:28:33 +03:00
parent a2371f00df
commit d5cff8bd5e
13 changed files with 1030 additions and 5 deletions

View File

@@ -1,7 +1,7 @@
import { useAppStore } from "@/stores/app-store";
import { useConnections } from "@/hooks/use-connections";
import { ScrollArea } from "@/components/ui/scroll-area";
import { X, Table2, Code, Columns, Users, Activity } from "lucide-react";
import { X, Table2, Code, Columns, Users, Activity, Search } from "lucide-react";
export function TabBar() {
const { tabs, activeTabId, setActiveTabId, closeTab } = useAppStore();
@@ -15,6 +15,7 @@ export function TabBar() {
structure: <Columns className="h-3 w-3" />,
roles: <Users className="h-3 w-3" />,
sessions: <Activity className="h-3 w-3" />,
lookup: <Search className="h-3 w-3" />,
};
return (
@@ -41,7 +42,14 @@ export function TabBar() {
) : null;
})()}
{iconMap[tab.type]}
<span className="max-w-[120px] truncate">{tab.title}</span>
<span className="max-w-[150px] truncate">
{tab.title}
{tab.database && (
<span className="ml-1 text-[10px] text-muted-foreground">
{tab.database}
</span>
)}
</span>
<button
className="ml-1 rounded-sm opacity-0 hover:bg-accent group-hover:opacity-100"
onClick={(e) => {

View File

@@ -8,7 +8,7 @@ import { ReadOnlyToggle } from "@/components/layout/ReadOnlyToggle";
import { useAppStore } from "@/stores/app-store";
import { useConnections, useReconnect } from "@/hooks/use-connections";
import { toast } from "sonner";
import { Database, Plus, RefreshCw } from "lucide-react";
import { Database, Plus, RefreshCw, Search } from "lucide-react";
import type { ConnectionConfig, Tab } from "@/types";
import { getEnvironment } from "@/lib/environment";
@@ -16,7 +16,7 @@ export function Toolbar() {
const [listOpen, setListOpen] = useState(false);
const [dialogOpen, setDialogOpen] = useState(false);
const [editingConn, setEditingConn] = useState<ConnectionConfig | null>(null);
const { activeConnectionId, addTab } = useAppStore();
const { activeConnectionId, currentDatabase, addTab } = useAppStore();
const { data: connections } = useConnections();
const reconnectMutation = useReconnect();
const activeConn = connections?.find((c) => c.id === activeConnectionId);
@@ -38,11 +38,24 @@ export function Toolbar() {
type: "query",
title: "New Query",
connectionId: activeConnectionId,
database: currentDatabase ?? undefined,
sql: "",
};
addTab(tab);
};
const handleNewLookup = () => {
if (!activeConnectionId) return;
const tab: Tab = {
id: crypto.randomUUID(),
type: "lookup",
title: "Entity Lookup",
connectionId: activeConnectionId,
database: currentDatabase ?? undefined,
};
addTab(tab);
};
return (
<>
<div
@@ -91,6 +104,17 @@ export function Toolbar() {
New Query
</Button>
<Button
variant="ghost"
size="sm"
className="h-7 gap-1.5"
onClick={handleNewLookup}
disabled={!activeConnectionId}
>
<Search className="h-3.5 w-3.5" />
Entity Lookup
</Button>
<div className="flex-1" />
<span className="text-xs font-semibold text-muted-foreground tracking-wide">