diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql index 0f2dfcc..fe140ac 100644 --- a/db/app_uoj233.sql +++ b/db/app_uoj233.sql @@ -185,7 +185,7 @@ CREATE TABLE `contests` ( `zan` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), 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 */; -- @@ -337,7 +337,7 @@ CREATE TABLE `contests_submissions` ( `cnt` int DEFAULT NULL, `n_failures` int DEFAULT NULL, 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 */; -- diff --git a/web/app/models/DB.php b/web/app/models/DB.php index 3edc229..4979dd1 100644 --- a/web/app/models/DB.php +++ b/web/app/models/DB.php @@ -4,14 +4,14 @@ class DB { public static mysqli $conn; public static array $cache = []; public static bool $in_transaction = false; - + const WLOCK = "WRITE"; const RLOCK = "READ"; const ASSOC = MYSQLI_ASSOC; const NUM = MYSQLI_NUM; const BOTH = MYSQLI_BOTH; - + public static function init() { $server = UOJConfig::$data['database']['host']; $username = UOJConfig::$data['database']['username']; @@ -19,7 +19,7 @@ class DB { $dbname = UOJConfig::$data['database']['database']; DB::$conn = new mysqli($server, $username, $password, $dbname); 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'); } if (!DB::$conn->set_charset("utf8mb4")) { @@ -27,113 +27,113 @@ class DB { die('There is something wrong with the database >_<.... Charset utf8 not supported'); } } - + // lc: local cache public static function lc() { return new DBUseLocalCache(''); } - + public static function escape($str) { return DB::$conn->real_escape_string($str); } - public static function raw($str) { - return new DBRawString($str); - } - public static function rawbracket($q) { - return DB::raw(DB::bracket($q)); - } - public static function rawvalue($str) { - return DB::raw(DB::value($str)); - } - public static function rawtuple(array $vals) { - return DB::raw(DB::tuple($vals)); - } - public static function call($fun, ...$args) { - return DB::raw("{$fun}(".implode(',', array_map('DB::value', $args)).')'); - } - public static function now() { - return DB::call('now'); - } - public static function instr($str, $substr) { - return DB::call('instr', $str, $substr); - } - public static function cast_as_json($value) { - return DB::raw('cast('.DB::value($value).' as json)'); - } - public static function json_set($json_doc, ...$args) { - return DB::call('json_set', DB::raw($json_doc), ...$args); - } - public static function json_insert($json_doc, ...$args) { - return DB::call('json_insert', DB::raw($json_doc), ...$args); - } - public static function json_replace($json_doc, ...$args) { - return DB::call('json_replace', DB::raw($json_doc), ...$args); - } - public static function json_remove($json_doc, ...$args) { - return DB::call('json_remove', DB::raw($json_doc), ...$args); - } - public static function json_array_append($json_doc, ...$args) { - return DB::call('json_array_append', DB::raw($json_doc), ...$args); - } - public static function json_array_insert($json_doc, ...$args) { - return DB::call('json_array_insert', DB::raw($json_doc), ...$args); - } - public static function json_unquote($json_doc) { - return DB::call('json_unquote', DB::raw($json_doc)); - } + public static function raw($str) { + return new DBRawString($str); + } + public static function rawbracket($q) { + return DB::raw(DB::bracket($q)); + } + public static function rawvalue($str) { + return DB::raw(DB::value($str)); + } + public static function rawtuple(array $vals) { + return DB::raw(DB::tuple($vals)); + } + public static function call($fun, ...$args) { + return DB::raw("{$fun}(" . implode(',', array_map('DB::value', $args)) . ')'); + } + public static function now() { + return DB::call('now'); + } + public static function instr($str, $substr) { + return DB::call('instr', $str, $substr); + } + public static function cast_as_json($value) { + return DB::raw('cast(' . DB::value($value) . ' as json)'); + } + public static function json_set($json_doc, ...$args) { + return DB::call('json_set', DB::raw($json_doc), ...$args); + } + public static function json_insert($json_doc, ...$args) { + return DB::call('json_insert', DB::raw($json_doc), ...$args); + } + public static function json_replace($json_doc, ...$args) { + return DB::call('json_replace', DB::raw($json_doc), ...$args); + } + public static function json_remove($json_doc, ...$args) { + return DB::call('json_remove', DB::raw($json_doc), ...$args); + } + public static function json_array_append($json_doc, ...$args) { + return DB::call('json_array_append', DB::raw($json_doc), ...$args); + } + public static function json_array_insert($json_doc, ...$args) { + return DB::call('json_array_insert', DB::raw($json_doc), ...$args); + } + public static function json_unquote($json_doc) { + return DB::call('json_unquote', DB::raw($json_doc)); + } public static function table($table) { //return '`'.str_replace('`', '``', $table).'`'; return $table; } - public static function fields($fields) { - if (is_assoc($fields)) { - $new_fields = []; - foreach ($fields as $name => $val) { - if (is_int($name)) { - $new_fields[] = $val; - } else { - $new_fields[] = DB::field_as($val, $name); - } - } - $fields = $new_fields; - } - return implode(',', $fields); - } - public static function bracketed_fields($fields) { - return '('.DB::fields($fields).')'; - } - public static function value($str) { - if ($str === null) { - return 'NULL'; - } elseif ($str === true) { - return 'true'; - } elseif ($str === false) { - return 'false'; - } elseif (is_int($str)|| is_float($str)) { - return $str; - } elseif (is_string($str)) { - return '\''.DB::escape($str).'\''; - } elseif ($str instanceof DBRawString) { - return $str->str; - } else { - return false; - } - } - public static function field_as($field, $name) { - return "{$field} as {$name}"; - } - public static function value_as($value, $name) { - return DB::value($value)." as {$name}"; - } + public static function fields($fields) { + if (is_assoc($fields)) { + $new_fields = []; + foreach ($fields as $name => $val) { + if (is_int($name)) { + $new_fields[] = $val; + } else { + $new_fields[] = DB::field_as($val, $name); + } + } + $fields = $new_fields; + } + return implode(',', $fields); + } + public static function bracketed_fields($fields) { + return '(' . DB::fields($fields) . ')'; + } + public static function value($str) { + if ($str === null) { + return 'NULL'; + } elseif ($str === true) { + return 'true'; + } elseif ($str === false) { + return 'false'; + } elseif (is_int($str) || is_float($str)) { + return $str; + } elseif (is_string($str)) { + return '\'' . DB::escape($str) . '\''; + } elseif ($str instanceof DBRawString) { + return $str->str; + } else { + return false; + } + } + public static function field_as($field, $name) { + return "{$field} as {$name}"; + } + public static function value_as($value, $name) { + return DB::value($value) . " as {$name}"; + } - public static function if_func($conds, $val1, $val2) { - return 'if('.DB::conds($conds).','.DB::value($val1).','.DB::value($val2).')'; - } - - public static function setValue($field, $val) { - return $field.' = '.DB::value($val); - } + public static function if_func($conds, $val1, $val2) { + return 'if(' . DB::conds($conds) . ',' . DB::value($val1) . ',' . DB::value($val2) . ')'; + } + + public static function setValue($field, $val) { + return $field . ' = ' . DB::value($val); + } public static function setValues(array $arr) { $all = []; foreach ($arr as $key => $val) { @@ -152,16 +152,16 @@ class DB { $lhs = $cond[0] instanceof DBRawString ? $cond[0]->str : $cond[0]; $op = $cond[1]; $rhs = DB::value($cond[2]); - return $lhs.' '.$op.' '.$rhs; + return $lhs . ' ' . $op . ' ' . $rhs; } else { return false; } } return $cond; } - public static function conds($conds) { - return is_array($conds) ? DB::land($conds) : $conds; - } + public static function conds($conds) { + return is_array($conds) ? DB::land($conds) : $conds; + } public static function land(array $conds) { if (is_assoc($conds)) { $new_conds = []; @@ -178,7 +178,7 @@ class DB { } $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) { if (is_assoc($conds)) { @@ -196,7 +196,7 @@ class DB { } $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) { $str = '('; @@ -212,20 +212,20 @@ class DB { $str .= ')'; return $str; } - public static function tuples(array $tuples) { - $all = []; - foreach ($tuples as $vals) { - $all[] = DB::tuple($vals); - } - return implode(', ', $all); - } - + public static function tuples(array $tuples) { + $all = []; + foreach ($tuples as $vals) { + $all[] = DB::tuple($vals); + } + return implode(', ', $all); + } + public static function fetch($res, $opt = DB::ASSOC) { return $res->fetch_array($opt); } public static function bracket($q) { - return '('.DB::query_str($q).')'; + return '(' . DB::query_str($q) . ')'; } public static function query_str($q) { if (is_array($q)) { @@ -257,11 +257,11 @@ class DB { } return $q; } - + public static function exists($q) { - return 'exists '.DB::bracket($q); + return 'exists ' . DB::bracket($q); } - + public static function query($q) { return DB::$conn->query(DB::query_str($q)); } @@ -269,7 +269,7 @@ class DB { $ret = DB::$conn->query(DB::query_str($q)); if ($ret === false) { UOJLog::error(DB::query_str($q)); - UOJLog::error('update failed: '.DB::$conn->error); + UOJLog::error('update failed: ' . DB::$conn->error); } return $ret; } @@ -277,19 +277,19 @@ class DB { $ret = DB::$conn->query(DB::query_str($q)); if ($ret === false) { UOJLog::error(DB::query_str($q)); - UOJLog::error('insert failed: '.DB::$conn->error); + UOJLog::error('insert failed: ' . DB::$conn->error); } return $ret; } public static function insert_id() { return DB::$conn->insert_id; } - + public static function delete($q) { $ret = DB::$conn->query(DB::query_str($q)); if ($ret === false) { UOJLog::error(DB::query_str($q)); - UOJLog::error('delete failed: '.DB::$conn->error); + UOJLog::error('delete failed: ' . DB::$conn->error); } return $ret; } @@ -323,7 +323,7 @@ class DB { return false; } // return $qres->fetch_all($opt); not supported - + $res = []; while ($row = $qres->fetch_array($opt)) { $res[] = $row; @@ -337,21 +337,21 @@ class DB { } return $res->fetch_array($opt); } - public static function selectSingle($q) { - $res = DB::select($q); - if ($res === false) { - return false; - } - $row = $res->fetch_row(); - if (!$row) { - return false; - } - return $row[0]; - } + public static function selectSingle($q) { + $res = DB::select($q); + if ($res === false) { + return false; + } + $row = $res->fetch_row(); + if (!$row) { + return false; + } + 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) { $res = DB::select($q); if ($res === false) { @@ -361,87 +361,87 @@ class DB { return $cnt; } - /** + /** * perform SQL query: select exists ($q); * * on success, returns 0 or 1 * on failure, returns false * * @return int|false - */ - public static function selectExists($q) { - $res = DB::select(["select", DB::exists($q)]); - if ($res === false) { - return false; - } - return (int)($res->fetch_row()[0]); - } - - public static function limit() { - $num = func_get_args(); - if (count($num) == 1) { - return "limit ".((int)$num[0]); - } elseif (count($num) == 2) { - return "limit ".((int)$num[0]).",".((int)$num[1]); - } else { - return false; - } - } + */ + public static function selectExists($q) { + $res = DB::select(["select", DB::exists($q)]); + if ($res === false) { + return false; + } + return (int)($res->fetch_row()[0]); + } - public static function for_share() { - return "for share"; - } - public static function for_update() { - return "for update"; - } + public static function limit() { + $num = func_get_args(); + if (count($num) == 1) { + return "limit " . ((int)$num[0]); + } elseif (count($num) == 2) { + return "limit " . ((int)$num[0]) . "," . ((int)$num[1]); + } else { + return false; + } + } - public static function startTransaction() { - return DB::$conn->begin_transaction(); - } + public static function for_share() { + return "for share"; + } + public static function for_update() { + return "for update"; + } - public static function rollback() { - return DB::$conn->rollback(); - } + public static function startTransaction() { + return DB::$conn->begin_transaction(); + } - public static function commit() { - return DB::$conn->commit(); - } + public static function rollback() { + return DB::$conn->rollback(); + } + + public static function commit() { + return DB::$conn->commit(); + } + + public static function transaction($func) { + if (DB::$in_transaction) { + $ret = $func(); + } else { + DB::$in_transaction = true; + DB::startTransaction(); + $ret = $func(); + DB::commit(); + DB::$in_transaction = false; + } + return $ret; + } - public static function transaction($func) { - if (DB::$in_transaction) { - $ret = $func(); - } else { - DB::$in_transaction = true; - DB::startTransaction(); - $ret = $func(); - DB::commit(); - DB::$in_transaction = false; - } - return $ret; - } - public static function lock($tables, $func) { $q = []; foreach ($tables as $table => $type) { if ($type != DB::WLOCK && $type != DB::RLOCK) { - UOJLog::error('Unknown type: '.$type); + UOJLog::error('Unknown type: ' . $type); return false; } - $q[] = $table.' '.$type; + $q[] = $table . ' ' . $type; } - $q = 'lock tables '.implode(',', $q); + $q = 'lock tables ' . implode(',', $q); DB::query($q); - + $ret = $func(); - + DB::query("unlock tables"); - + return $ret; } public static function checkTableExists($name) { return DB::select(["select 1 from", DB::table($name)]) !== false; } - + public static function num_rows($res) { return $res->num_rows; } diff --git a/web/app/models/UOJContest.php b/web/app/models/UOJContest.php index 773f6d8..5a21f2e 100644 --- a/web/app/models/UOJContest.php +++ b/web/app/models/UOJContest.php @@ -191,114 +191,127 @@ class UOJContest { ignore_user_abort(true); set_time_limit(0); - DB::update([ - "update contests", - "set", ["status" => 'testing'], - "where", ["id" => $this->info['id']] - ]); - - 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, + DB::transaction(function () { + $status = DB::selectSingle([ + "select status from contests", + "where", ["id" => $this->info['id']], + DB::for_update(), ]); - } - // warning: check if this command works well when the database is not MySQL - 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 - ] - ]); + if ($status !== 'unfinished') { + // 已经有其他人开始评测了,不进行任何操作 + return; + } - $updated = []; - foreach ($res as $submission) { - $submitter = $submission['submitter']; - $pid = $submission['problem_id']; - if (isset($updated[$submitter]) && isset($updated[$submitter][$pid])) { - continue; + $res = DB::selectAll([ + "select id, problem_id, content, result, submitter, hide_score_to_others from submissions", + "where", ["contest_id" => $this->info['id']], + DB::for_update(), + ]); + 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])) { - $updated[$submitter] = []; + + // warning: check if this command works well when the database is not MySQL + 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() { diff --git a/web/app/models/UOJSubmission.php b/web/app/models/UOJSubmission.php index be20cd6..f556f93 100644 --- a/web/app/models/UOJSubmission.php +++ b/web/app/models/UOJSubmission.php @@ -359,7 +359,7 @@ class UOJSubmission { ]) ]) ], DB::for_update() - ]); + ]); DB::update(["update submissions", "set", $cfg['set_q'], "where", [$cond]]); }); } else { diff --git a/web/app/upgrade/14_sync_from_uoj.ac/up.sql b/web/app/upgrade/14_sync_from_uoj.ac/up.sql index 8d69501..31a84f5 100644 --- a/web/app/upgrade/14_sync_from_uoj.ac/up.sql +++ b/web/app/upgrade/14_sync_from_uoj.ac/up.sql @@ -1,5 +1,7 @@ -- InnoDB 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 `problems_contents` COLLATE=utf8mb4_unicode_ci; ALTER TABLE `submissions` ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;