feat: 18 引擎驱动全家桶 + 驱动管理/离线包 + 设置中心 + 工作台交互打磨
CI / Go server (push) Canceled after 0s
CI / Web + core packages (push) Canceled after 0s
CI / Desktop (Tauri) cargo check (push) Canceled after 0s

驱动接入(全部本机真实例验收):
- Elasticsearch 原生 SQL API、InfluxDB 原生 HTTP+Flux(官方 JDBC 付费许可,实测弃用)
- ZooKeeper/MongoDB JVM agent(连接时自动下载官方驱动 jar,sha256 钉版本到 ~/.opsxdb/drivers/)
- 达梦 JDBC agent 桥就绪(待厂商 jar+实例);TiDB 真实例验收
- 协议族代码审查放行 PolarDB/TDSQL/OceanBase/Kingbase/GaussDB/Oracle
- 驱动能力模型:按驱动开关对象树分组/右键菜单/行编辑,NoSQL 树拍平(对象直挂库节点)

驱动管理:
- 驱动管理弹框(状态/预装/离线包导入/JDK 检测)+ /api/drivers/* 三接口
- opsxdb-driver 驱动商店仓库(清单/fetch.sh/pack.sh 离线包/import.sh)
- 连接时懒加载:DMG 零 Java 产物,首连自动下载(实测 ZK 九件套 1.35s),离线包自动回退

UI/交互:
- 设置中心(⚙):常规/执行/结果/驱动/快捷键/关于,左导航+右面板(对标 opsxshell)
- 实例级展开箭头(行首 SVG chevron,替代字体回退失真的 Unicode 三角)
- 树内嵌套渲染修复:对象树挂在激活连接下;工具栏窄窗口不断行(按钮 nowrap+整颗换行)
- 侧栏连接行精简(图标+连接名)、SQLite SSH 守卫、Oracle 死参数清理

质量:
- Go 协议层/离线回退单测、E2E 门控测试(ES/ZK/Influx/Mongo/TiDB/JDBC 桥)
- 驱动注册表单一事实来源 connection-types.json,README/BACKLOG/AGENTS 同步
This commit is contained in:
logan committed 2026-09-22 01:07:46 +08:00
1 parent 26f459d1b8
commit a9510235bb
156 files changed
+17200 -2520

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
package db
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"strings"
"testing"
)
func newExportConn(t *testing.T) ConnReq {
return newSQLiteFile(t,
`CREATE TABLE t_export (id INTEGER PRIMARY KEY, name TEXT, note TEXT)`,
`INSERT INTO t_export (id, name, note) VALUES (1, 'alice', 'hi, "q"')`,
`INSERT INTO t_export (id, name, note) VALUES (2, 'bo''b', NULL)`,
`INSERT INTO t_export (id, name, note) VALUES (3, 'carol', 'line1`+"\n"+`line2')`,
)
}
// R2-B7:csv 输出形状(表头 + csv 引号转义由 encoding/csv 负责)。
func TestExportCSV(t *testing.T) {
c := newExportConn(t)
c.SQL = `SELECT id, name, note FROM t_export ORDER BY id`
c.Format = "csv"
var buf bytes.Buffer
if err := ExportQuery(context.Background(), c, &buf); err != nil {
t.Fatal(err)
}
out := buf.String()
if !strings.HasPrefix(out, "id,name,note\n") {
t.Fatalf("csv 表头错误: %q", out)
}
// 含逗号/引号的字段整体加引号、内部引号 doubling
if !strings.Contains(out, `"hi, ""q"""`) {
t.Fatalf("csv 转义错误: %q", out)
}
// 单引号 doubling 是 SQL 字面量层;csv 层原样输出 'bo''b' 存好的 bo'b
if !strings.Contains(out, "2,bo'b,") {
t.Fatalf("csv 内容错误: %q", out)
}
// 多行字面量会使单条记录跨物理行,用 csv.Reader 按记录解析
recs, err := csv.NewReader(strings.NewReader(out)).ReadAll()
if err != nil {
t.Fatalf("csv 输出应可解析: %v", err)
}
if len(recs) != 4 { // 表头 + 3 行
t.Fatalf("应为表头+3 行: %q", out)
}
}
func TestExportJSON(t *testing.T) {
c := newExportConn(t)
c.SQL = `SELECT id, name, note FROM t_export ORDER BY id`
c.Format = "json"
var buf bytes.Buffer
if err := ExportQuery(context.Background(), c, &buf); err != nil {
t.Fatal(err)
}
var rows []map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &rows); err != nil {
t.Fatalf("json 输出应可解析: %v\n%s", err, buf.String())
}
if len(rows) != 3 {
t.Fatalf("应为 3 行: %v", rows)
}
if rows[0]["name"] != "alice" || rows[1]["note"] != nil {
t.Fatalf("行值错误: %v", rows)
}
}
func TestExportInsert(t *testing.T) {
c := newExportConn(t)
c.SQL = `SELECT id, name, note FROM t_export ORDER BY id`
c.Format = "insert"
c.Table = "t_export"
var buf bytes.Buffer
if err := ExportQuery(context.Background(), c, &buf); err != nil {
t.Fatal(err)
}
out := buf.String()
if !strings.Contains(out, `INSERT INTO "t_export" ("id", "name", "note") VALUES (1, 'alice', 'hi, "q"');`) {
t.Fatalf("insert 首行错误: %q", out)
}
// 字符串单引号 doubling;NULL 原样
if !strings.Contains(out, `VALUES (2, 'bo''b', NULL);`) {
t.Fatalf("insert 字面量错误: %q", out)
}
// 多行字面量内换行原样保留
if !strings.Contains(out, "line1\nline2") {
t.Fatalf("insert 换行字面量错误: %q", out)
}
}
// limit 生效(含默认上限收敛)。
func TestExportLimit(t *testing.T) {
c := newExportConn(t)
c.SQL = `SELECT id FROM t_export ORDER BY id`
c.Format = "csv"
c.Limit = 2
var buf bytes.Buffer
if err := ExportQuery(context.Background(), c, &buf); err != nil {
t.Fatal(err)
}
rows := strings.Split(strings.TrimSpace(buf.String()), "\n")
if len(rows) != 3 { // 表头 + 2 行
t.Fatalf("limit=2 应只导 2 行: %q", buf.String())
}
}
func TestExportAllowedQuery(t *testing.T) {
for _, ok := range []string{
"SELECT * FROM t", "WITH x AS (SELECT 1) SELECT * FROM x",
"SHOW TABLES", "DESCRIBE t", "EXPLAIN SELECT 1",
"-- c\nSELECT 1",
} {
if !ExportAllowedQuery(ok) {
t.Fatalf("应允许只读: %q", ok)
}
}
for _, bad := range []string{
"DELETE FROM t", "INSERT INTO t VALUES (1)", "DROP TABLE t",
"UPDATE t SET a=1", "", " ",
} {
if ExportAllowedQuery(bad) {
t.Fatalf("不应允许: %q", bad)
}
}
}
func TestExportBadFormat(t *testing.T) {
c := newExportConn(t)
c.SQL = "SELECT 1"
c.Format = "xml"
var buf bytes.Buffer
if err := ExportQuery(context.Background(), c, &buf); err == nil {
t.Fatal("未知格式应报错")
}
}