"""迁移 026:用户产品小册子资料库与海报产品来源快照。""" import logging from sqlalchemy import inspect, text logger = logging.getLogger(__name__) def _primary_key_sql(dialect: str) -> str: if dialect == "postgresql": return "BIGSERIAL PRIMARY KEY" if dialect in ("mysql", "mariadb"): return "BIGINT AUTO_INCREMENT PRIMARY KEY" return "INTEGER PRIMARY KEY AUTOINCREMENT" def _column_names(db, table_name: str) -> set[str]: return {item["name"] for item in inspect(db.engine).get_columns(table_name)} def _index_names(db, table_name: str) -> set[str]: return {item["name"] for item in inspect(db.engine).get_indexes(table_name)} def migrate(): """幂等创建用户资料表并扩展海报关联字段。""" from insurance.db.compat import db dialect = db.engine.dialect.name inspector = inspect(db.engine) if not inspector.has_table("insurance_user_product_materials"): db.session.execute(text(f""" CREATE TABLE insurance_user_product_materials ( id {_primary_key_sql(dialect)}, owner_user_id VARCHAR(64) NOT NULL, tenant_id VARCHAR(64) NOT NULL DEFAULT 'default', company_id VARCHAR(50), company_name VARCHAR(100), product_name VARCHAR(150), plan_type VARCHAR(20), original_name VARCHAR(200) NOT NULL, file_key VARCHAR(500) NOT NULL, file_size BIGINT NOT NULL DEFAULT 0, page_count INTEGER, sha256 VARCHAR(64) NOT NULL, parse_status VARCHAR(20) NOT NULL DEFAULT 'pending', parse_message VARCHAR(500) NOT NULL DEFAULT '', parse_error TEXT, parse_task_id VARCHAR(200), parse_started_at TIMESTAMP, parse_finished_at TIMESTAMP, parsed_rules TEXT, confirmed_rules TEXT, confirmed_by VARCHAR(64), confirmed_at TIMESTAMP, review_status VARCHAR(20) NOT NULL DEFAULT 'private', status SMALLINT NOT NULL DEFAULT 1, deleted_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """)) db.session.commit() logger.info("[migrate_026] 已创建 insurance_user_product_materials") desired_indexes = { "idx_user_material_owner_updated": "owner_user_id, updated_at", "idx_user_material_owner_sha256": "owner_user_id, sha256", "idx_user_material_parse_status": "parse_status", } existing_indexes = _index_names(db, "insurance_user_product_materials") for index_name, columns in desired_indexes.items(): if index_name not in existing_indexes: db.session.execute(text( f"CREATE INDEX {index_name} " f"ON insurance_user_product_materials ({columns})" )) extra_columns = { "product_source_type": "VARCHAR(20)", "product_source_id": "VARCHAR(64)", "product_snapshot_json": "TEXT", } for table_name in ("poster_case_uploads", "poster_records"): current_columns = _column_names(db, table_name) for column_name, column_type in extra_columns.items(): if column_name not in current_columns: db.session.execute(text( f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}" )) db.session.execute(text( f"UPDATE {table_name} " "SET product_source_type = 'library_product', product_source_id = product_id " "WHERE product_id IS NOT NULL AND product_id <> '' " "AND (product_source_type IS NULL OR product_source_type = '')" )) db.session.commit() logger.info("[migrate_026] 用户产品资料库迁移完成")