Files
opsxdb/apps/server/internal/db/ddl.go
T
logan a9510235bb
CI / Go server (push) Canceled after 0s
CI / Web + core packages (push) Canceled after 0s
CI / Desktop (Tauri) cargo check (push) Canceled after 0s
feat: 18 引擎驱动全家桶 + 驱动管理/离线包 + 设置中心 + 工作台交互打磨
驱动接入(全部本机真实例验收):
- 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 同步
2026-09-22 01:07:46 +08:00

122 lines
3.3 KiB
Go

package db
import (
"context"
"database/sql"
"fmt"
"strings"
)
// GetDDL 返回表/视图的建表语句。mysql 族走 SHOW CREATE TABLE,sqlite 读 sqlite_master,
// pg/mssql/oracle 用 ListColumns 元数据在 Go 侧拼装 CREATE TABLE。
func GetDDL(ctx context.Context, c ConnReq) (string, error) {
ctx = reqCtx(ctx)
if c.Table == "" {
return "", fmt.Errorf("缺少表名")
}
if isRedis(c.Driver) {
return "", fmt.Errorf("该驱动不支持 DDL 查看")
}
// pg/mssql/oracle:复用列元数据拼装,避免再查一次库
switch {
case isPostgresFamily(c.Driver), isMSSQL(c.Driver), driverKey(c.Driver) == "oracle":
cols, err := ListColumns(ctx, c)
if err != nil {
return "", err
}
return buildDDLFromColumns(c.Driver, c.Table, cols)
}
var out string
err := withDB(c, func(db *sql.DB) error {
var err error
switch {
case isMySQLFamily(c.Driver):
out, err = getDDLMySQL(ctx, db, c)
case driverKey(c.Driver) == "sqlite":
out, err = getDDLSQLite(ctx, db, c.Table)
default:
err = unsupportedDriverErr(c)
}
return err
})
if err != nil {
return "", err
}
return out, nil
}
// getDDLMySQL SHOW CREATE TABLE 第二列即 DDL(列名随对象类型变化:Create Table/Create View)。
func getDDLMySQL(ctx context.Context, dbh *sql.DB, c ConnReq) (string, error) {
ident := quoteMySQLIdent(c.Table)
if c.Database != "" {
ident = quoteMySQLIdent(c.Database) + "." + ident
}
rows, err := dbh.QueryContext(ctx, "SHOW CREATE TABLE "+ident)
if err != nil {
return "", err
}
defer rows.Close()
if rows.Next() {
var name string
var ddl sql.NullString
if err := rows.Scan(&name, &ddl); err != nil {
return "", err
}
if !ddl.Valid {
return "", fmt.Errorf("无法读取 %s 的 DDL", c.Table)
}
return ddl.String, nil
}
if err := rows.Err(); err != nil {
return "", err
}
return "", fmt.Errorf("表 %s 不存在", c.Table)
}
// getDDLSQLite sqlite_master 的 sql 列即建表语句。
func getDDLSQLite(ctx context.Context, dbh *sql.DB, table string) (string, error) {
var ddl sql.NullString
err := dbh.QueryRowContext(ctx, `SELECT sql FROM sqlite_master WHERE name = ?`, table).Scan(&ddl)
if err == sql.ErrNoRows {
return "", fmt.Errorf("表 %s 不存在", table)
}
if err != nil {
return "", err
}
if !ddl.Valid {
return "", fmt.Errorf("表 %s 无 DDL 记录", table)
}
return ddl.String, nil
}
// buildDDLFromColumns 用列元数据拼装 CREATE TABLE(类型直接用 ColumnInfo.Type)。
func buildDDLFromColumns(driver, table string, cols []ColumnInfo) (string, error) {
if len(cols) == 0 {
return "", fmt.Errorf("表 %s 不存在或无列", table)
}
var pks []string
for _, col := range cols {
if col.Key == "PRI" {
pks = append(pks, quoteIdentFor(driver, col.Name))
}
}
var b strings.Builder
fmt.Fprintf(&b, "CREATE TABLE %s (\n", quoteTableNameFor(driver, table))
for i, col := range cols {
line := " " + quoteIdentFor(driver, col.Name) + " " + col.Type
if !col.Nullable {
line += " NOT NULL"
}
// 非最后一列、或后面还有 PRIMARY KEY 子句时需要逗号
if i < len(cols)-1 || len(pks) > 0 {
line += ","
}
b.WriteString(line + "\n")
}
if len(pks) > 0 {
b.WriteString(" PRIMARY KEY (" + strings.Join(pks, ", ") + ")\n")
}
b.WriteString(");\n")
return b.String(), nil
}