feat(street): 草稿图支持主副分组(并入/并入上一张/拆出)
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
// **单个纯 SQL 文件**,拷到另一台机器后用 dbtool 或 psql 灌入即可 —— 无需 pg_dump。
|
||||
//
|
||||
// dbtool dump [-config <yml>] [-out <file.sql>] [-with-extension] [-clean] 导出
|
||||
// dbtool import [-config <yml>] [-in <file.sql>] 导入
|
||||
// dbtool import [-config <yml>] [-in <file.sql>] [-clean] 导入
|
||||
//
|
||||
// 导入(目标机器,二选一):
|
||||
//
|
||||
@ -30,9 +30,17 @@
|
||||
// 目标库为空还是已有数据:
|
||||
//
|
||||
// 默认只 CREATE TABLE IF NOT EXISTS + INSERT,**不清表**,因此要求目标库为空
|
||||
// (对已有数据的库会主键冲突)。想让同一份文件直接覆盖一个已有数据的库,
|
||||
// 导出时加 -clean —— 它会在结构段前输出 DROP TABLE IF EXISTS ... CASCADE,
|
||||
// 且只针对本次 dump 里的表(与 pg_dump --clean 口径一致,目标库多出来的表不动)。
|
||||
// (对已有数据的库会主键冲突)。要覆盖一个已有数据的库,用 -clean:
|
||||
//
|
||||
// dump -clean 在结构段前输出 DROP TABLE IF EXISTS ... CASCADE,只针对本次
|
||||
// dump 里的表(与 pg_dump --clean 口径一致),清理语义写在文件里,
|
||||
// 因此 psql -f 同样能灌进脏库。
|
||||
// import -clean 导入前 DROP public 下全部表,不必重新导出文件;适合「手上只有
|
||||
// 一份旧文件、目标库状态不明」的情况。
|
||||
//
|
||||
// 约束段另有一层幂等保护:先 DROP CONSTRAINT IF EXISTS 再 ADD。这样即使目标库里
|
||||
// 已存在同名主键(例如早先被 AutoMigrate 建过、或上一次导入中断留下的),
|
||||
// 也不会报 relation "<name>" already exists。
|
||||
//
|
||||
// 前置条件:目标库必须已启用 pgvector 扩展,否则 vector(64) 列建不出来。
|
||||
// 默认**不**导出扩展语句(可用 -with-extension 带上)。若目标容器由 scripts/pgvector 的
|
||||
@ -105,15 +113,17 @@ func usage() {
|
||||
用法:
|
||||
dbtool dump [-config <yml>] [-out <file>] [-with-extension] [-clean]
|
||||
导出「结构 + 主键 / 索引 + 数据」为单个纯 SQL 文件
|
||||
dbtool import [-config <yml>] [-in <file>]
|
||||
dbtool import [-config <yml>] [-in <file>] [-clean]
|
||||
把 dump 出来的 SQL 文件灌入目标库
|
||||
|
||||
说明:
|
||||
连接信息读取 configs/config.yml 的 database 段(可用 -config 覆盖)。
|
||||
-with-extension 在文件开头加 CREATE EXTENSION IF NOT EXISTS vector;
|
||||
-with-extension(dump)在文件开头加 CREATE EXTENSION IF NOT EXISTS vector;
|
||||
默认不加,此时目标库须已启用 pgvector,否则 vector 列建不出来。
|
||||
-clean 在结构段前加 DROP TABLE IF EXISTS,使脚本可灌进**已有数据的库**
|
||||
(目标库中这些表的既有数据会丢失)。不加时目标库必须是空库,否则主键冲突。
|
||||
-clean 有两个位置,都会丢弃目标库中对应表的既有数据:
|
||||
dump -clean 在结构段前写 DROP TABLE IF EXISTS,使文件可灌进已有数据的库
|
||||
import -clean 导入前先 DROP public 下全部表,适合「只有旧文件、目标库状态不明」
|
||||
不加 -clean 时要求目标库为空(或至少同名表为空),否则会主键冲突。
|
||||
import 等价于 psql -v ON_ERROR_STOP=1 -f;整个脚本在一个隐式事务里执行,出错整体回滚。`)
|
||||
}
|
||||
|
||||
@ -231,6 +241,7 @@ func runImport(args []string) {
|
||||
fs := flag.NewFlagSet("import", flag.ExitOnError)
|
||||
in := fs.String("in", "db_dump.sql", "导入文件路径")
|
||||
cfgPath := fs.String("config", "", "配置文件路径(默认 configs/config.yml)")
|
||||
clean := fs.Bool("clean", false, "导入前先 DROP public 下全部表(覆盖式还原;目标库这些数据会丢失)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
script, err := os.ReadFile(*in)
|
||||
@ -244,6 +255,12 @@ func runImport(args []string) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if *clean {
|
||||
// 与脚本拼成一段再执行:两者落在同一个隐式事务里,要么都成功、要么都回滚。
|
||||
script = append([]byte(dropAllTablesSQL+"\n"), script...)
|
||||
log.Printf("→ 导入前先清空 public 下全部表(-clean)")
|
||||
}
|
||||
|
||||
log.Printf("→ 导入 %s(%d KB)-> %s ...", *in, len(script)/1024, dcfg.Addr())
|
||||
if err := execScript(context.Background(), db, string(script)); err != nil {
|
||||
log.Fatalf("✗ 导入失败(已整体回滚): %v", err)
|
||||
@ -251,6 +268,24 @@ func runImport(args []string) {
|
||||
log.Printf("✓ 导入完成(%d KB)", len(script)/1024)
|
||||
}
|
||||
|
||||
// dropAllTablesSQL 删除 public 下全部基表(CASCADE 连带索引 / 约束 / 归属该表的序列)。
|
||||
//
|
||||
// 只删表,不动扩展:pgvector 的 vector 类型是 extension 对象,不属于任何表,因此
|
||||
// DROP TABLE 之后扩展仍然可用,无需重新 CREATE EXTENSION。
|
||||
const dropAllTablesSQL = `
|
||||
DO $$
|
||||
DECLARE
|
||||
r record;
|
||||
BEGIN
|
||||
FOR r IN
|
||||
SELECT c.relname FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public' AND c.relkind = 'r'
|
||||
LOOP
|
||||
EXECUTE format('DROP TABLE IF EXISTS public.%I CASCADE', r.relname);
|
||||
END LOOP;
|
||||
END $$;`
|
||||
|
||||
// execScript 用 pgx 简单查询协议执行整段脚本。
|
||||
//
|
||||
// 必须走底层 *pgx.Conn:database/sql 的 Exec 用扩展协议,不支持一次发多条语句。
|
||||
@ -479,6 +514,10 @@ func writeConstraints(w *bufio.Writer, db *sql.DB, table string) error {
|
||||
if err := rows.Scan(&name, &def); err != nil {
|
||||
return err
|
||||
}
|
||||
// 先 DROP CONSTRAINT IF EXISTS 再 ADD,让这一段幂等:
|
||||
// 目标库若已存在同名约束(例如早先被 AutoMigrate 建过、或导入中断留下的),
|
||||
// 不先删就会报 relation "<name>" already exists —— 约束底层的索引名在 schema 内唯一。
|
||||
fmt.Fprintf(w, "ALTER TABLE ONLY %s DROP CONSTRAINT IF EXISTS %s;\n", qname(table), qi(name))
|
||||
fmt.Fprintf(w, "ALTER TABLE ONLY %s ADD CONSTRAINT %s %s;\n", qname(table), qi(name), def)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
||||
Reference in New Issue
Block a user