AutoRpa/rpa_oms/app/inbound/oracle_repo.py

136 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 接口 1 Oracle 落表。
#
# 11.2 只能 thick(Instant Client)。连接从 rpa_oracle 池借。
# fid 由序列触发器生成,INSERT 不绑这一列。
# 唯一约束只认 uk_rpa_sgmd_oms_id 为 oms_id 重复;其它唯一约束当系统故障。
from __future__ import annotations
from rpa_oracle.errors import RepoUnavailable, is_sales_oms_id_duplicate
_INSERT_SQL = """
INSERT INTO rpa_sales_goods_mapping_data (
oms_id, bar_code, order_date, sale_price, sale_qty, plan_purchase_qty,
goods_id, goods_name, goods_spec, manufacturer, approval_no, unit,
customer_id, customer_name, org_code, goods_mdm_code, customer_mdm_code,
create_time, create_user_id, create_user_name,
update_time, update_user_id, update_user_name,
deal_flag
) VALUES (
:oms_id, :bar_code, :order_date, :sale_price, :sale_qty, :plan_purchase_qty,
:goods_id, :goods_name, :goods_spec, :manufacturer, :approval_no, :unit,
:customer_id, :customer_name, :org_code, :goods_mdm_code, :customer_mdm_code,
SYSDATE, 0, '系统管理员',
SYSDATE, 0, '系统管理员',
0
)
"""
_BIND_KEYS = (
"oms_id",
"bar_code",
"order_date",
"sale_price",
"sale_qty",
"plan_purchase_qty",
"goods_id",
"goods_name",
"goods_spec",
"manufacturer",
"approval_no",
"unit",
"customer_id",
"customer_name",
"org_code",
"goods_mdm_code",
"customer_mdm_code",
)
# 接口 1 连 Oracle 的仓库,管表 1 查重和批量插入。
#
# 连接由调用方从池里借来,一个请求一条。
class OracleSalesGoodsRepo:
# 挂上本请求的连接。不要在仓库里再 connect。
def __init__(self, conn):
self._conn = conn
# 查出表 1 里已经有的 oms_id。空列表不访问库。
def existing_oms_ids(self, oms_ids: list[int]) -> set[int]:
if not oms_ids:
return set()
placeholders, binds = in_binds(oms_ids)
sql = (
"SELECT oms_id FROM rpa_sales_goods_mapping_data "
f"WHERE oms_id IN ({placeholders})"
)
try:
with self._conn.cursor() as cur:
cur.execute(sql, binds)
return {row[0] for row in cur.fetchall()}
except Exception as exc:
raise RepoUnavailable("数据库不可用") from exc
# 一次 executemany 插入待写入行。
#
# 返回撞上 uk_rpa_sgmd_oms_id 的 oms_id。其它唯一约束或库故障抛 RepoUnavailable。
# 成功行在本方法内 commit。
def insert_many(self, records: list[dict]) -> set[int]:
if not records:
return set()
payload = [{key: record[key] for key in _BIND_KEYS} for record in records]
try:
with self._conn.cursor() as cur:
cur.executemany(_INSERT_SQL, payload, batcherrors=True)
collided = unique_collisions(
cur, records, "oms_id", is_sales_oms_id_duplicate
)
self._conn.commit()
return collided
except RepoUnavailable:
_rollback(self._conn)
raise
except Exception as exc:
_rollback(self._conn)
if is_sales_oms_id_duplicate(exc) and len(records) == 1:
return {records[0]["oms_id"]}
raise RepoUnavailable("数据库不可用") from exc
# 把一组整数编成 :id0, :id1 占位和 bind 字典。
def in_binds(ids: list[int]) -> tuple[str, dict[str, int]]:
binds = {f"id{i}": value for i, value in enumerate(ids)}
placeholders = ", ".join(f":id{i}" for i in range(len(ids)))
return placeholders, binds
# 把 getbatcherrors() 单项收成 ora_code / ora_constraint 能认的异常。
def as_ora_exc(err) -> Exception:
message = getattr(err, "message", str(err))
code = getattr(err, "code", None)
info = type("Info", (), {"code": code, "message": message})()
exc = Exception(message)
exc.args = (info,)
return exc
# 从批错误里挑出幂等键冲突的 id;碰到未知约束则整批失败。
def unique_collisions(cur, records: list[dict], key: str, is_dup) -> set[int]:
collided: set[int] = set()
errors = cur.getbatcherrors() if hasattr(cur, "getbatcherrors") else []
for err in errors:
wrapped = as_ora_exc(err)
if is_dup(wrapped):
collided.add(records[err.offset][key])
continue
raise RepoUnavailable("数据库不可用") from wrapped
return collided
# 回滚本请求已写入的行;回滚失败忽略。
def _rollback(conn) -> None:
try:
conn.rollback()
except Exception:
pass