Files
opsxdb/server/internal/db/ddl.go
T

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
}