Files
OnlineJudge/judge/sql_dispatcher.py
yuetsh 9f7f818d51 新增 SQL 题型:SQLite 内联判题(不依赖外部沙箱)
- SQL 作为语言接入现有提交流程,judge_task 按 language 分流到 SQLJudgeDispatcher
- judge/sql_runner.py:内存 SQLite 判题核心,查询题比结果集/增删改题比表状态,
  authorizer + progress_handler + max_page_count 三重防护
- dispatcher 提取 _process_judge_result/_push_status 供 SQL 判题复用(行为不变)
- 测试点通道支持 1.sql..N.sql 压缩包,出题只需数据脚本+标准答案
- Problem 增加 sql_config 字段;options 数据迁移注册 SQL 语言

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:23:13 -06:00

102 lines
4.4 KiB
Python
Raw 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.
"""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