package db import ( "context" "fmt" "io" "strings" "time" ) // DumpMode 表转储范围:仅结构 / 仅数据 / 结构+数据(对齐 Navicat「转储 SQL 文件」三态)。 const ( DumpStructure = "structure" DumpData = "data" DumpBoth = "structure_data" ) // DumpTable 把单表转储为 .sql 写入 w(流式,数据部分复用 ExportQuery 的 insert 编码)。 // 不自动附带 DROP TABLE,避免导入时误删既有表;注释头标注来源与时间便于追溯。 func DumpTable(ctx context.Context, c ConnReq, w io.Writer) error { mode := strings.ToLower(strings.TrimSpace(c.Format)) if mode == "" { mode = DumpBoth } if mode != DumpStructure && mode != DumpData && mode != DumpBoth { return fmt.Errorf("mode 仅支持 structure/data/structure_data") } if strings.TrimSpace(c.Table) == "" { return fmt.Errorf("缺少表名") } // table 支持 "库.表" 形式:拆出 database,避免整体被引号包住导致 No database selected if i := strings.Index(c.Table, "."); i > 0 { if strings.TrimSpace(c.Database) == "" { c.Database = c.Table[:i] } c.Table = c.Table[i+1:] } // 数据部分复用 insert 编码:值拷贝整个 ConnReq,只覆盖导出相关字段 dataReq := c dataReq.Format = "insert" dataReq.SQL = SelectAllSQL(c.Driver, c.Table) if mode == DumpData { return ExportQuery(ctx, dataReq, w) } var b strings.Builder fmt.Fprintf(&b, "-- OpsxDB 表转储 %s\n-- 表: %s\n-- 时间: %s\n\n", modeLabel(mode), c.Table, time.Now().Format("2006-01-02 15:04:05")) ddl, err := GetDDL(ctx, c) if err != nil { return err } b.WriteString(strings.TrimRight(ddl, ";\n")) b.WriteString(";\n") if mode == DumpBoth { b.WriteString("\n") } if _, err := io.WriteString(w, b.String()); err != nil { return err } if mode == DumpBoth { if err := ExportQuery(ctx, dataReq, w); err != nil { return err } } return nil } func modeLabel(mode string) string { switch mode { case DumpStructure: return "· 仅结构" case DumpData: return "· 仅数据" default: return "· 结构和数据" } }