* feat(core): implement PIP-0001 database connection timeout recovery
- Add DbOperationBudget struct for unified execution budget model
- Add deadpool create/recycle timeout for PostgreSQL pools
- Add checkout_postgres_client helper replacing 14 bare pool.get() calls
with timeout + cancel token support
- Fix PostgreSQL TLS cancel: rebuild TLS connector from stored context,
fallback to NoTls on failure
- Add timeout to MySQL kill_query and kill_query_with_opts (5s)
- Extend is_connection_error to match deadpool pool timeout messages
- Unify keepalive default to 30s (was 60s Rust / 0s frontend mismatch)
- Inject PostgreSQL TCP keepalive params (idle=30s, interval=10s, retries=3)
- Inject MySQL tcp_keepalive=30s in OptsBuilder
- Make MySQL inactive_connection_ttl configurable from idle_timeout_secs
- Add stage-based structured logging for pool.checkout/health.check/cancel
- Store PostgresCancelContext in AppState for TLS cancel reconstruction
- Clean up cancel contexts on pool removal/drain
- Add unit tests for DbOperationBudget and pool timeout error detection
* fix(core): align timeout recovery with PIP-0001
* fix(core): handle timeout recovery review issues
* fix(core): complete connection timeout recovery
* fix(core): fall back postgres cancel connector build
* refactor: revert comment translations to English in connection.rs and query.rs
- Revert DbOperationBudget doc comments to English (query.rs)
- Revert PostgresCancelContext and TLS cancel comments to English (connection.rs)
- Keep Chinese error-matching strings in is_connection_error unchanged
---------
Co-authored-by: onceMisery <onceMisery@users.noreply.github.com>
* fix(starrocks): detect primary keys via information_schema.COLUMNS
The Doris family (StarRocks/Doris) loaded columns through `SHOW COLUMNS`
for performance (perf(doris) commit 69ecde8f). However StarRocks reports
the `Key` column as `YES`/`NO` instead of MySQL's `PRI`, so `is_primary_key`
was never set and the data grid always showed "无主键定位" (keyless edit
warning) for tables that actually have a primary key.
Switch to `get_columns`, which queries information_schema.COLUMNS first —
where `COLUMN_KEY = 'PRI'` correctly identifies primary keys (and only real
primary keys, not duplicate-key sort columns) — and still falls back to
`SHOW COLUMNS` when information_schema is unavailable.
Verified against a live StarRocks connection: information_schema.COLUMNS
reports `COLUMN_KEY = 'PRI'` for primary-key columns while `SHOW COLUMNS`
reports `Key = YES`. Paimon catalog tables are unaffected (both paths
report no primary key for them).
* fix(test): add redis_scan_page_size to ConnectionConfig test helpers
`d815a16b` (feat(redis): move scan page size to connections) added
`redis_scan_page_size` to `ConnectionConfig` but did not update several
struct-literal test helpers, so `cargo clippy --workspace --all-targets`
fails with `missing field redis_scan_page_size`. This is a pre-existing
breakage on `main` (CI has been red since that commit) and is unrelated to
this PR's actual change; included here only to unblock CI.
Helpers fixed here:
- src-tauri/src/commands/connection.rs (`mongodb_config`)
- crates/dbx-web/src/routes/connection.rs (`sqlite_config`)
* fix(mysql): health-check pooled connection before DDL query
`mysql_ddl` fetched a connection with a raw `pool.get_conn()` and issued
`SHOW CREATE TABLE` directly, unlike other read paths (`get_columns`,
`list_indexes`, ...) which go through `get_conn_with_health_check`. A
pooled connection that went stale while idle (server closed it after its
idle timeout, NAT/firewall dropped the TCP state, ...) would then fail on
the first DDL request after a period of inactivity, surfacing a low-level
connection error that a manual refresh would mask.
Route `mysql_ddl` through `get_conn_with_health_check` so stale
connections are pinged and replaced before the query runs, matching the
rest of the MySQL metadata path.
* fix(test): add redis_scan_page_size to ConnectionConfig test helpers
`d815a16b` (feat(redis): move scan page size to connections) added
`redis_scan_page_size` to `ConnectionConfig` but did not update several
struct-literal test helpers, so `cargo clippy --workspace --all-targets`
fails with `missing field redis_scan_page_size`. This is a pre-existing
breakage on `main` (CI has been red since that commit) and is unrelated to
this PR's actual change; included here only to unblock CI.
Helpers fixed here:
- src-tauri/src/commands/connection.rs (`mongodb_config`)
- crates/dbx-web/src/routes/connection.rs (`sqlite_config`)
* fix(ai): resolve Codex CLI from shell PATH on macOS
* fix(ai): hide Codex CLI subprocess windows on Windows
* feat(ai): allow to set environment variable for Codex CLI
* fix(ai): parse Codex MCP tool events
* fix(mcp): honor Postgres TLS settings in direct queries
Problem: When exporting DISTINCT/GROUP BY query results from distributed
databases like Doris 2.1, LIMIT/OFFSET pagination without ORDER BY causes
duplicate rows across pages because tablet scan order varies between
independent query executions.
Fix: Detect dedup queries (DISTINCT, GROUP BY, HAVING) that lack an
ORDER BY clause and automatically inject a positional ORDER BY (1, 2, ..., N)
before applying LIMIT/OFFSET. Positional references work correctly with
aliases, expressions, CASE WHEN, subqueries, and aggregate functions.
Changes:
- Add dedup_projection_count_without_order_by() using sqlparser to detect
dedup queries and count projection columns
- Add ormat_positional_order_by() to generate ORDER BY 1, 2, ..., N
- Modify dd_standard_limit() to accept dedup_projection_count and inject
ORDER BY; wrap in subquery when original query already has LIMIT
- Update dd_outer_standard_limit() to accept an order_sql parameter
- Add 20 unit tests covering Doris, MySQL, PostgreSQL, aliases, expressions,
subqueries, GROUP BY, UNION, and existing ORDER BY scenarios
All 1203 tests pass (61 in query_result_sql, 11 in query_result_export).
StarRocks/Doris expose external storage through a catalog. dbx had no
catalog concept, so connecting to e.g. `paimon_catalog.clip` failed: the
URL path's database was sent as the MySQL handshake schema and rejected
before any `SET catalog` could run, and table listing/preview ran against
the default catalog.
Add an opt-in `catalog=<name>` URL parameter for mysql-family connections:
- `mysql_connection_catalog`: reads the `catalog` param (percent-decoded).
- `mysql_setup_queries`: emits `SET catalog = <name>` when present. Pushed
last so mysql_async's back-to-front setup execution (Vec::pop) runs it
before `USE <database>`, which would otherwise fail (the database lives
in the external catalog). The pool re-runs setup after each reset, so the
catalog stays current.
- `mysql_async_url`: when a catalog is configured, strip the database path
from the URL handed to mysql_async so the handshake does not send the
external-catalog database as the schema. The original URL (with path) is
still used for setup, so `USE <database>` is emitted correctly.
- Add `catalog` to `is_dbx_handled_mysql_url_param` so mysql_async ignores
the unknown param.
This is opt-in: only connections that set `catalog=` are affected; all
existing mysql/StarRocks/Doris connections behave unchanged. Configure a
Paimon connection as `database=clip`, `url_params=catalog=paimon_catalog`.
Verified against a StarRocks + Paimon catalog via the dbx MCP bridge:
SHOW DATABASES, SHOW TABLES, DESCRIBE, and unqualified `SELECT * FROM <t>`
all resolve in the selected catalog.