"""SQL 题判题调度:不走 JudgeServer 沙箱,在 dramatiq worker 内用 sqlite3 直接判题。 复用 JudgeDispatcher 的 _process_judge_result 完成状态聚合、统计、排名和 WebSocket 推送, 因此比赛排名、rejudge、题目统计的语义与沙箱判题完全一致。 """ import json import logging import os from django.conf import settings from judge.dispatcher import JudgeDispatcher from judge.sql_runner import SQLCaseError, run_case from submission.models import JudgeStatus, Submission from utils.shortcuts import natural_sort_key logger = logging.getLogger(__name__) class SQLProblemConfigError(Exception): pass class SQLJudgeDispatcher(JudgeDispatcher): def judge(self): Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.JUDGING) self._push_status(JudgeStatus.JUDGING, "judging") try: ref_sql, mode, order_sensitive, init_scripts = self._load_problem_config() except SQLProblemConfigError as e: self._system_error(str(e)) return cases = [] for index, init_sql in enumerate(init_scripts, start=1): try: case = run_case( init_sql, ref_sql, self.submission.code, mode=mode, order_sensitive=order_sensitive, time_limit_ms=self.problem.time_limit, memory_limit_mb=self.problem.memory_limit, ) except SQLCaseError as e: # 初始化/标准答案执行失败,属出题配置问题 self._system_error(e.message) return case["test_case"] = str(index) # 语法错误与数据无关,首个测试点即可确认,整题按编译错误处理(ACM 不罚时,前端展示 err_info) if index == 1 and case["result"] == JudgeStatus.COMPILE_ERROR: self._process_judge_result({"err": "CompileError", "data": case["error_message"]}) return cases.append(case) self._process_judge_result({"err": None, "data": cases}) def _system_error(self, message): logger.error(f"SQL judge system error, submission {self.submission.id}, problem {self.problem.id}: {message}") Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.SYSTEM_ERROR, statistic_info={"err_info": message}) self._push_status(JudgeStatus.SYSTEM_ERROR, "error") def _load_problem_config(self): """校验并加载 SQL 题配置,返回 (标准答案, mode, order_sensitive, 各测试点初始化脚本)。""" sql_config = self.problem.sql_config or {} mode = sql_config.get("mode") if mode not in ("query", "modify"): raise SQLProblemConfigError("题目缺少 SQL 配置(题型)") ref_sql = None for item in self.problem.answers or []: if item.get("language") == "SQL" and item.get("code", "").strip(): ref_sql = item["code"] break if not ref_sql: raise SQLProblemConfigError("题目缺少 SQL 标准答案") test_case_dir = os.path.join(settings.TEST_CASE_DIR, self.problem.test_case_id) try: with open(os.path.join(test_case_dir, "info"), encoding="utf-8") as f: info = json.load(f) except (OSError, json.JSONDecodeError) as e: raise SQLProblemConfigError(f"测试点信息读取失败: {e}") if not info.get("sql"): raise SQLProblemConfigError("测试点不是 SQL 类型,请重新上传 SQL 测试点压缩包") init_scripts = [] # 按 "1","2",… 自然序遍历,保证与 test_case_score 的下标对应(OI 计分依赖顺序) for key in sorted(info["test_cases"].keys(), key=natural_sort_key): input_name = info["test_cases"][key]["input_name"] try: with open(os.path.join(test_case_dir, input_name), encoding="utf-8") as f: init_scripts.append(f.read()) except OSError as e: raise SQLProblemConfigError(f"测试点脚本 {input_name} 读取失败: {e}") if not init_scripts: raise SQLProblemConfigError("题目没有任何测试点") return ref_sql, mode, sql_config.get("order_sensitive", False), init_scripts