Browsing any GP table threw "column ctid does not exist" because the
table-viewer's pagination query unconditionally appended `ctid::text`
to the SELECT. AO and AOCO tables (the dominant storage in GP)
genuinely have no ctid, and even on heap tables the value isn't
reliable across segments — so editing by ctid would be wrong even when
the column exists.
- get_table_data: skip the ctid column entirely on Greenplum (`SELECT *`
instead of `SELECT *, ctid::text`); ctids field stays as an empty Vec.
- update_row / delete_rows: when the table has no PK on a GP connection,
return an actionable error instead of falling through to the ctid
path. Tells the user to add a PK or use raw SQL.
Vanilla Postgres behavior is unchanged.
Two changes that close the "everything lives in one global pool" gap.
UI: Saved Queries sidebar now defaults to "This connection" and lists
only entries with `connection_id == activeConnectionId` plus unattached
entries (legacy global saves before F2). A small toggle ("All") above
the search box brings the previous behavior back when copying queries
between databases. Each row in "All" mode shows a tag with the source
connection's name; legacy global entries show "unattached".
Backend: delete_connection now best-effort cleans persisted state for
that connection — removes `memory/<id>.md` (delete_memory_core in
memory.rs) and drops every entry in saved_queries.json with the
matching `connection_id` (delete_by_connection_core in
saved_queries.rs). Entries with `connection_id == None` are deliberately
preserved. Cleanup errors are logged but don't block the deletion since
connections.json is the source of truth.
Memory was already per-connection (F1); query history already filters
by connection. This commit makes saved queries behave the same and
stops orphan files from accumulating.
Two changes that together kill the "prompt is too long: 1991577 tokens"
crash on large Greenplum catalogs.
1. Overview table cap (build_overview_postgres):
The agent re-injects the full overview into the system prompt every
turn. On a multi-thousand-table catalog (typical for GP data lakes)
this was megabytes per call and quickly overran even Kimi-K2's 256K
window. Now we list at most 400 tables and replace the rest with
per-schema counts plus a hint to call list_tables for specifics.
2. Friendly Fireworks-400 rewrite:
Detect "prompt is too long" / "maximum context length" in the
Fireworks 400 body and turn it into an actionable message asking
the user to /compact, instead of leaking raw API JSON to the chat.
When connected to a Greenplum cluster, schema introspection now surfaces
the two attributes that dominate query performance there: the data
distribution policy (DISTRIBUTED BY / RANDOMLY) and the storage kind
(heap / AO row / AO column). Without these, the agent writes
PG-optimal SQL that triggers Redistribute Motion in GP and runs orders
of magnitude slower than necessary.
- track Greenplum major version (6 vs 7) at connect time, since GP6
uses pg_class.relstorage and GP7 dropped it in favor of pg_am
- new fetch_gp_table_extras helper queries gp_distribution_policy and
the version-appropriate storage catalog, returns a per-table map
- format_table_block prints ` -- GP: DISTRIBUTED BY (...) | STORAGE: ...`
under the TABLE header when extras are available
- build_overview_postgres appends a GREENPLUM NOTES block with the
distribution-aware-join rules and a skew-detection one-liner; the
agent sees this in every system prompt on a GP connection
- build_schema_context (legacy generate_sql / explain_sql / fix_sql_error
path) and the chat get_columns tool both feed extras into format_table_block
P0 of the GP support arc — partitioning, EXPLAIN motion-aware parsing,
external tables, resource queues, and a skew-check tool are deliberately
deferred to follow-up commits.
@uiw/react-codemirror's outer wrapper inherited h-full but the inner
.cm-editor / .cm-scroller had no height constraint, so long queries
spilled outside the visible area with no way to scroll. Pass
`height="100%"` to the CodeMirror component and pin .cm-editor to the
wrapper height via a Tailwind arbitrary selector so the scroller
renders correctly inside the resizable panel.
The chat usage badge was hardcoded to ~8K-token Ollama defaults
(`CONTEXT_BUDGET_CHARS = 24_000`), which made every Fireworks session
look 150%+ full after a few hops even though models like Kimi-K2 carry
256K context windows. Now the budget is selected per-provider:
- Ollama → 24K chars (~8K tok), unchanged
- Fireworks → 384K chars (~128K tok), a safe floor for the smallest
Fireworks chat models (qwen2.5-coder 32K) while not stuffing the bar
for the larger ones
Auto-compact thresholds and the % badge both read this back from the
backend, so they now scale correctly when the user switches providers.
Routes chat-completions through a managed OpenAI-compatible inference
endpoint as an alternative to local Ollama, useful when the agent needs
fast multi-hop reasoning that local hardware can't sustain.
- backend: rename `call_ollama_chat_messages` → `call_chat_messages`,
dispatch by provider; add `call_fireworks` branch (Bearer auth,
`response_format: json_object` mapped from internal `format="json"`)
and `list_fireworks_models` Tauri command
- settings: extend `AiProvider` enum + `AiSettings.fireworks_api_key`
(serde-default for legacy config compat); Fireworks base URL hardcoded
- UI: provider selector in both popover and AppSettingsSheet (only
ollama+fireworks shown; legacy openai/anthropic kept for serde-compat
but normalized to ollama in UI); password input + dynamic model list
for Fireworks; switching provider clears stale model selection
- 4 unit tests: serde round-trip, legacy settings deserialization,
Fireworks chat-completions parsing, models-list parsing
Adds inline data visualisation to the chat agent. After a successful
run_query, the agent can call make_chart(chart_type, x, y, [group,
title, orientation]) and the result is rendered as a bar / line / area
/ pie chart inline in the chat thread, sourced from the previous query
result.
Backend (commands/chat.rs, models/chat.rs)
- New ChartConfig{chart_type, x, y, group?, title?, orientation?} model.
- New AgentAction::MakeChart{config} variant. Parser accepts both
`chart_type` and the alternative `type` field name (qwen3 sometimes
emits the latter). Validates chart_type is one of bar/line/area/pie.
- last_successful_query_result helper finds the most recent successful
run_query in the working thread.
- MakeChart dispatcher: validates that x/y/group columns exist in the
attached query result, emits a tool_result with the same QueryResult
in `result` and the chart_config JSON in `text`. Mismatches surface
as a clear error ("y column `name` is not in the last result.
Available: company_name, legal_name, …").
- build_history compression unchanged: make_chart's tool_result text
field (the small chart_config JSON) is included in LLM history; the
large QueryResult.rows are NOT, since the per-tool branch only emits
text for non-run_query tools.
- System prompt: documents make_chart with concrete usage hints
(top-N → bar, time series → line/area, proportions → pie; skip for
≤2 or >500 rows). 7 new parser/dispatcher tests.
Frontend (src/components/chat/)
- recharts ^3.8 added.
- New ChartPreview component renders bar (vertical+horizontal), line,
area, pie. Supports grouped series via the `group` config field by
pivoting rows into a wide format. Y values coerced to numbers
(parses strings, nulls → 0). Caps to 500 points to keep things
responsive on huge results.
- ChatMessageView routes tool=="make_chart" tool_result through a new
ChartToolResult that parses the config JSON from the message text
and feeds the embedded QueryResult into ChartPreview.
- New labels/icons (BarChart3) and preview-extraction for make_chart
in tool-call collapsed headers (`bar: carrier_name → trip_count`).
Verification: cargo test --lib 77 pass (+7), tsc clean, vitest 20
pass.
The previous symptom: agent succeeded on its 8th run_query (got 30
rows) but the loop ended without a final because that was the last
allowed hop. Result: "Stopped after 8 tool calls" and the data was
wasted. Also: agent kept assuming `legal_entities.name` existed even
after get_columns showed it didn't.
Backend (commands/chat.rs)
- MAX_HOPS 8 -> 10. With list_databases / list_tables / get_columns /
switch_database / run_query / remember / save_query / find_queries
available, complex investigations need a bit more headroom.
- New force_final_synthesis: when the loop falls through MAX_HOPS,
one extra LLM call is made WITHOUT the JSON action protocol,
asking the model to write a plain-text answer based on whatever
data was already collected. This rescues cases where the agent
succeeded on the last hop but had no budget for a final. Output
goes through clean_summary so any stray JSON or fences are stripped.
- Stronger RULES in system prompt:
* Explicit ban on guessing column names: "After get_columns, your
next run_query must use ONLY column names that appear verbatim
in that output."
* Concrete example of how to read PG's "column le.name does not
exist" — the alias `le` tells you which table is missing it.
* Mention the new hop budget (10) so the model spends it
deliberately.
Verification: cargo test --lib 70 pass, tsc clean.
The previous loop burned all 8 hops re-running the same broken query
("operator does not exist: character varying = uuid") because (a) the
agent never saw PostgreSQL's HINT — only the bare error message — and
(b) the prompt's "retry once" rule was advisory, not enforced.
Backend (commands/chat.rs)
- New format_db_error helper. When the error is sqlx::Error::Database
with a PostgreSQL backend, downcast to PgDatabaseError and append
DETAIL and HINT lines. Common PG hints are exactly the spelled-out
fix the agent needs ("You might need to add explicit type casts").
- New last_run_query_error helper to fish the most recent failing SQL
text out of working history for the give-up message.
- Hard server-side guard: track consecutive_query_errors. On
consecutive run_query failures >= 2, force-emit a `final` message
that quotes the last error and suggests next steps (cast hints,
open the table in sidebar, switch to Advanced mode). The model
cannot loop past this regardless of how many hops remain.
- Counter resets to 0 when the model takes any non-RunQuery action
(get_columns, list_tables, etc.) — investigation buys a fresh
error budget.
- Stronger prompt RULES section: explicitly walks through three of
the most common PG error classes ("operator does not exist",
"column does not exist", "relation does not exist") and the
matching fixes. Tells the model the harness force-stops after 2
consecutive failures.
Tests (4 new): format_db_error fallback, last_run_query_error finds
most recent / handles empty / handles no-errors thread.
Verification: cargo test --lib 70 pass (+4), tsc clean, vitest 20
pass.
INTERVAL handling
- pg_value_to_json now decodes PG INTERVAL via PgInterval and renders
it psql-style: `1 year 2 mons 3 days 04:05:06`. Previously
AVG(timestamp - timestamp) and similar interval-returning queries
showed `<unsupported type: INTERVAL>` in chat results.
- 7 unit tests covering zero, days-only, mixed, negative, microsecond
fraction, and the singular/plural unit rules.
Compact reliability
- Sharper system prompt: explicitly instructs plain text starting with
`-`, no JSON, no fences, no field names. qwen3-coder is heavily
trained on the agent JSON protocol and was sometimes returning
`{"action":"final","text":"..."}` even for the compact prompt.
- New clean_summary helper strips ``` fences (with or without lang
identifier) and extracts the underlying string from a JSON envelope
if the model still wraps the answer (looks for text/summary/content/
answer/output keys). 6 unit tests.
- Frontend useChat.compact: success/no-op/error toasts via sonner so
the user sees what happened. "Nothing to compact" appears when there
is no older history beyond the last user turn (previously silent).
Verification: cargo test --lib 66 pass (+13), tsc clean, vitest 20
pass.
Adds visibility into how much of the model context window the chat agent
is using and a way to free space when it fills up.
Backend
- New ContextUsage{used_chars, budget_chars} returned from chat_send
alongside messages (return type ChatTurnResult). Computed by running
build_history once at end of turn and counting char bytes — same data
path as the actual LLM call, so the count is exact for the chosen
budget unit.
- CONTEXT_BUDGET_CHARS = 24,000 (~6-8K tokens). Tuned for Ollama
defaults; can be exposed via AiSettings later.
- New chat_compact Tauri command. Splits the thread at the last user
turn, LLM-summarises everything before it (3-6 bullet points,
language-aware, < 800 chars), and returns a thread of
[Assistant("📋 Compacted N messages: …"), <last_user_turn?>]. The
recent user turn is preserved untouched so the agent can keep
answering it.
- render_thread_for_summary skips QueryResult.rows entirely so a single
large run_query can't blow the summariser's context.
- 3 new unit tests (last_user_turn_index, render skipping rows, empty
thread no-op).
Frontend
- ChatPanel header gets a usage badge: progress bar + `Xk / Yk tok ·
P%`, color-coded green (<30%) / muted (<60%) / amber (<85%) / red
(≥85%). Tooltip explains and nudges /compact when ≥60%.
- Compact button next to Clear in the header.
- Slash commands in ChatComposer: /compact, /clear.
- Empty-state shows the slash-command hint.
- Auto-compact: if the previous turn pushed usage past 85% AND the
thread has more than one message, the next user turn first runs
chat_compact transparently before chat_send. The compaction surfaces
as a visible Assistant("📋 Compacted …") message so the user can see
what the agent kept.
- app-store gets chatUsage map per tab + replaceChatThread + setChatUsage
actions; closeTab and clearChatThread clean up usage too.
Verification: cargo check clean, cargo test --lib 53 pass (+3),
tsc --noEmit clean, vitest run 20 pass.
Vite default 5173 frequently conflicts with parallel local projects.
Move Tusk's vite dev server (and tauri devUrl) to 5174 so it can
coexist with another project running on the default port.
Removes enterprise/DBA features and replaces the marginal AI bar with a
central chat agent that has progressive-discovery tools, cross-session
memory, saved-query reuse, and inline result actions. Adds ClickHouse
support alongside PostgreSQL/Greenplum.
Cleanup
- Drop ~10k LOC of advanced features: Docker, Snapshots, Validation,
Index Advisor, Role/User Management, Data Generator, ERD, Lookup.
- Trim deps: drop @xyflow/react, dagre, @types/dagre; cut tokio features
to rt-multi-thread/sync/time/net/macros.
- Remove unused TuskError variants and dead helpers (topological_sort,
invalidate_schema_cache).
Multi-DB (PostgreSQL + ClickHouse)
- New src-tauri/src/db/ module: ChClient (HTTP-based, reuses reqwest),
sql_guard (cross-flavor read-only whitelist with 8 tests).
- ConnectionConfig gains db_flavor and secure fields with serde defaults
for backwards-compatible connections.json.
- All connection/query/schema/data commands dispatch by flavor; CH
covers connect, execute_query, list_databases/schemas/tables/views/
columns/completion_schema, paginated table fetch.
- Frontend: dbCapabilities matrix, ConnectionDialog engine selector
with port auto-swap and HTTPS toggle, SqlEditor switches to
StandardSQL dialect for CH, TableDataView surfaces CH connections as
read-only.
AI-first chat agent
- New src/components/chat/ panel with composer, message rendering,
collapsible tool-call/result blocks, top-level ErrorBoundary.
- Backend agent loop in commands/chat.rs with strict-JSON tool
protocol. Nine tools: list_databases, list_tables, get_columns,
switch_database, run_query, remember, save_query, find_queries, final.
Forgiving parser accepts both flat and nested-input shapes.
- Compressed history: only the last 4 run_query results carry sample
rows (≤10, cells truncated to 200 chars) into LLM context; older
results marked omitted.
- System prompt uses lite OVERVIEW (DB list + active-DB tables only)
instead of full DDL — schema details are loaded on demand via
get_columns. CH OVERVIEW shows cross-DB tables since CH allows
db.table queries.
Cross-session memory (F1)
- Per-connection markdown file at app_data_dir/memory/<connection_id>.md,
16KB cap with oldest-block eviction. Agent appends via remember()
tool; the file is injected into LEARNED NOTES section of every system
prompt.
- New Memory sidebar tab with editable textarea, badge for note count,
empty-state with template. Edits picked up on the next agent turn.
Saved-query reuse (F2)
- Tools save_query and find_queries scoped to current connection.
save_query attaches a UUID + timestamp; find_queries returns top 10
matches with SQL preview ≤500 chars.
- Storage shared with the sidebar Saved panel.
Inline result actions (F3)
- run_query result block in chat gets Open-full (90vw × 80vh modal with
full ResultsTable, no row cap) and Export (reuses ExportDialog for
CSV/JSON via existing exportCsv/exportJson commands).
Verification
- cargo check clean, zero warnings.
- cargo test --lib: 50 pass (20 chat parser + 4 memory + 8 sql_guard +
6 clean_sql + 12 escape_ident).
- npx tsc --noEmit clean.
- npx vitest run: 20 pass.
Replace raw WHERE input with a dual-mode filter:
- Visual mode: column/operator/value dropdowns with AND/OR support
- SQL mode: raw WHERE clause input (auto-strips "where" prefix)
The act-based Gitea runner executes JS actions inside the specified
container, but ubuntu:22.04 has no Node.js. Use git clone directly
to avoid the dependency.
Replace useEffect-based state resets in dialogs with React's render-time
state adjustment pattern. Wrap ref assignments in hooks with useEffect.
Suppress known third-party library warnings (shadcn CVA exports,
TanStack Table). Remove warn downgrades from eslint config.
- Make reqwest::Client a LazyLock singleton instead of per-call allocation
- Parallelize 3 independent DB queries in get_index_advisor_report with tokio::join!
- Eliminate per-iteration Vec allocation in snapshot FK dependency loop
- Hoist try_local_pg_dump() call in SampleData clone mode to avoid double execution
- Evict stale schema cache entries on write to prevent unbounded memory growth
- Remove unused ValidationReport struct and config_path field
- Rename IndexRecommendationType variants to remove redundant suffix
Reformat Rust code with rustfmt, suppress clippy::too_many_arguments
for Tauri IPC commands, derive Default for AppSettings, fix unused
variable pattern in TableDataView, and add unit tests for utils.
Replace install -D with mkdir -p + install for macOS portability,
add vitest with jsdom and testing-library, configure eslint for
react-hooks v7 warnings, and add tokio test deps for Rust.
Outfit + JetBrains Mono typography, soft dark palette with blue
undertones, electric teal primary, purple-branded AI features,
noise texture, glow effects, glassmorphism, and refined grid/tree.
Use explicit ubuntu:22.04 container instead of marketplace actions
that may not work on self-hosted Gitea runners. Install Node.js and
Rust toolchain directly via curl/rustup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adapt GitHub Actions workflows for Gitea with Docker runner:
- Linux-only builds (no macOS/Windows matrix)
- Manual `npm run tauri build` instead of tauri-action
- Release creation via Gitea API with asset upload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add macOS Intel build (macos-13 / x86_64-apple-darwin) to CI matrix
- Add artifact upload step to CI build job
- Add release workflow triggered by v* tags with draft GitHub Release
- Add AppImage to Linux bundle targets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When creating/restoring snapshots with 4+ tables, the progress percentage
calculation overflowed u8 (e.g. 4*80=320 > 255), causing a panic that left
the IPC call unresolved. Also deduplicate fetch_foreign_keys_raw call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four new killer features leveraging AI (Ollama) and PostgreSQL internals:
- Data Validation: describe quality rules in natural language, AI generates
SQL to find violations, run with pass/fail results and sample violations
- Test Data Generator: right-click table to generate realistic FK-aware test
data with AI, preview before inserting in a transaction
- Index Advisor: analyze pg_stat tables + AI recommendations for CREATE/DROP
INDEX with one-click apply
- Data Snapshots: export selected tables to JSON (FK-ordered), restore from
file with optional truncate in a transaction
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix SQL injection in data.rs by wrapping get_table_data in READ ONLY transaction
- Fix SQL injection in docker.rs CREATE DATABASE via escape_ident
- Fix command injection in docker.rs by validating pg_version/container_name
and escaping shell-interpolated values
- Fix UTF-8 panic on stderr truncation with char_indices
- Wrap delete_rows in a transaction for atomicity
- Replace .expect() with proper error propagation in lib.rs
- Cache AI settings in AppState to avoid repeated disk reads
- Cap JSONB column discovery at 50 to prevent unbounded queries
- Fix ERD colorMode to respect system theme via useTheme()
- Extract AppState::get_pool() replacing ~19 inline pool patterns
- Extract shared AiSettingsFields component (DRY popover + sheet)
- Make get_connections_path pub(crate) and reuse from docker.rs
- Deduplicate check_docker by delegating to check_docker_internal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a table has no PRIMARY KEY, use PostgreSQL's ctid (physical row ID)
to identify rows for UPDATE/DELETE operations instead of blocking edits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add AppSettingsSheet (gear icon in Toolbar) with MCP, Docker, and AI sections
- MCP Server: toggle on/off, port config, status badge, endpoint URL with copy
- Docker: local/remote daemon selector with remote URL input
- AI: moved Ollama settings into the unified sheet
- MCP status probes actual TCP port for reliable running detection
- Docker commands respect configurable docker host (-H flag) for remote daemons
- MCP server supports graceful shutdown via tokio watch channel
- Settings persisted to app_settings.json alongside existing config files
- StatusBar shows MCP indicator (green/gray dot) with tooltip
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use bash with pipefail instead of sh to detect pg_dump failures in pipes
- Switch full clone from binary format (pg_dump -Fc | pg_restore) to plain
text (pg_dump | psql) for reliable transfer through docker exec
- Add --no-owner --no-acl flags to avoid errors from missing roles
- Extract shared run_pipe_cmd helper with proper error handling
- Remove shell commands from progress events to prevent credential leaks
- Fix process log layout overflow with break-all and block-level details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clone any database to a local Docker PostgreSQL container with schema
and/or data transfer via pg_dump. Supports three modes: schema only,
full clone, and sample data. Includes container lifecycle management
(start/stop/remove) in the Admin panel, progress tracking with
collapsible process log, and automatic connection creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add interactive ER diagram with ReactFlow + dagre auto-layout, accessible
via right-click context menu on schema nodes in the sidebar
- Enhance TableStructure: column comments, FK referenced table/columns,
ON UPDATE/DELETE rules, new Triggers tab
- Backend: rewrite get_table_constraints using pg_constraint for proper
composite FK support, add get_table_triggers and get_schema_erd commands
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Render all tabs simultaneously with display:none for inactive ones,
instead of conditionally rendering only the active tab. This prevents
React from unmounting/remounting components on tab switch, preserving
query results, editor cursor, AI explanations, and other local state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Radix ScrollArea with plain overflow-auto div to allow nested
horizontal scrolling in lookup result tables. Add overflow-auto to
table containers. Increase per-database search timeout from 30s to 120s.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add select-text to override global user-select:none on results table,
JSON view, error messages, and AI explanation. Also keep AI prompt text
after generation with a clear button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared call_ollama_chat helper from generate_sql to reuse
settings loading and Ollama API call logic. Add two new AI commands:
- explain_sql: explains what a SQL query does in plain language
- fix_sql_error: suggests corrected SQL based on the error and schema
UI additions: "AI Explain" toolbar button, "Explain" and "Fix with AI"
action buttons on query errors, inline explanation display in results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce unnecessary re-fetches on large DWH by caching schema/management
queries for 5 minutes, and add a refresh button in the sidebar to
force-reload when needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greenplum 7 (PG12-based) compatibility:
- Auto-detect GP via version() string, store DbFlavor per connection
- connect returns ConnectResult with version + flavor
- Fix pg_total_relation_size to use c.oid (universal, safer on both PG/GP)
- Branch is_identity column query for GP (lacks the column)
- Branch list_sessions wait_event fields for GP
- Exclude gp_toolkit schema in schema listing, completion, lookup, AI context
- Smart StatusBar version display: GP shows "GP 7.0.0 (PG 12.4)"
- Fix connection list spinner showing on all cards during connect
AI SQL generation (Ollama):
- Add AI settings, model selection, and generate_sql command
- Frontend AI panel with prompt input and SQL output
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>