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
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@ Navop user-facing release notes. Generate and review each bilingual version entr

<!-- NAVOP_RELEASES -->

## [v0.18.6] - 2026-09-21

#### 更新内容

- 数据库对象列表支持全选与拖选。表、视图、存储过程等对象现在可以 Shift 点击或用鼠标拖选多行,Cmd/Ctrl 拖选在已有选择上继续追加,来回拖动区间可正常收缩。修掉了两处交互问题:在列表外松开鼠标时不再沿用旧锚点截出错误区间;Shift 点击只把目标行滚动到最近位置,不会再把可见行整块顶走。
- PostgreSQL 支持外部表和物化视图。此前 PG 连接的表目录写死只列普通表,外部表与分区表根本列不出来,物化视图也没有入口(视图列表不含它),用户已经建好的对象在 Navop 里等于不存在。现在外部表与普通表、分区表同处「表」目录,类型列分别显示 Table / Partitioned Table / Foreign Table,物化视图单独一个目录;重命名、清空、删除、转储等右键动作按对象类型分别生成 SQL,非 public schema 下也带 schema 限定名,不会把 `DROP TABLE` 打到外部表上。外部表不参与结构比较、数据比较、ER 图与整库 DDL 转储(数据转储仍可用)。

#### 修复与优化

- 修复 SQL 页签无法并发执行的问题。A 页签执行时 B 页签就连不上,即使 B 连的是另一台库——根因是整张会话表共用一把锁,取到连接后要一直持有到语句执行结束,等于所有页签排队。会话池的复用与释放还有三处并发缺陷:释放与下一条语句交错时会把正在执行的会话标成空闲,随后被清理回收(报 session not found);复用扫描一个正在跑长语句的会话会连带堵住其它页签;会话被并发摘除时会重复断开连接。现在锁粒度降到每个会话各自的连接,会话状态查询不再等待执行中的语句,复用与生命周期按引用计数同步,一条语句恰好占用与释放一次。同一会话内仍保持互斥。
- 修复 MySQL 文本列被显示成二进制的问题。连接某些 MySQL 兼容实现或代理时,结果列元数据里的字符集为 0,取不到解码器就一律降级成二进制,文本列在网格里显示为「二进制 · N B」,表结构元数据、自定义 SQL 与 UNION ALL 结果同样受影响,而这些字节其实是合法 UTF-8。现在字符集未知时按严格 UTF-8 兜底解码,非法 UTF-8 或含控制字符的字节仍保留二进制。
- 修复 SSH 目录上传小文件过慢的问题。上传每个文件固定要付 6 次控制往返(stat / open / fsync / fstat / close / rename),串行执行时吞吐由网络往返延迟决定而不是带宽,且任何时刻只有一个文件在传。现在小文件走受限并发(并发度 6),超过 512 KiB 的文件独占预算、行为与串行时一致,不会在内存里堆积未确认字节;失败语义不变,出错后不再接纳新任务、等在飞任务跑完再返回,避免残留暂存文件。进度累计改为全局原子量,并发上报时不再回跳。
- 修复 macOS 上关闭内置远程编辑器导致应用崩溃的问题。Intel Touch Bar 机型上关闭编辑器弹窗必现崩溃:原生窗口被销毁后,系统在显示周期里注销 Touch Bar 观察者时抛出未捕获异常,进程被直接终止。现在 macOS 上关闭不再销毁原生窗口,改为隐藏并复用,下次打开直接复用该窗口;同时把连接改为按标签持有,修掉跨会话复用会走错连接的问题,旧弹窗的确认也不会作用到复用后的新会话上。

国内下载:如果 GitHub 下载较慢,可从 [CNB 镜像](https://cnb.cool/navop-dev/navop/-/releases/tag/v0.18.6) 下载桌面端安装包

---

#### What's New

- Database object lists now support select all and drag-select. Tables, views and stored procedures can be Shift-clicked or drag-selected as multiple rows, Cmd/Ctrl drag-select adds to the existing selection, and dragging back and forth shrinks the range as expected. Two interaction bugs are fixed as well: releasing the mouse outside the list no longer reuses a stale anchor and cuts out a wrong range, and Shift-click scrolls the target row just into view instead of pushing the visible rows away.
- PostgreSQL foreign tables and materialized views are now supported. The table catalogue for PG connections was hard-coded to ordinary tables only, so foreign and partitioned tables simply never showed up, and materialized views had no entry point at all because the view list does not include them — objects the user had already created did not exist in Navop. Foreign tables now live in the same "Tables" folder as ordinary and partitioned tables, with the type column showing Table / Partitioned Table / Foreign Table, and materialized views get their own folder. Rename, truncate, drop and dump actions generate SQL according to the object type and are schema-qualified outside `public`, so `DROP TABLE` is never issued against a foreign table. Foreign tables are excluded from schema comparison, data comparison, ER diagrams and whole-database DDL dumps (data dumps still work).

#### Fixes and Improvements

- Fixed SQL tabs not being able to execute concurrently. While one tab was running, no other tab could execute even when connected to a different database: the whole session table shared a single lock, held from acquiring the connection until the statement finished, so every tab queued behind it. The session pool also had three concurrency defects: releasing a session interleaved with the next statement could mark a running session as idle, which was then reclaimed by the idle sweep (surfacing as "session not found"); scanning for a reusable session blocked other tabs when it hit one running a long statement; and a concurrently detached session could be disconnected twice. Locking is now per-session on the connection itself, session state queries no longer wait for a running statement, and reuse and lifecycle are synchronized by reference counting so a statement acquires and releases exactly once. Mutual exclusion within the same session is preserved.
- Fixed MySQL text columns being displayed as binary. When connecting to some MySQL-compatible implementations or proxies, the character set in the result column metadata is 0, so with no decoder available every column was downgraded to binary and text columns showed up as "二进制 · N B" in the grid. Table structure metadata, custom SQL and UNION ALL results were affected in the same way, even though the bytes were valid UTF-8. Unknown character sets now fall back to strict UTF-8 decoding; bytes that are not valid UTF-8 or contain control characters still stay binary.
- Fixed SSH directory uploads being slow with many small files. Every uploaded file costs six control round trips (stat / open / fsync / fstat / close / rename), so serial execution made throughput depend on round-trip latency rather than bandwidth, and only one file was ever in flight. Small files now use bounded concurrency (6 in flight), while files above 512 KiB take the whole budget and behave exactly as before, so unacknowledged bytes are not piled up in memory. Failure semantics are unchanged: after the first error no new task is accepted and in-flight ones are awaited before returning, which keeps temporary files from being left behind. Progress accumulation is now a global atomic counter, so concurrent reports no longer jump backwards.
- Fixed the app crashing on macOS when closing the built-in remote editor. On Intel Touch Bar models closing the editor dialog crashed every time: after the native window was destroyed, the system unregistered the Touch Bar observer during a display cycle and threw an uncaught exception, terminating the process. Closing no longer destroys the native window on macOS — it is hidden and reused, and reopening reuses that window. Connections are now held per tab, fixing cross-session reuse hitting the wrong connection, and a confirmation from a previous dialog can no longer act on the reused session.

**Full Changelog**: https://github.com/feigeCode/navop/compare/v0.18.5...v0.18.6

## [v0.18.5] - 2026-09-20

#### 修复与优化
Expand Down
6 changes: 5 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/db/locales/db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,10 @@ ObjectView:
en: "%{count} view(s)"
zh-CN: "%{count} 个视图"
zh-HK: "%{count} 個檢視"
materialized_views:
en: "%{count} materialized view(s)"
zh-CN: "%{count} 个物化视图"
zh-HK: "%{count} 個實體化檢視"
functions:
en: "%{count} function(s)"
zh-CN: "%{count} 个函数"
Expand Down
3 changes: 3 additions & 0 deletions crates/db/src/compare/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ pub fn table_schema_from_metadata(
object_type: match table.object_type {
TableObjectType::Table => SchemaObjectType::Table,
TableObjectType::View => SchemaObjectType::View,
// 外部表不在结构比较范围内(调用方按 `is_ddl_comparable` 过滤),
// 万一传入也按表处理,避免凭空生成新对象类型。
TableObjectType::ForeignTable => SchemaObjectType::Table,
},
columns: columns
.into_iter()
Expand Down
113 changes: 107 additions & 6 deletions crates/db/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::runtime_contract::require_tokio_runtime;
use crate::sqlite::SqlitePlugin;
use crate::{
DbNode, DbNodeType, ExecOptions, SqlErrorInfo, SqlResult, SqlSource, TableDesign,
TableSaveResponse,
TableObjectType, TableSaveResponse,
};
use dashmap::DashMap;
use gpui::{AppContext, AsyncApp, Global, Task};
Expand Down Expand Up @@ -1612,12 +1612,40 @@ impl GlobalDbState {
database: String,
schema: Option<String>,
table_name: String,
) -> anyhow::Result<SqlResult> {
self.drop_table_like(
cx,
config_id,
database,
schema,
table_name,
TableObjectType::Table,
)
.await
}

/// 删除「表」目录下的对象。
///
/// 外部表必须使用 `DROP FOREIGN TABLE`,否则 PostgreSQL 会拒绝执行。
pub async fn drop_table_like(
&self,
cx: &mut AsyncApp,
config_id: String,
database: String,
schema: Option<String>,
table_name: String,
object_type: TableObjectType,
) -> anyhow::Result<SqlResult> {
let mut config = self
.get_config(&config_id)
.ok_or_else(|| anyhow::anyhow!("Connection not found: {}", config_id))?;
let plugin = self.get_plugin(&config.database_type)?;
let sql = plugin.drop_table(&database, schema.as_deref(), &table_name);
let sql = match object_type {
TableObjectType::ForeignTable => {
plugin.drop_foreign_table(&database, schema.as_deref(), &table_name)
}
_ => plugin.drop_table(&database, schema.as_deref(), &table_name),
};

// For non-Oracle databases, modify config.database to switch database
if config.database_type != DatabaseType::Oracle {
Expand Down Expand Up @@ -1678,12 +1706,64 @@ impl GlobalDbState {
database: String,
old_name: String,
new_name: String,
) -> anyhow::Result<SqlResult> {
self.rename_table_like(
cx,
config_id,
database,
None,
old_name,
new_name,
TableObjectType::Table,
)
.await
}

/// 重命名「表」目录下的对象(外部表需 `ALTER FOREIGN TABLE`)。
pub async fn rename_table_like(
&self,
cx: &mut AsyncApp,
config_id: String,
database: String,
schema: Option<String>,
old_name: String,
new_name: String,
object_type: TableObjectType,
) -> anyhow::Result<SqlResult> {
let mut config = self
.get_config(&config_id)
.ok_or_else(|| anyhow::anyhow!("Connection not found: {}", config_id))?;
let plugin = self.get_plugin(&config.database_type)?;
let sql = plugin.rename_table(&database, &old_name, &new_name);
let sql = match object_type {
TableObjectType::ForeignTable => {
plugin.rename_foreign_table(&database, schema.as_deref(), &old_name, &new_name)
}
_ => plugin.rename_table(&database, &old_name, &new_name),
};

if config.database_type != DatabaseType::Oracle {
config.database = Some(database);
}

let result = self.execute_with_session(cx, config, sql, None).await?;

Self::wrapper_operation_result(result)
}

/// 删除物化视图(`DROP MATERIALIZED VIEW`)。
pub async fn drop_materialized_view(
&self,
cx: &mut AsyncApp,
config_id: String,
database: String,
schema: Option<String>,
view_name: String,
) -> anyhow::Result<SqlResult> {
let mut config = self
.get_config(&config_id)
.ok_or_else(|| anyhow::anyhow!("Connection not found: {}", config_id))?;
let plugin = self.get_plugin(&config.database_type)?;
let sql = plugin.drop_materialized_view(&database, schema.as_deref(), &view_name);

if config.database_type != DatabaseType::Oracle {
config.database = Some(database);
Expand Down Expand Up @@ -3049,6 +3129,21 @@ impl GlobalDbState {
})
}

/// List materialized views(仅 PostgreSQL 等支持物化视图的数据库)
pub async fn list_materialized_views_view(
&self,
cx: &mut AsyncApp,
connection_id: String,
database: String,
schema: Option<String>,
) -> anyhow::Result<crate::types::ObjectView> {
with_plugin_session_db!(self, cx, connection_id, database.clone(), |plugin, conn| {
plugin
.list_materialized_views_view(&*conn, &database, schema)
.await
})
}

/// List functions view
pub async fn list_functions_view(
&self,
Expand Down Expand Up @@ -3305,11 +3400,17 @@ impl GlobalDbState {
.list_tables_view(&*conn, &database, schema)
.await
.ok(),
DbNodeType::Table | DbNodeType::ColumnsFolder => plugin
.list_columns_view(&*conn, &database, schema, &table)
DbNodeType::Table | DbNodeType::ForeignTable | DbNodeType::ColumnsFolder => {
plugin
.list_columns_view(&*conn, &database, schema, &table)
.await
.ok()
}
DbNodeType::ViewsFolder => plugin.list_views_view(&*conn, &database).await.ok(),
DbNodeType::MaterializedViewsFolder => plugin
.list_materialized_views_view(&*conn, &database, schema)
.await
.ok(),
DbNodeType::ViewsFolder => plugin.list_views_view(&*conn, &database).await.ok(),
DbNodeType::FunctionsFolder => plugin
.list_functions_view_in_schema(&*conn, &database, schema)
.await
Expand Down
1 change: 1 addition & 0 deletions crates/db/src/mysql/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ fn build_mysql_ui_manifest() -> DatabaseUiManifest {
supports_schema: false,
uses_schema_as_database: false,
supports_views: true,
supports_materialized_views: false,
supports_indexes: true,
supports_users: true,
supports_user_create: true,
Expand Down
Loading
Loading