新增 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>
This commit is contained in:
101
judge/sql_dispatcher.py
Normal file
101
judge/sql_dispatcher.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user