feat: add auto table creation and per-PK sync verification

- Add new Access table mappings for transmitter contracts (25/26年变送器) and tube bending workshop (弯管车间烘洗)
- Add auto-create target table from Access schema when table does not exist (db_utils + init_full_sync)
- Implement per-primary-key verification in incremental sync: verify deletes are gone and inserts are present before marking Synced=1
- Add post-commit re-verification with Synced rollback on failure for automatic retry
- Batch IN clause parameters to stay under SQL Server limit
- Adjust poll interval from 5s to 30s
- Improve IDENTITY_INSERT cleanup in finally blocks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-06-12 10:50:01 +08:00
parent 11d284a6bf
commit 1ae901a040
5 changed files with 334 additions and 96 deletions

View File

@@ -1,7 +1,82 @@
# db_utils.py
import pyodbc
import datetime
import decimal
from config import SQL_SERVER_CONN, ACCESS_DRIVER
# ================= 表存在性检查 & 自动建表 =================
def table_exists(cursor, schema, table):
"""检查 SQL Server 表是否存在"""
query = """
SELECT COUNT(*)
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name = ? AND t.name = ?
"""
cursor.execute(query, (schema, table))
return cursor.fetchone()[0] > 0
def ensure_schema(cursor, schema):
"""确保 schema 存在,不存在则创建"""
cursor.execute("SELECT SCHEMA_ID(?)", (schema,))
if cursor.fetchone()[0] is None:
cursor.execute(f"CREATE SCHEMA [{schema}]")
def _access_col_to_sql(col_info, pk_col):
"""将 Access 列描述 (cursor.description 元组) 转为 SQL Server 列定义
Access ODBC 驱动的 type_code 是 Python 类型对象:
int → INT
str → NVARCHAR(size) (size > 4000 时为 Memo 字段 → NVARCHAR(MAX))
datetime.datetime → DATETIME
float → FLOAT
decimal.Decimal → DECIMAL(p,s)
bool → BIT
"""
col_name, type_code, _, size, precision, scale, nullable = col_info
is_pk = (col_name == pk_col)
if type_code is int:
sql_type = "INT"
if is_pk:
sql_type += " IDENTITY(1,1) PRIMARY KEY"
elif type_code is float:
sql_type = "FLOAT"
elif type_code is bool:
sql_type = "BIT"
elif type_code is datetime.datetime:
sql_type = "DATETIME"
elif type_code is decimal.Decimal:
sql_type = f"DECIMAL({precision or 18}, {scale or 0})"
elif type_code is str:
# size > 4000 → Access Memo 字段,用 NVARCHAR(MAX)
if not size or size <= 0 or size > 4000:
sql_type = "NVARCHAR(MAX)"
else:
sql_type = f"NVARCHAR({size})"
if is_pk:
sql_type += " PRIMARY KEY"
else:
sql_type = "NVARCHAR(255)"
if is_pk:
sql_type += " PRIMARY KEY"
return f"[{col_name}] {sql_type}"
def create_table_from_access(sql_cursor, target_schema, target_table,
acc_description, pk_col):
"""根据 Access cursor.description 在 SQL Server 自动建表"""
col_defs = [_access_col_to_sql(col, pk_col) for col in acc_description]
full_name = fmt_table(target_schema, target_table)
col_str = ",\n ".join(col_defs)
create_sql = f"CREATE TABLE {full_name} (\n {col_str}\n)"
sql_cursor.execute(create_sql)
def get_sql_conn():
"""获取 SQL Server 连接"""
# 显式添加 TrustServerCertificate=yes 以兼容 ODBC Driver 18+