init: 初始化 dpb 桃育种系统代码库
前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""为育种核心表补「部分唯一索引(未软删)」并发兜底,幂等,可 --check 预览。
|
||||
|
||||
背景:combination_code / tree_no / cultivar_name / personnel.name / (site_id, plot_code)
|
||||
无 DB 层唯一约束,service 层 select-then-insert 存在并发竞态。此处补部分唯一索引
|
||||
(WHERE is_deleted = false),与各 service 的查重语义(只查未软删)一致:
|
||||
- 未软删记录之间不允许重复 → 并发插入同一编号仅一条成功,另一条 409。
|
||||
- 已软删记录不参与约束 → 删除后仍可重新使用该编号。
|
||||
|
||||
用法(backend/ 目录):
|
||||
python scripts/add_unique_constraints.py --check
|
||||
python scripts/add_unique_constraints.py
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if BACKEND_DIR not in sys.path:
|
||||
sys.path.insert(0, BACKEND_DIR)
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from app.config.setting import settings
|
||||
|
||||
# (表, 索引名, 唯一列, 说明)
|
||||
UNIQUE_INDEXES = [
|
||||
("bre_cross_combination", "uniq_bre_cross_combination_code", "combination_code", "组合编号"),
|
||||
("bre_tree", "uniq_bre_tree_no", "tree_no", "单株编号"),
|
||||
("bre_germplasm", "uniq_bre_germplasm_cultivar_name", "cultivar_name", "品种/种质名称"),
|
||||
("bre_personnel", "uniq_bre_personnel_name", "name", "人员姓名"),
|
||||
("bre_plot", "uniq_bre_plot_site_code", "site_id, plot_code", "基地地块编号"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="为育种表补部分唯一索引(未软删)")
|
||||
parser.add_argument("--check", action="store_true", help="仅打印将执行的 DDL,不落库")
|
||||
args = parser.parse_args()
|
||||
|
||||
engine = create_engine(settings.DB_URI)
|
||||
print(f"# 连接: {settings.DB_URI}")
|
||||
|
||||
with engine.connect() as conn:
|
||||
for table, idx_name, cols, label in UNIQUE_INDEXES:
|
||||
# 1) 未软删重复检查(部分索引只约束未软删行,故只查未软删)
|
||||
dup_sql = (
|
||||
f"SELECT {cols} FROM {table} "
|
||||
"WHERE is_deleted = false "
|
||||
f"GROUP BY {cols} HAVING COUNT(*) > 1"
|
||||
)
|
||||
try:
|
||||
dups = conn.execute(text(dup_sql)).fetchall()
|
||||
except Exception as e:
|
||||
print(f"# [跳过] {table}: 查重失败 {e!s}")
|
||||
continue
|
||||
if dups:
|
||||
print(f"# [跳过] {label}({table}.{cols})存在 {len(dups)} 组未软删重复,请先清洗数据:")
|
||||
for d in dups[:10]:
|
||||
print(" " + " / ".join(str(x) for x in d))
|
||||
continue
|
||||
|
||||
# 2) 索引是否已存在
|
||||
exists = conn.execute(
|
||||
text("SELECT 1 FROM pg_indexes WHERE indexname = :name"),
|
||||
{"name": idx_name},
|
||||
).fetchone()
|
||||
if exists:
|
||||
print(f"# [已存在] {idx_name}")
|
||||
continue
|
||||
|
||||
ddl = (
|
||||
f"CREATE UNIQUE INDEX {idx_name} ON {table} ({cols}) "
|
||||
"WHERE is_deleted = false"
|
||||
)
|
||||
print(" " + ddl)
|
||||
if not args.check:
|
||||
try:
|
||||
with engine.begin() as c:
|
||||
c.execute(text(ddl))
|
||||
print("OK " + ddl)
|
||||
except Exception as e:
|
||||
print("ERR " + ddl + " -> " + str(e)[:160])
|
||||
|
||||
print("# --check 模式:未执行任何变更。" if args.check else "# 完成。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user