This commit is contained in:
toom1996
2026-09-21 19:51:57 +08:00
parent e5ce8aed3e
commit e8dd5b56ac
2 changed files with 59 additions and 12 deletions

View File

@ -1,15 +1,33 @@
-- dbtool 导出:127.0.0.1:5432/fashion -- dbtool 导出:127.0.0.1:5432/fashion
-- 生成时间:2026-09-21T19:42:05+08:00 -- 生成时间:2026-09-21T19:51:42+08:00
-- --
-- 导入(二选一): -- 导入(二选一):
-- dbtool import -in <本文件> -- dbtool import -in <本文件>
-- psql -U fashion -d fashion -v ON_ERROR_STOP=1 -f <本文件> -- psql -U fashion -d fashion -v ON_ERROR_STOP=1 -f <本文件>
-- --
-- 注意:目标库必须是空库;本脚本不清表,重复执行会主键冲突。 -- 注意:本文件由 dump -clean 生成,结构段前会 DROP 同名表 —— 目标库中这些表的既有数据将丢失。
SET client_encoding = 'UTF8'; SET client_encoding = 'UTF8';
SET client_min_messages = warning; SET client_min_messages = warning;
-- ==================== 结构 ==================== -- ==================== 结构 ====================
-- 清理(-clean):DROP 下列表,目标库中这些表的既有数据将丢失
DROP TABLE IF EXISTS public."brand_runway_draft_images" CASCADE;
DROP TABLE IF EXISTS public."brand_runway_drafts" CASCADE;
DROP TABLE IF EXISTS public."brand_runway_images" CASCADE;
DROP TABLE IF EXISTS public."brand_runways" CASCADE;
DROP TABLE IF EXISTS public."brands" CASCADE;
DROP TABLE IF EXISTS public."favorites" CASCADE;
DROP TABLE IF EXISTS public."histories" CASCADE;
DROP TABLE IF EXISTS public."image_embeddings" CASCADE;
DROP TABLE IF EXISTS public."ingest_jobs" CASCADE;
DROP TABLE IF EXISTS public."ingest_nonces" CASCADE;
DROP TABLE IF EXISTS public."refresh_tokens" CASCADE;
DROP TABLE IF EXISTS public."street_snap_draft_images" CASCADE;
DROP TABLE IF EXISTS public."street_snap_drafts" CASCADE;
DROP TABLE IF EXISTS public."street_snap_images" CASCADE;
DROP TABLE IF EXISTS public."street_snaps" CASCADE;
DROP TABLE IF EXISTS public."users" CASCADE;
CREATE TABLE IF NOT EXISTS public."brand_runway_draft_images" ( CREATE TABLE IF NOT EXISTS public."brand_runway_draft_images" (
"id" bigint GENERATED BY DEFAULT AS IDENTITY, "id" bigint GENERATED BY DEFAULT AS IDENTITY,
"draft_id" bigint, "draft_id" bigint,

View File

@ -3,7 +3,7 @@
// 用途:把一个 PostgreSQL 库(fashion)的「结构 + 索引 + 约束 + 数据」导出为 // 用途:把一个 PostgreSQL 库(fashion)的「结构 + 索引 + 约束 + 数据」导出为
// **单个纯 SQL 文件**,拷到另一台机器后用 dbtool 或 psql 灌入即可 —— 无需 pg_dump。 // **单个纯 SQL 文件**,拷到另一台机器后用 dbtool 或 psql 灌入即可 —— 无需 pg_dump。
// //
// dbtool dump [-config <yml>] [-out <file.sql>] [-with-extension] 导出 // dbtool dump [-config <yml>] [-out <file.sql>] [-with-extension] [-clean] 导出
// dbtool import [-config <yml>] [-in <file.sql>] 导入 // dbtool import [-config <yml>] [-in <file.sql>] 导入
// //
// 导入(目标机器,二选一): // 导入(目标机器,二选一):
@ -27,7 +27,14 @@
// //
// 视图 / 触发器 / 外键 / 注释 / 权限不导出(当前库中也不存在)。 // 视图 / 触发器 / 外键 / 注释 / 权限不导出(当前库中也不存在)。
// //
// 前置条件:目标库必须为空,且已启用 pgvector 扩展,否则 vector(64) 列建不出来。 // 目标库为空还是已有数据:
//
// 默认只 CREATE TABLE IF NOT EXISTS + INSERT,**不清表**,因此要求目标库为空
// (对已有数据的库会主键冲突)。想让同一份文件直接覆盖一个已有数据的库,
// 导出时加 -clean —— 它会在结构段前输出 DROP TABLE IF EXISTS ... CASCADE,
// 且只针对本次 dump 里的表(与 pg_dump --clean 口径一致,目标库多出来的表不动)。
//
// 前置条件:目标库必须已启用 pgvector 扩展,否则 vector(64) 列建不出来。
// 默认**不**导出扩展语句(可用 -with-extension 带上)。若目标容器由 scripts/pgvector 的 // 默认**不**导出扩展语句(可用 -with-extension 带上)。若目标容器由 scripts/pgvector 的
// compose 启动,initdb/01-extensions.sql 会在数据卷首次初始化时自动创建扩展。 // compose 启动,initdb/01-extensions.sql 会在数据卷首次初始化时自动创建扩展。
// //
@ -96,16 +103,17 @@ func usage() {
fmt.Println(`dbtool - 后台数据库导出 / 导入脚本(PostgreSQL) fmt.Println(`dbtool - 后台数据库导出 / 导入脚本(PostgreSQL)
用法: 用法:
dbtool dump [-config <yml>] [-out <file>] [-with-extension] dbtool dump [-config <yml>] [-out <file>] [-with-extension] [-clean]
导出「结构 + 主键 / 索引 + 数据」为单个纯 SQL 文件 导出「结构 + 主键 / 索引 + 数据」为单个纯 SQL 文件
dbtool import [-config <yml>] [-in <file>] dbtool import [-config <yml>] [-in <file>]
把 dump 出来的 SQL 文件灌入目标库 把 dump 出来的 SQL 文件灌入目标库
说明: 说明:
连接信息读取 configs/config.yml 的 database 段(可用 -config 覆盖)。 连接信息读取 configs/config.yml 的 database 段(可用 -config 覆盖)。
-with-extension 会在导出文件开头加 CREATE EXTENSION IF NOT EXISTS vector; -with-extension 在文件开头加 CREATE EXTENSION IF NOT EXISTS vector;
默认不加,此时目标库须已启用 pgvector,否则 vector 列建不出来。 默认不加,此时目标库须已启用 pgvector,否则 vector 列建不出来。
目标库必须是**空库**(脚本按 CREATE TABLE IF NOT EXISTS + INSERT 写入,不会清表)。 -clean 在结构段前加 DROP TABLE IF EXISTS,使脚本可灌进**已有数据的库**
(目标库中这些表的既有数据会丢失)。不加时目标库必须是空库,否则主键冲突。
import 等价于 psql -v ON_ERROR_STOP=1 -f;整个脚本在一个隐式事务里执行,出错整体回滚。`) import 等价于 psql -v ON_ERROR_STOP=1 -f;整个脚本在一个隐式事务里执行,出错整体回滚。`)
} }
@ -135,6 +143,7 @@ func runDump(args []string) {
out := fs.String("out", "db_dump.sql", "导出文件路径") out := fs.String("out", "db_dump.sql", "导出文件路径")
cfgPath := fs.String("config", "", "配置文件路径(默认 configs/config.yml)") cfgPath := fs.String("config", "", "配置文件路径(默认 configs/config.yml)")
withExt := fs.Bool("with-extension", false, "在文件开头加 CREATE EXTENSION IF NOT EXISTS vector") withExt := fs.Bool("with-extension", false, "在文件开头加 CREATE EXTENSION IF NOT EXISTS vector")
clean := fs.Bool("clean", false, "在结构段前加 DROP TABLE IF EXISTS,使脚本可灌进已有数据的库(这些表的既有数据会丢失)")
_ = fs.Parse(args) _ = fs.Parse(args)
db, dcfg, err := openDB(*cfgPath) db, dcfg, err := openDB(*cfgPath)
@ -150,7 +159,7 @@ func runDump(args []string) {
defer f.Close() defer f.Close()
w := bufio.NewWriter(f) w := bufio.NewWriter(f)
writeHeader(w, dcfg, *withExt) writeHeader(w, dcfg, *withExt, *clean)
tables, err := listTables(db) tables, err := listTables(db)
if err != nil { if err != nil {
@ -162,8 +171,11 @@ func runDump(args []string) {
cols := make(map[string][]column, len(tables)) cols := make(map[string][]column, len(tables))
// ① 结构 // ① 结构(-clean 时先输出清理段)
fmt.Fprintln(w, "-- ==================== 结构 ====================") fmt.Fprintln(w, "-- ==================== 结构 ====================")
if *clean {
writeClean(w, tables)
}
for _, t := range tables { for _, t := range tables {
cs, err := listColumns(db, t) cs, err := listColumns(db, t)
if err != nil { if err != nil {
@ -259,8 +271,21 @@ func execScript(ctx context.Context, db *sql.DB, script string) error {
}) })
} }
// writeClean 输出清理段:DROP 掉本次要重建的表,让脚本可以灌进「已有数据的库」。
//
// 只 DROP 本次 dump 里出现的表(与 pg_dump --clean 口径一致)——目标库中多出来的表保持不动。
// 用 CASCADE:索引 / 约束 / 归属该表的序列随表一起删除,不必逐个列举;
// 顺序也无所谓,因为 CASCADE 会处理依赖,且整段脚本在同一个隐式事务里执行。
func writeClean(w *bufio.Writer, tables []string) {
fmt.Fprintln(w, "-- 清理(-clean):DROP 下列表,目标库中这些表的既有数据将丢失")
for _, t := range tables {
fmt.Fprintf(w, "DROP TABLE IF EXISTS %s CASCADE;\n", qname(t))
}
fmt.Fprintln(w)
}
// writeHeader 写文件头与几个会话设置。 // writeHeader 写文件头与几个会话设置。
func writeHeader(w *bufio.Writer, dcfg *config.DatabaseConfig, withExtension bool) { func writeHeader(w *bufio.Writer, dcfg *config.DatabaseConfig, withExtension, clean bool) {
fmt.Fprintf(w, "-- dbtool 导出:%s\n", dcfg.Addr()) fmt.Fprintf(w, "-- dbtool 导出:%s\n", dcfg.Addr())
fmt.Fprintf(w, "-- 生成时间:%s\n", time.Now().Format(time.RFC3339)) fmt.Fprintf(w, "-- 生成时间:%s\n", time.Now().Format(time.RFC3339))
fmt.Fprintln(w, "--") fmt.Fprintln(w, "--")
@ -268,7 +293,11 @@ func writeHeader(w *bufio.Writer, dcfg *config.DatabaseConfig, withExtension boo
fmt.Fprintf(w, "-- dbtool import -in <本文件>\n") fmt.Fprintf(w, "-- dbtool import -in <本文件>\n")
fmt.Fprintf(w, "-- psql -U %s -d %s -v ON_ERROR_STOP=1 -f <本文件>\n", dcfg.User, dcfg.Name) fmt.Fprintf(w, "-- psql -U %s -d %s -v ON_ERROR_STOP=1 -f <本文件>\n", dcfg.User, dcfg.Name)
fmt.Fprintln(w, "--") fmt.Fprintln(w, "--")
fmt.Fprintln(w, "-- 注意:目标库必须是空库;本脚本不清表,重复执行会主键冲突。") if clean {
fmt.Fprintln(w, "-- 注意:本文件由 dump -clean 生成,结构段前会 DROP 同名表 —— 目标库中这些表的既有数据将丢失。")
} else {
fmt.Fprintln(w, "-- 注意:本文件不含 DROP,目标库必须是空库;重复执行会主键冲突。")
}
fmt.Fprintln(w, "SET client_encoding = 'UTF8';") fmt.Fprintln(w, "SET client_encoding = 'UTF8';")
fmt.Fprintln(w, "SET client_min_messages = warning;") fmt.Fprintln(w, "SET client_min_messages = warning;")
fmt.Fprintln(w) fmt.Fprintln(w)