refactor(contest): finalTest database transaction
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Baoshuo Ren 2023-02-20 09:45:40 +08:00
parent c2fdca91cd
commit d7741c2910
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
5 changed files with 310 additions and 295 deletions

View File

@ -185,7 +185,7 @@ CREATE TABLE `contests` (
`zan` int NOT NULL DEFAULT '0', `zan` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `status` (`status`,`id`) USING BTREE KEY `status` (`status`,`id`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -337,7 +337,7 @@ CREATE TABLE `contests_submissions` (
`cnt` int DEFAULT NULL, `cnt` int DEFAULT NULL,
`n_failures` int DEFAULT NULL, `n_failures` int DEFAULT NULL,
PRIMARY KEY (`contest_id`,`submitter`,`problem_id`) PRIMARY KEY (`contest_id`,`submitter`,`problem_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --

View File

@ -19,7 +19,7 @@ class DB {
$dbname = UOJConfig::$data['database']['database']; $dbname = UOJConfig::$data['database']['database'];
DB::$conn = new mysqli($server, $username, $password, $dbname); DB::$conn = new mysqli($server, $username, $password, $dbname);
if (DB::$conn->connect_error) { if (DB::$conn->connect_error) {
UOJLog::error('database initialization failed: '. DB::$conn->connect_error); UOJLog::error('database initialization failed: ' . DB::$conn->connect_error);
die('There is something wrong with the database >_<... Connection failed'); die('There is something wrong with the database >_<... Connection failed');
} }
if (!DB::$conn->set_charset("utf8mb4")) { if (!DB::$conn->set_charset("utf8mb4")) {
@ -36,104 +36,104 @@ class DB {
public static function escape($str) { public static function escape($str) {
return DB::$conn->real_escape_string($str); return DB::$conn->real_escape_string($str);
} }
public static function raw($str) { public static function raw($str) {
return new DBRawString($str); return new DBRawString($str);
} }
public static function rawbracket($q) { public static function rawbracket($q) {
return DB::raw(DB::bracket($q)); return DB::raw(DB::bracket($q));
} }
public static function rawvalue($str) { public static function rawvalue($str) {
return DB::raw(DB::value($str)); return DB::raw(DB::value($str));
} }
public static function rawtuple(array $vals) { public static function rawtuple(array $vals) {
return DB::raw(DB::tuple($vals)); return DB::raw(DB::tuple($vals));
} }
public static function call($fun, ...$args) { public static function call($fun, ...$args) {
return DB::raw("{$fun}(".implode(',', array_map('DB::value', $args)).')'); return DB::raw("{$fun}(" . implode(',', array_map('DB::value', $args)) . ')');
} }
public static function now() { public static function now() {
return DB::call('now'); return DB::call('now');
} }
public static function instr($str, $substr) { public static function instr($str, $substr) {
return DB::call('instr', $str, $substr); return DB::call('instr', $str, $substr);
} }
public static function cast_as_json($value) { public static function cast_as_json($value) {
return DB::raw('cast('.DB::value($value).' as json)'); return DB::raw('cast(' . DB::value($value) . ' as json)');
} }
public static function json_set($json_doc, ...$args) { public static function json_set($json_doc, ...$args) {
return DB::call('json_set', DB::raw($json_doc), ...$args); return DB::call('json_set', DB::raw($json_doc), ...$args);
} }
public static function json_insert($json_doc, ...$args) { public static function json_insert($json_doc, ...$args) {
return DB::call('json_insert', DB::raw($json_doc), ...$args); return DB::call('json_insert', DB::raw($json_doc), ...$args);
} }
public static function json_replace($json_doc, ...$args) { public static function json_replace($json_doc, ...$args) {
return DB::call('json_replace', DB::raw($json_doc), ...$args); return DB::call('json_replace', DB::raw($json_doc), ...$args);
} }
public static function json_remove($json_doc, ...$args) { public static function json_remove($json_doc, ...$args) {
return DB::call('json_remove', DB::raw($json_doc), ...$args); return DB::call('json_remove', DB::raw($json_doc), ...$args);
} }
public static function json_array_append($json_doc, ...$args) { public static function json_array_append($json_doc, ...$args) {
return DB::call('json_array_append', DB::raw($json_doc), ...$args); return DB::call('json_array_append', DB::raw($json_doc), ...$args);
} }
public static function json_array_insert($json_doc, ...$args) { public static function json_array_insert($json_doc, ...$args) {
return DB::call('json_array_insert', DB::raw($json_doc), ...$args); return DB::call('json_array_insert', DB::raw($json_doc), ...$args);
} }
public static function json_unquote($json_doc) { public static function json_unquote($json_doc) {
return DB::call('json_unquote', DB::raw($json_doc)); return DB::call('json_unquote', DB::raw($json_doc));
} }
public static function table($table) { public static function table($table) {
//return '`'.str_replace('`', '``', $table).'`'; //return '`'.str_replace('`', '``', $table).'`';
return $table; return $table;
} }
public static function fields($fields) { public static function fields($fields) {
if (is_assoc($fields)) { if (is_assoc($fields)) {
$new_fields = []; $new_fields = [];
foreach ($fields as $name => $val) { foreach ($fields as $name => $val) {
if (is_int($name)) { if (is_int($name)) {
$new_fields[] = $val; $new_fields[] = $val;
} else { } else {
$new_fields[] = DB::field_as($val, $name); $new_fields[] = DB::field_as($val, $name);
} }
} }
$fields = $new_fields; $fields = $new_fields;
} }
return implode(',', $fields); return implode(',', $fields);
} }
public static function bracketed_fields($fields) { public static function bracketed_fields($fields) {
return '('.DB::fields($fields).')'; return '(' . DB::fields($fields) . ')';
} }
public static function value($str) { public static function value($str) {
if ($str === null) { if ($str === null) {
return 'NULL'; return 'NULL';
} elseif ($str === true) { } elseif ($str === true) {
return 'true'; return 'true';
} elseif ($str === false) { } elseif ($str === false) {
return 'false'; return 'false';
} elseif (is_int($str)|| is_float($str)) { } elseif (is_int($str) || is_float($str)) {
return $str; return $str;
} elseif (is_string($str)) { } elseif (is_string($str)) {
return '\''.DB::escape($str).'\''; return '\'' . DB::escape($str) . '\'';
} elseif ($str instanceof DBRawString) { } elseif ($str instanceof DBRawString) {
return $str->str; return $str->str;
} else { } else {
return false; return false;
} }
} }
public static function field_as($field, $name) { public static function field_as($field, $name) {
return "{$field} as {$name}"; return "{$field} as {$name}";
} }
public static function value_as($value, $name) { public static function value_as($value, $name) {
return DB::value($value)." as {$name}"; return DB::value($value) . " as {$name}";
} }
public static function if_func($conds, $val1, $val2) { public static function if_func($conds, $val1, $val2) {
return 'if('.DB::conds($conds).','.DB::value($val1).','.DB::value($val2).')'; return 'if(' . DB::conds($conds) . ',' . DB::value($val1) . ',' . DB::value($val2) . ')';
} }
public static function setValue($field, $val) { public static function setValue($field, $val) {
return $field.' = '.DB::value($val); return $field . ' = ' . DB::value($val);
} }
public static function setValues(array $arr) { public static function setValues(array $arr) {
$all = []; $all = [];
foreach ($arr as $key => $val) { foreach ($arr as $key => $val) {
@ -152,16 +152,16 @@ class DB {
$lhs = $cond[0] instanceof DBRawString ? $cond[0]->str : $cond[0]; $lhs = $cond[0] instanceof DBRawString ? $cond[0]->str : $cond[0];
$op = $cond[1]; $op = $cond[1];
$rhs = DB::value($cond[2]); $rhs = DB::value($cond[2]);
return $lhs.' '.$op.' '.$rhs; return $lhs . ' ' . $op . ' ' . $rhs;
} else { } else {
return false; return false;
} }
} }
return $cond; return $cond;
} }
public static function conds($conds) { public static function conds($conds) {
return is_array($conds) ? DB::land($conds) : $conds; return is_array($conds) ? DB::land($conds) : $conds;
} }
public static function land(array $conds) { public static function land(array $conds) {
if (is_assoc($conds)) { if (is_assoc($conds)) {
$new_conds = []; $new_conds = [];
@ -178,7 +178,7 @@ class DB {
} }
$conds = $new_conds; $conds = $new_conds;
} }
return '('.implode(' and ', array_map('DB::cond', $conds)).')'; return '(' . implode(' and ', array_map('DB::cond', $conds)) . ')';
} }
public static function lor(array $conds) { public static function lor(array $conds) {
if (is_assoc($conds)) { if (is_assoc($conds)) {
@ -196,7 +196,7 @@ class DB {
} }
$conds = $new_conds; $conds = $new_conds;
} }
return '('.implode(' or ', array_map('DB::cond', $conds)).')'; return '(' . implode(' or ', array_map('DB::cond', $conds)) . ')';
} }
public static function tuple(array $vals) { public static function tuple(array $vals) {
$str = '('; $str = '(';
@ -212,20 +212,20 @@ class DB {
$str .= ')'; $str .= ')';
return $str; return $str;
} }
public static function tuples(array $tuples) { public static function tuples(array $tuples) {
$all = []; $all = [];
foreach ($tuples as $vals) { foreach ($tuples as $vals) {
$all[] = DB::tuple($vals); $all[] = DB::tuple($vals);
} }
return implode(', ', $all); return implode(', ', $all);
} }
public static function fetch($res, $opt = DB::ASSOC) { public static function fetch($res, $opt = DB::ASSOC) {
return $res->fetch_array($opt); return $res->fetch_array($opt);
} }
public static function bracket($q) { public static function bracket($q) {
return '('.DB::query_str($q).')'; return '(' . DB::query_str($q) . ')';
} }
public static function query_str($q) { public static function query_str($q) {
if (is_array($q)) { if (is_array($q)) {
@ -259,7 +259,7 @@ class DB {
} }
public static function exists($q) { public static function exists($q) {
return 'exists '.DB::bracket($q); return 'exists ' . DB::bracket($q);
} }
public static function query($q) { public static function query($q) {
@ -269,7 +269,7 @@ class DB {
$ret = DB::$conn->query(DB::query_str($q)); $ret = DB::$conn->query(DB::query_str($q));
if ($ret === false) { if ($ret === false) {
UOJLog::error(DB::query_str($q)); UOJLog::error(DB::query_str($q));
UOJLog::error('update failed: '.DB::$conn->error); UOJLog::error('update failed: ' . DB::$conn->error);
} }
return $ret; return $ret;
} }
@ -277,7 +277,7 @@ class DB {
$ret = DB::$conn->query(DB::query_str($q)); $ret = DB::$conn->query(DB::query_str($q));
if ($ret === false) { if ($ret === false) {
UOJLog::error(DB::query_str($q)); UOJLog::error(DB::query_str($q));
UOJLog::error('insert failed: '.DB::$conn->error); UOJLog::error('insert failed: ' . DB::$conn->error);
} }
return $ret; return $ret;
} }
@ -289,7 +289,7 @@ class DB {
$ret = DB::$conn->query(DB::query_str($q)); $ret = DB::$conn->query(DB::query_str($q));
if ($ret === false) { if ($ret === false) {
UOJLog::error(DB::query_str($q)); UOJLog::error(DB::query_str($q));
UOJLog::error('delete failed: '.DB::$conn->error); UOJLog::error('delete failed: ' . DB::$conn->error);
} }
return $ret; return $ret;
} }
@ -337,21 +337,21 @@ class DB {
} }
return $res->fetch_array($opt); return $res->fetch_array($opt);
} }
public static function selectSingle($q) { public static function selectSingle($q) {
$res = DB::select($q); $res = DB::select($q);
if ($res === false) { if ($res === false) {
return false; return false;
} }
$row = $res->fetch_row(); $row = $res->fetch_row();
if (!$row) { if (!$row) {
return false; return false;
} }
return $row[0]; return $row[0];
} }
/** /**
* perform SQL query $q in the form of select count(*) from XXX where XXX; * perform SQL query $q in the form of select count(*) from XXX where XXX;
*/ */
public static function selectCount($q) { public static function selectCount($q) {
$res = DB::select($q); $res = DB::select($q);
if ($res === false) { if ($res === false) {
@ -361,75 +361,75 @@ class DB {
return $cnt; return $cnt;
} }
/** /**
* perform SQL query: select exists ($q); * perform SQL query: select exists ($q);
* *
* on success, returns 0 or 1 * on success, returns 0 or 1
* on failure, returns false * on failure, returns false
* *
* @return int|false * @return int|false
*/ */
public static function selectExists($q) { public static function selectExists($q) {
$res = DB::select(["select", DB::exists($q)]); $res = DB::select(["select", DB::exists($q)]);
if ($res === false) { if ($res === false) {
return false; return false;
} }
return (int)($res->fetch_row()[0]); return (int)($res->fetch_row()[0]);
} }
public static function limit() { public static function limit() {
$num = func_get_args(); $num = func_get_args();
if (count($num) == 1) { if (count($num) == 1) {
return "limit ".((int)$num[0]); return "limit " . ((int)$num[0]);
} elseif (count($num) == 2) { } elseif (count($num) == 2) {
return "limit ".((int)$num[0]).",".((int)$num[1]); return "limit " . ((int)$num[0]) . "," . ((int)$num[1]);
} else { } else {
return false; return false;
} }
} }
public static function for_share() { public static function for_share() {
return "for share"; return "for share";
} }
public static function for_update() { public static function for_update() {
return "for update"; return "for update";
} }
public static function startTransaction() { public static function startTransaction() {
return DB::$conn->begin_transaction(); return DB::$conn->begin_transaction();
} }
public static function rollback() { public static function rollback() {
return DB::$conn->rollback(); return DB::$conn->rollback();
} }
public static function commit() { public static function commit() {
return DB::$conn->commit(); return DB::$conn->commit();
} }
public static function transaction($func) { public static function transaction($func) {
if (DB::$in_transaction) { if (DB::$in_transaction) {
$ret = $func(); $ret = $func();
} else { } else {
DB::$in_transaction = true; DB::$in_transaction = true;
DB::startTransaction(); DB::startTransaction();
$ret = $func(); $ret = $func();
DB::commit(); DB::commit();
DB::$in_transaction = false; DB::$in_transaction = false;
} }
return $ret; return $ret;
} }
public static function lock($tables, $func) { public static function lock($tables, $func) {
$q = []; $q = [];
foreach ($tables as $table => $type) { foreach ($tables as $table => $type) {
if ($type != DB::WLOCK && $type != DB::RLOCK) { if ($type != DB::WLOCK && $type != DB::RLOCK) {
UOJLog::error('Unknown type: '.$type); UOJLog::error('Unknown type: ' . $type);
return false; return false;
} }
$q[] = $table.' '.$type; $q[] = $table . ' ' . $type;
} }
$q = 'lock tables '.implode(',', $q); $q = 'lock tables ' . implode(',', $q);
DB::query($q); DB::query($q);
$ret = $func(); $ret = $func();

View File

@ -191,114 +191,127 @@ class UOJContest {
ignore_user_abort(true); ignore_user_abort(true);
set_time_limit(0); set_time_limit(0);
DB::update([ DB::transaction(function () {
"update contests", $status = DB::selectSingle([
"set", ["status" => 'testing'], "select status from contests",
"where", ["id" => $this->info['id']] "where", ["id" => $this->info['id']],
]); DB::for_update(),
if (DB::affected_rows() !== 1) {
// 已经有其他人开始评测了,不进行任何操作
return;
}
$res = DB::selectAll([
"select id, problem_id, content, result, submitter, hide_score_to_others from submissions",
"where", ["contest_id" => $this->info['id']]
]);
foreach ($res as $submission) {
$content = json_decode($submission['content'], true);
if (isset($content['final_test_config'])) {
$content['config'] = $content['final_test_config'];
unset($content['final_test_config']);
}
if (isset($content['first_test_config'])) {
unset($content['first_test_config']);
}
$q = [
'content' => json_encode($content),
];
$problem_judge_type = $this->info['extra_config']["problem_{$submission['problem_id']}"] ?: $this->defaultProblemJudgeType();
$result = json_decode($submission['result'], true);
switch ($problem_judge_type) {
case 'sample':
if (isset($result['final_result']) && $result['final_result']['status'] == 'Judged') {
$q += [
'result' => json_encode($result['final_result']),
'score' => $result['final_result']['score'],
'used_time' => $result['final_result']['time'],
'used_memory' => $result['final_result']['memory'],
'judge_time' => $this->info['end_time_str'],
'status' => 'Judged',
];
if ($submission['hide_score_to_others']) {
$q['hidden_score'] = $q['score'];
$q['score'] = null;
}
}
break;
case 'no-details':
case 'full':
if ($result['status'] == 'Judged' && !isset($result['final_result'])) {
$q += [
'result' => $submission['result'],
'score' => $result['score'],
'used_time' => $result['time'],
'used_memory' => $result['memory'],
'judge_time' => $this->info['end_time_str'],
'status' => 'Judged',
];
if ($submission['hide_score_to_others']) {
$q['hidden_score'] = $q['score'];
$q['score'] = null;
}
}
break;
}
UOJSubmission::rejudgeById($submission['id'], [
'reason_text' => HTML::stripTags($this->info['name']) . ' 最终测试',
'reason_url' => HTML::url(UOJContest::cur()->getUri()),
'set_q' => $q,
]); ]);
}
// warning: check if this command works well when the database is not MySQL if ($status !== 'unfinished') {
DB::update([ // 已经有其他人开始评测了,不进行任何操作
"update submissions", return;
"set", [ }
"score = hidden_score",
"hidden_score = NULL",
"hide_score_to_others = 0"
], "where", [
"contest_id" => $this->info['id'],
"hide_score_to_others" => 1
]
]);
$updated = []; $res = DB::selectAll([
foreach ($res as $submission) { "select id, problem_id, content, result, submitter, hide_score_to_others from submissions",
$submitter = $submission['submitter']; "where", ["contest_id" => $this->info['id']],
$pid = $submission['problem_id']; DB::for_update(),
if (isset($updated[$submitter]) && isset($updated[$submitter][$pid])) { ]);
continue; foreach ($res as $submission) {
$content = json_decode($submission['content'], true);
if (isset($content['final_test_config'])) {
$content['config'] = $content['final_test_config'];
unset($content['final_test_config']);
}
if (isset($content['first_test_config'])) {
unset($content['first_test_config']);
}
$q = [
'content' => json_encode($content),
];
$problem_judge_type = $this->info['extra_config']["problem_{$submission['problem_id']}"] ?: $this->defaultProblemJudgeType();
$result = json_decode($submission['result'], true);
switch ($problem_judge_type) {
case 'sample':
if (isset($result['final_result']) && $result['final_result']['status'] == 'Judged') {
$q += [
'result' => json_encode($result['final_result']),
'score' => $result['final_result']['score'],
'used_time' => $result['final_result']['time'],
'used_memory' => $result['final_result']['memory'],
'judge_time' => $this->info['end_time_str'],
'status' => 'Judged',
];
if ($submission['hide_score_to_others']) {
$q['hidden_score'] = $q['score'];
$q['score'] = null;
}
}
break;
case 'no-details':
case 'full':
if ($result['status'] == 'Judged' && !isset($result['final_result'])) {
$q += [
'result' => $submission['result'],
'score' => $result['score'],
'used_time' => $result['time'],
'used_memory' => $result['memory'],
'judge_time' => $this->info['end_time_str'],
'status' => 'Judged',
];
if ($submission['hide_score_to_others']) {
$q['hidden_score'] = $q['score'];
$q['score'] = null;
}
}
break;
}
UOJSubmission::rejudgeById($submission['id'], [
'reason_text' => HTML::stripTags($this->info['name']) . ' 最终测试',
'reason_url' => HTML::url(UOJContest::cur()->getUri()),
'set_q' => $q,
]);
} }
updateBestACSubmissions($submitter, $pid);
if (!isset($updated[$submitter])) { // warning: check if this command works well when the database is not MySQL
$updated[$submitter] = []; DB::update([
"update submissions",
"set", [
"score = hidden_score",
"hidden_score = NULL",
"hide_score_to_others = 0"
], "where", [
"contest_id" => $this->info['id'],
"hide_score_to_others" => 1
]
]);
$updated = [];
foreach ($res as $submission) {
$submitter = $submission['submitter'];
$pid = $submission['problem_id'];
if (isset($updated[$submitter]) && isset($updated[$submitter][$pid])) {
continue;
}
updateBestACSubmissions($submitter, $pid);
if (!isset($updated[$submitter])) {
$updated[$submitter] = [];
}
$updated[$submitter][$pid] = true;
} }
$updated[$submitter][$pid] = true;
} DB::update([
"update contests",
"set", [
"status" => "testing",
],
"where", [
"id" => $this->info['id'],
],
]);
});
} }
public function queryJudgeProgress() { public function queryJudgeProgress() {

View File

@ -1,5 +1,7 @@
-- InnoDB -- InnoDB
ALTER TABLE `best_ac_submissions` ENGINE=InnoDB TABLESPACE `innodb_system`; ALTER TABLE `best_ac_submissions` ENGINE=InnoDB TABLESPACE `innodb_system`;
ALTER TABLE `contests` ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `contests_submissions` ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `judger_info` COLLATE=utf8mb4_unicode_ci; ALTER TABLE `judger_info` COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `problems_contents` COLLATE=utf8mb4_unicode_ci; ALTER TABLE `problems_contents` COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `submissions` ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ALTER TABLE `submissions` ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;