跪拜 Guibai
← All articles
Database · MySQL

FluxDB Rebuilds the Database Client in Rust + GPUI: Bounded Queries, Virtualized Tables, and Slot-Aware SQL Completion

By FluxDB ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Native Rust tooling now has a concrete reference for building database clients that stay responsive at scale without Electron's memory cost. The same patterns, from LIMIT n+1 to latest-wins completion, apply to any SQL editor or data grid that must handle large result sets gracefully.

Summary

FluxDB is a native cross-platform database client written in Rust and GPUI, with no Electron or WebView layer. It stays responsive because queries, rendering, completion, and writes have explicit boundaries. Queries get a default 100-row limit and a 16 MiB result memory budget; PostgreSQL pages fetch one extra row to detect has_more without COUNT(*); and the grid virtualizes both rows and columns so only visible cells become UI elements. Cell previews are clipped to 300 characters at character boundaries while full values remain available for editing, copying, and detail views.

SQL completion is the second major piece. Instead of matching text before the cursor against keywords, the fluxdb-completion crate parses the incomplete statement and locates a syntax slot. A tolerant parser masks the partial word with equal-length placeholders, lexical scopes handle aliases that shadow each other, and foreign-key metadata drives JOIN ... ON suggestions. A LineColIndex maps parser character positions to byte offsets so Chinese and emoji don't corrupt offsets. The pipeline is bounded: 64 KiB window around the cursor, 200 candidates, 75 ms debounce, and a latest-wins adoption guard that drops stale results.

Editing is staged locally in a DataChangeSet, then committed inside one transaction in delete, update, insert order. Updates and deletes must affect exactly one row or the batch rolls back. Performance budgets are explicit in code: background work like completion targets 100 ms, frame-sensitive work targets 16 ms, and large documents degrade syntax highlighting to lexical analysis. The result is a working template for keeping a native database client fast without hiding data loss behind silent truncation.

Takeaways
— Query results are bounded at the source: a default 100-row limit, injected LIMIT clauses, and SQL Server's TOP or OFFSET ... FETCH; a user can explicitly choose no limit.
— PostgreSQL data pages fetch one extra row (LIMIT n+1) solely to set has_more, avoiding COUNT(*) for the next-page button.
— Pagination appends a primary-key tie-breaker after user sorting so non-unique sort columns don't cause duplicate or missing rows.
— A 16 MiB result memory budget stops oversized rows from exhausting process memory; truncation sets has_more instead of silently dropping data.
— The data grid virtualizes rows and columns with uniform_list, and cell previews are clipped to 300 characters at character boundaries; full values load on demand for edit, copy, and details.
— Filters and sorts are pushed down as parameterized SQL; invalid operations like missing BETWEEN endpoints or empty IN lists fail explicitly instead of silently dropping conditions.
— Local edits stage in a DataChangeSet with inserts, updates, deletes, and a three-state insert intent that distinguishes default, NULL, and explicit value.
— Commits run in one transaction in delete, update, insert order; updates and deletes must affect exactly one row or the entire batch rolls back.
— Completion candidates are chosen by syntax slot, not prefix match; a tolerant parser masks the partial word at the cursor with equal-length x's to keep AST offsets valid.
— Scoped symbols make inner aliases shadow outer aliases, sibling subqueries invisible to each other, and CTEs separate from physical tables.
— Foreign-key metadata narrows JOIN ON candidates to paired columns, and same-named columns across tables get qualified labels like u.id and o.id.
— The completion index uses a 30-minute TTL with persisted snapshots and stale-while-refresh; databases over 500 tables preload only table names, and analysis covers a 64 KiB window around the cursor with a 200-candidate cap.
— Auto-completion debounces at 75 ms and adopts results only when the request ID is latest, the buffer version is unchanged, and the edit session hasn't expired.
— Performance budgets are explicit: background tasks like syntax, diagnostics, hover, and completion target 100 ms; frame-sensitive tasks like inlay, codelens, wrap, and signature target 16 ms.
— Documents over 512 KiB downgrade syntax highlighting to lexical analysis and use a larger diagnostics debounce.
Conclusions

Bounding every layer is what makes the client feel fast; once query size, rendered rows, candidate count, and analysis window have explicit caps, performance becomes diagnosable rather than mysterious.

The three-state WriteValue distinction is a small modeling decision with large consequences: treating NULL as omitted is a common source of data corruption in database tools, and preserving intent until commit avoids it.

Completion design that assumes SQL is always incomplete while typing, and parses anyway by masking the cursor word, is more robust than falling back to prefix matching on parse failure.

Because GPUI Tasks can't kill a running future, the UI relies on adoption guards; async cancellation in native UI frameworks often needs explicit result validation, not just dropping a task.

The 500-table cutoff and 64 KiB window trade metadata completeness for startup and keystroke latency; large databases pay for columns only when a table is actually referenced.

Concepts & terms
GPUI
A native UI framework for Rust developed for the Zed code editor. It handles rendering and interaction without a web runtime; FluxDB uses it as the desktop layer.
Tolerant parsing
Parsing SQL that isn't valid yet by masking the partial word at the cursor with equal-length placeholders, so the parser can build an AST while offsets stay aligned with the original text.
Slot-based completion
A completion strategy that first determines which grammar position the cursor occupies, such as table name, column name, or JOIN condition, and then offers candidates valid for that position instead of prefix-matching keywords.
Stale-while-refresh
A caching strategy where expired data continues to serve reads while a background task refreshes it; FluxDB applies this to its completion metadata index.
Latest-wins
A concurrency rule for async UI requests: only the result of the most recent request may be applied, and older responses are discarded even if they arrive later.
LIMIT n+1
A pagination technique that fetches one extra row beyond the page size; if the extra row exists, there is a next page, avoiding a separate COUNT(*) query.
From the discussion
Featured comments
亚雷

How does pagination continue for an extra-large single row?

FluxDB

Currently there is a 16 MiB limit; if exceeded, it prompts truncation. Large fields will be specifically optimized later.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗