122 lines
3.3 KiB
Go
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
|
|
}
|