驱动接入(全部本机真实例验收): - 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 同步
95 lines
2.8 KiB
Go
95 lines
2.8 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// newSQLiteFile 建一个 sqlite 测试库文件并执行建表/插数语句,返回 ConnReq。
|
|
func newSQLiteFile(t *testing.T, stmts ...string) ConnReq {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "test.db")
|
|
if err := os.WriteFile(path, []byte{}, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, s := range stmts {
|
|
res := ExecSQL(context.Background(), ConnReq{Driver: "sqlite", Database: path, SQL: s})
|
|
if res.Error != "" {
|
|
t.Fatalf("初始化语句失败 %q: %s", s, res.Error)
|
|
}
|
|
}
|
|
return ConnReq{Driver: "sqlite", Database: path}
|
|
}
|
|
|
|
// R2-B5:sqlite 返回 sqlite_master 中的原始建表语句。
|
|
func TestGetDDLSQLite(t *testing.T) {
|
|
c := newSQLiteFile(t, `CREATE TABLE t_person (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER)`)
|
|
c.Table = "t_person"
|
|
ddl, err := GetDDL(context.Background(), c)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(ddl, "CREATE TABLE t_person") || !strings.Contains(ddl, "name TEXT") {
|
|
t.Fatalf("DDL 内容不符: %s", ddl)
|
|
}
|
|
|
|
c.Table = "no_such_table"
|
|
if _, err := GetDDL(context.Background(), c); err == nil {
|
|
t.Fatal("不存在的表应报错")
|
|
}
|
|
}
|
|
|
|
func TestGetDDLRedis(t *testing.T) {
|
|
_, err := GetDDL(context.Background(), ConnReq{Driver: "redis", Table: "k"})
|
|
if err == nil || !strings.Contains(err.Error(), "不支持") {
|
|
t.Fatalf("redis 应报不支持: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGetDDLMissingTable(t *testing.T) {
|
|
_, err := GetDDL(context.Background(), ConnReq{Driver: "sqlite", Table: ""})
|
|
if err == nil {
|
|
t.Fatal("空表名应报错")
|
|
}
|
|
}
|
|
|
|
// R2-B5:pg/mssql/oracle 族用列元数据拼装,各 family 引号不同。
|
|
func TestBuildDDLFromColumns(t *testing.T) {
|
|
cols := []ColumnInfo{
|
|
{Name: "id", Type: "integer", Nullable: false, Key: "PRI"},
|
|
{Name: "na\"me", Type: "text", Nullable: true},
|
|
}
|
|
// pg:双引号加倍
|
|
ddl, err := buildDDLFromColumns("postgres", "public.t", cols)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(ddl, `"public"."t"`) || !strings.Contains(ddl, `"na""me" text`) ||
|
|
!strings.Contains(ddl, `"id" integer NOT NULL,`) || !strings.Contains(ddl, `PRIMARY KEY ("id")`) {
|
|
t.Fatalf("pg DDL 不符:\n%s", ddl)
|
|
}
|
|
// mssql:中括号
|
|
ddl, err = buildDDLFromColumns("mssql", "dbo.t", cols)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(ddl, `[id] integer NOT NULL`) || !strings.Contains(ddl, `PRIMARY KEY ([id])`) {
|
|
t.Fatalf("mssql DDL 不符:\n%s", ddl)
|
|
}
|
|
// oracle:原名
|
|
ddl, err = buildDDLFromColumns("oracle", "T", cols)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE TABLE T (`) || !strings.Contains(ddl, `PRIMARY KEY (id)`) {
|
|
t.Fatalf("oracle DDL 不符:\n%s", ddl)
|
|
}
|
|
// 无列报错
|
|
if _, err := buildDDLFromColumns("postgres", "t", nil); err == nil {
|
|
t.Fatal("无列应报错")
|
|
}
|
|
}
|