Files
OJ2/apps/api/src/db/schema.ts
yuetsh eabb61189c fix(数据库): 索引方向被 .op() 吞掉,schema.ts 和真实库对不上
根因不是 .desc(),是 opclass。drizzle-kit 的 CreatePgIndexConvertor 里那个
三元一旦走进 opclass 分支就回不到方向分支,而 pull 给每一列都挂了 .op(...),
于是"写了 .desc() 却生成不出 DESC"每次都会重演。实测确认:不写 .op() 时
多列混合方向能正常生成,所以原先"多列混合方向的索引别指望 generate"这条
自我限制不成立。

announcement_list_idx 和 contest_create_time_idx 两条真实索引踩在这上面:
生产库里它们带 DESC(schema.sql:1636、:1685),schema.ts 却会生成成全 ASC。
contest_create_time_idx 的 opclass 还串了位(contest_id 标 timestamptz_ops、
create_time 标 int4_ops),那条 SQL 真拿去执行 Postgres 会直接拒绝。

改快照而不是生成迁移:generate 想 drop + recreate,但重建出来的和生产库里
那两条完全等价(DESC 默认就是 NULLS FIRST),执行它只是在 12.3 万行的表上
白挨一次锁。手法和当初处理 problem_tag_name_ci_unique 的 opclass 一致。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 09:53:05 -06:00

675 lines
33 KiB
TypeScript
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.
// 本文件由 `drizzle-kit pull` 从生产库自动生成,之后按下面几条手工维护。
//
// 2026-08-26旧 Django 后端下线7 张框架表auth_group、auth_group_permissions、
// auth_permission、django_content_type、django_dramatiq_task、django_migrations、
// django_session已由 0002_drop_django_leftovers 删除drizzle.config.ts 的
// tablesFilter 随之移除。库里现在就是这 27 张业务表。
//
// 手工修正(都是 `pull` 自己没法无损 round-trip 的地方,改回去会让 generate 产生假 diff
// 详见 CLAUDE.md「改 schema 走 drizzle migration」
// * bigint identity 的 maxValue 用字符串,不能写成 JS number 字面量(会丢精度)。
// * 索引不写 `.desc()`,生成 SQL 时方向会被丢掉。
//
// 关于 10 张表的 bigint idproblemset*、achievement、user_achievement、user_stat、
// user_badge、ai_analysis这是历史巧合不是设计——这些 app 的 0001_initial 生成时
// Django 还没设 DEFAULT_AUTO_FIELD用了 3.2+ 的默认 BigAutoField更早的表user、
// problem、contest、submission都是 int4。现存最大 id 一万出头,确实都用不上 bigint
// 但 2026-08-26 评估后决定**不改**:省 4 字节/行毫无意义ALTER TYPE 要重写整表并拿
// ACCESS EXCLUSIVE 锁,而且其中 6 处 id 被外键绑着得连坐。别再提这件事了。
import { pgTable, index, foreignKey, bigint, text, jsonb, timestamp, integer, boolean, serial, doublePrecision, varchar, unique, uniqueIndex } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"
export const aiAnalysis = pgTable("ai_analysis", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "ai_analysis_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
provider: text().notNull(),
data: jsonb().notNull(),
systemPrompt: text("system_prompt").notNull(),
userPrompt: text("user_prompt").notNull(),
analysis: text().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
userId: integer("user_id").notNull(),
model: text().notNull(),
isPinned: boolean("is_pinned").notNull(),
}, (table) => [
index("ai_analysis_user_id_3aa23011").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "ai_analysis_user_id_3aa23011_fk_user_id"
}),
]);
export const announcement = pgTable("announcement", {
id: serial().primaryKey().notNull(),
title: text().notNull(),
content: text().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
lastUpdateTime: timestamp("last_update_time", { withTimezone: true, mode: 'string' }).notNull(),
visible: boolean().notNull(),
createdById: integer("created_by_id").notNull(),
tag: text().notNull(),
top: boolean().notNull(),
}, (table) => [
index("announcement_created_by_id_359ccf50").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
// 不写 .op()opclass 会吞掉方向(见 CLAUDE.md。生产库是 (visible, top DESC, create_time DESC)
// 写了 .op() 的话 generate 出来的是全 ASCschema.ts 就和真实库对不上了。
index("announcement_list_idx").using("btree", table.visible.asc().nullsLast(), table.top.desc().nullsFirst(), table.createTime.desc().nullsFirst()),
foreignKey({
columns: [table.createdById],
foreignColumns: [user.id],
name: "announcement_created_by_id_359ccf50_fk_user_id"
}),
]);
export const achievement = pgTable("achievement", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "achievement_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
name: text().notNull(),
description: text().notNull(),
icon: text().notNull(),
rarity: text().notNull(),
hidden: boolean().default(false).notNull(),
metric: text().notNull(),
operator: text().notNull(),
threshold: integer().notNull(),
visible: boolean().default(true).notNull(),
unlockCount: integer("unlock_count").default(0).notNull(),
order: integer().default(0).notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
});
export const contest = pgTable("contest", {
id: serial().primaryKey().notNull(),
title: text().notNull(),
description: text().notNull(),
password: text(),
startTime: timestamp("start_time", { withTimezone: true, mode: 'string' }).notNull(),
endTime: timestamp("end_time", { withTimezone: true, mode: 'string' }).notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
lastUpdateTime: timestamp("last_update_time", { withTimezone: true, mode: 'string' }).notNull(),
visible: boolean().notNull(),
createdById: integer("created_by_id").notNull(),
allowedIpRanges: jsonb("allowed_ip_ranges").notNull(),
tag: text().notNull(),
}, (table) => [
index("contest_created_by_id_a763ca7e").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.createdById],
foreignColumns: [user.id],
name: "contest_created_by_id_a763ca7e_fk_user_id"
}),
]);
export const contestAnnouncement = pgTable("contest_announcement", {
id: serial().primaryKey().notNull(),
title: text().notNull(),
content: text().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
contestId: integer("contest_id").notNull(),
createdById: integer("created_by_id").notNull(),
visible: boolean().notNull(),
}, (table) => [
index("contest_announcement_contest_id_a8cb419f").using("btree", table.contestId.asc().nullsLast().op("int4_ops")),
index("contest_announcement_created_by_id_469a14ce").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.contestId],
foreignColumns: [contest.id],
name: "contest_announcement_contest_id_a8cb419f_fk_contest_id"
}),
foreignKey({
columns: [table.createdById],
foreignColumns: [user.id],
name: "contest_announcement_created_by_id_469a14ce_fk_user_id"
}),
]);
export const flowchartSubmission = pgTable("flowchart_submission", {
id: text().primaryKey().notNull(),
mermaidCode: text("mermaid_code").notNull(),
flowchartData: jsonb("flowchart_data").notNull(),
status: integer().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
aiScore: doublePrecision("ai_score"),
aiGrade: varchar("ai_grade", { length: 10 }),
aiFeedback: text("ai_feedback"),
aiSuggestions: text("ai_suggestions"),
aiCriteriaDetails: jsonb("ai_criteria_details").notNull(),
aiProvider: varchar("ai_provider", { length: 50 }).notNull(),
aiModel: varchar("ai_model", { length: 50 }).notNull(),
processingTime: doublePrecision("processing_time"),
evaluationTime: timestamp("evaluation_time", { withTimezone: true, mode: 'string' }),
problemId: integer("problem_id").notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("flowchart_problem_time_idx").using("btree", table.problemId.asc().nullsLast().op("int4_ops"), table.createTime.asc().nullsLast().op("int4_ops")),
index("flowchart_status_idx").using("btree", table.status.asc().nullsLast().op("int4_ops")),
index("flowchart_submission_id_0dbfc4f9_like").using("btree", table.id.asc().nullsLast().op("text_pattern_ops")),
index("flowchart_submission_problem_id_8551edbf").using("btree", table.problemId.asc().nullsLast().op("int4_ops")),
index("flowchart_submission_user_id_225c83e8").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
index("flowchart_user_time_idx").using("btree", table.userId.asc().nullsLast().op("int4_ops"), table.createTime.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.problemId],
foreignColumns: [problem.id],
name: "flowchart_submission_problem_id_8551edbf_fk_problem_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "flowchart_submission_user_id_225c83e8_fk_user_id"
}),
]);
export const message = pgTable("message", {
id: integer().primaryKey().generatedByDefaultAsIdentity({ name: "message_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
message: text().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
recipientId: integer("recipient_id").notNull(),
senderId: integer("sender_id").notNull(),
submissionId: text("submission_id").notNull(),
}, (table) => [
index("message_recipient_id_2aa5dd76").using("btree", table.recipientId.asc().nullsLast().op("int4_ops")),
index("message_recipient_time_idx").using("btree", table.recipientId.asc().nullsLast().op("timestamptz_ops"), table.createTime.asc().nullsLast().op("int4_ops")),
index("message_sender_id_a2a2e825").using("btree", table.senderId.asc().nullsLast().op("int4_ops")),
index("message_submission_id_2fdf8a47").using("btree", table.submissionId.asc().nullsLast().op("text_ops")),
index("message_submission_id_2fdf8a47_like").using("btree", table.submissionId.asc().nullsLast().op("text_pattern_ops")),
foreignKey({
columns: [table.recipientId],
foreignColumns: [user.id],
name: "message_recipient_id_2aa5dd76_fk_user_id"
}),
foreignKey({
columns: [table.senderId],
foreignColumns: [user.id],
name: "message_sender_id_a2a2e825_fk_user_id"
}),
foreignKey({
columns: [table.submissionId],
foreignColumns: [submission.id],
name: "message_submission_id_2fdf8a47_fk_submission_id"
}),
]);
export const judgeServer = pgTable("judge_server", {
id: serial().primaryKey().notNull(),
hostname: text().notNull(),
ip: text(),
judgerVersion: text("judger_version").notNull(),
cpuCore: integer("cpu_core").notNull(),
memoryUsage: doublePrecision("memory_usage").notNull(),
cpuUsage: doublePrecision("cpu_usage").notNull(),
lastHeartbeat: timestamp("last_heartbeat", { withTimezone: true, mode: 'string' }).notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
taskNumber: integer("task_number").notNull(),
serviceUrl: text("service_url"),
isDisabled: boolean("is_disabled").notNull(),
});
export const exercise = pgTable("exercise", {
id: integer().primaryKey().generatedByDefaultAsIdentity({ name: "exercise_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
type: varchar({ length: 16 }).notNull(),
data: jsonb().notNull(),
order: integer().notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).notNull(),
tutorialId: integer("tutorial_id").notNull(),
}, (table) => [
index("exercise_tutorial_id_6fd04055").using("btree", table.tutorialId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.tutorialId],
foreignColumns: [tutorial.id],
name: "exercise_tutorial_id_6fd04055_fk_tutorial_id"
}),
]);
export const optionsSysoptions = pgTable("options_sysoptions", {
id: serial().primaryKey().notNull(),
key: text().notNull(),
value: jsonb().notNull(),
}, (table) => [
unique("options_sysoptions_key_key").on(table.key),
]);
export const problemset = pgTable("problemset", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "problemset_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
title: text().notNull(),
description: text().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
lastUpdateTime: timestamp("last_update_time", { withTimezone: true, mode: 'string' }).notNull(),
visible: boolean().notNull(),
difficulty: text().notNull(),
status: text().notNull(),
createdById: integer("created_by_id").notNull(),
endTime: timestamp("end_time", { withTimezone: true, mode: 'string' }),
}, (table) => [
index("problemset_created_by_id_01b5197f").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.createdById],
foreignColumns: [user.id],
name: "problemset_created_by_id_01b5197f_fk_user_id"
}),
]);
export const problemsetProblem = pgTable("problemset_problem", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "problemset_problem_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
order: integer().notNull(),
isRequired: boolean("is_required").notNull(),
score: integer().notNull(),
hint: text(),
problemId: integer("problem_id").notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
problemsetId: bigint("problemset_id", { mode: "number" }).notNull(),
}, (table) => [
index("problemset_problem_problem_id_fff2d686").using("btree", table.problemId.asc().nullsLast().op("int4_ops")),
index("problemset_problem_problemset_id_350d17fb").using("btree", table.problemsetId.asc().nullsLast().op("int8_ops")),
foreignKey({
columns: [table.problemId],
foreignColumns: [problem.id],
name: "problemset_problem_problem_id_fff2d686_fk_problem_id"
}),
foreignKey({
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_problem_problemset_id_350d17fb_fk_problemset_id"
}),
unique("unique_problemset_problem").on(table.problemId, table.problemsetId),
]);
export const problemsetProgress = pgTable("problemset_progress", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "problemset_progress_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
joinTime: timestamp("join_time", { withTimezone: true, mode: 'string' }).notNull(),
completeTime: timestamp("complete_time", { withTimezone: true, mode: 'string' }),
isCompleted: boolean("is_completed").notNull(),
progressPercentage: doublePrecision("progress_percentage").notNull(),
completedProblemsCount: integer("completed_problems_count").notNull(),
totalProblemsCount: integer("total_problems_count").notNull(),
totalScore: integer("total_score").notNull(),
progressDetail: jsonb("progress_detail").notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
problemsetId: bigint("problemset_id", { mode: "number" }).notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("problemset_progress_problemset_id_20a9632e").using("btree", table.problemsetId.asc().nullsLast().op("int8_ops")),
index("problemset_progress_user_id_c8041a80").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_progress_problemset_id_20a9632e_fk_problemset_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "problemset_progress_user_id_c8041a80_fk_user_id"
}),
unique("unique_problemset_progress_user").on(table.problemsetId, table.userId),
]);
export const problemsetSubmission = pgTable("problemset_submission", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "problemset_submission_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
problemId: integer("problem_id").notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
problemsetId: bigint("problemset_id", { mode: "number" }).notNull(),
submissionId: text("submission_id").notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("problemset__problem_1f39fa_idx").using("btree", table.problemsetId.asc().nullsLast().op("int4_ops"), table.userId.asc().nullsLast().op("int4_ops")),
index("problemset__problem_22f053_idx").using("btree", table.problemsetId.asc().nullsLast().op("int8_ops"), table.problemId.asc().nullsLast().op("int8_ops")),
index("problemset__user_id_2f1501_idx").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
index("problemset_submission_problem_id_5629b105").using("btree", table.problemId.asc().nullsLast().op("int4_ops")),
index("problemset_submission_problemset_id_85290e17").using("btree", table.problemsetId.asc().nullsLast().op("int8_ops")),
index("problemset_submission_submission_id_78e2b807").using("btree", table.submissionId.asc().nullsLast().op("text_ops")),
index("problemset_submission_submission_id_78e2b807_like").using("btree", table.submissionId.asc().nullsLast().op("text_pattern_ops")),
index("problemset_submission_user_id_915fc9c6").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.problemId],
foreignColumns: [problem.id],
name: "problemset_submission_problem_id_5629b105_fk_problem_id"
}),
foreignKey({
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_submission_problemset_id_85290e17_fk_problemset_id"
}),
foreignKey({
columns: [table.submissionId],
foreignColumns: [submission.id],
name: "problemset_submission_submission_id_78e2b807_fk_submission_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "problemset_submission_user_id_915fc9c6_fk_user_id"
}),
]);
export const reaction = pgTable("reaction", {
id: integer().primaryKey().generatedByDefaultAsIdentity({ name: "reaction_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
type: varchar({ length: 20 }).notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
problemId: integer("problem_id").notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("reaction_problem_id_a7f3b9f3").using("btree", table.problemId.asc().nullsLast().op("int4_ops")),
index("reaction_problem_type_idx").using("btree", table.problemId.asc().nullsLast().op("int4_ops"), table.type.asc().nullsLast().op("int4_ops")),
index("reaction_user_id_cfa7f469").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.problemId],
foreignColumns: [problem.id],
name: "reaction_problem_id_a7f3b9f3_fk_problem_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "reaction_user_id_cfa7f469_fk_user_id"
}),
unique("reaction_problem_user_unique").on(table.problemId, table.userId),
]);
export const problem = pgTable("problem", {
id: serial().primaryKey().notNull(),
title: text().notNull(),
description: text().notNull(),
inputDescription: text("input_description").notNull(),
outputDescription: text("output_description").notNull(),
samples: jsonb().notNull(),
testCaseId: text("test_case_id").notNull(),
testCaseScore: jsonb("test_case_score").notNull(),
hint: text(),
languages: jsonb().notNull(),
template: jsonb().notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
lastUpdateTime: timestamp("last_update_time", { withTimezone: true, mode: 'string' }),
timeLimit: integer("time_limit").notNull(),
memoryLimit: integer("memory_limit").notNull(),
visible: boolean().default(true).notNull(),
difficulty: text().notNull(),
source: text(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
submissionNumber: bigint("submission_number", { mode: "number" }).default(0).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
acceptedNumber: bigint("accepted_number", { mode: "number" }).default(0).notNull(),
createdById: integer("created_by_id").notNull(),
displayId: text("_id").notNull(),
statisticInfo: jsonb("statistic_info").default({}).notNull(),
contestId: integer("contest_id"),
isPublic: boolean("is_public").default(false).notNull(),
shareSubmission: boolean("share_submission").default(false).notNull(),
prompt: text(),
answers: jsonb(),
allowFlowchart: boolean("allow_flowchart").default(false).notNull(),
flowchartData: jsonb("flowchart_data").default({}).notNull(),
flowchartHint: text("flowchart_hint"),
mermaidCode: text("mermaid_code"),
showFlowchart: boolean("show_flowchart").default(false).notNull(),
astRules: jsonb("ast_rules"),
sqlConfig: jsonb("sql_config"),
sqlDisplay: jsonb("sql_display"),
}, (table) => [
index("problem__id_919b1d80").using("btree", table.displayId.asc().nullsLast().op("text_ops")),
index("problem_contest_id_328e013a").using("btree", table.contestId.asc().nullsLast().op("int4_ops")),
index("problem_contest_visible_idx").using("btree", table.contestId.asc().nullsLast().op("bool_ops"), table.visible.asc().nullsLast().op("int4_ops")),
index("problem_created_by_id_cb362143").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
index("problem_visible_idx").using("btree", table.visible.asc().nullsLast().op("bool_ops")),
foreignKey({
columns: [table.contestId],
foreignColumns: [contest.id],
name: "problem_contest_id_328e013a_fk_contest_id"
}),
foreignKey({
columns: [table.createdById],
foreignColumns: [user.id],
name: "problem_created_by_id_cb362143_fk_user_id"
}),
unique("unique_problem_id_contest").on(table.displayId, table.contestId),
]);
export const problemTags = pgTable("problem_tags", {
id: serial().primaryKey().notNull(),
problemId: integer("problem_id").notNull(),
problemtagId: integer("problemtag_id").notNull(),
}, (table) => [
index("problem_tags_problem_id_866ecb8d").using("btree", table.problemId.asc().nullsLast().op("int4_ops")),
index("problem_tags_problemtag_id_72d20571").using("btree", table.problemtagId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.problemId],
foreignColumns: [problem.id],
name: "problem_tags_problem_id_866ecb8d_fk_problem_id"
}),
foreignKey({
columns: [table.problemtagId],
foreignColumns: [problemTag.id],
name: "problem_tags_problemtag_id_72d20571_fk_problem_tag_id"
}),
unique("problem_tags_problem_id_problemtag_id_318459d1_uniq").on(table.problemId, table.problemtagId),
]);
export const problemTag = pgTable("problem_tag", {
id: serial().primaryKey().notNull(),
name: text().notNull(),
}, (table) => [
uniqueIndex("problem_tag_name_ci_unique").using("btree", sql`lower(name)`),
]);
export const submission = pgTable("submission", {
id: text().primaryKey().notNull(),
contestId: integer("contest_id"),
problemId: integer("problem_id").notNull(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }).notNull(),
userId: integer("user_id").notNull(),
code: text().notNull(),
result: integer().default(6).notNull(),
info: jsonb().default({}).notNull(),
language: text().notNull(),
shared: boolean().default(false).notNull(),
statisticInfo: jsonb("statistic_info").default({}).notNull(),
username: text().notNull(),
ip: text(),
}, (table) => [
// 同上,不写 .op()。原先 pull 出来的 opclass 还串了位contest_id 标成 timestamptz_ops、
// create_time 标成 int4_ops那条 SQL 真拿去执行 Postgres 会直接拒绝。
index("contest_create_time_idx").using("btree", table.contestId.asc().nullsLast(), table.createTime.desc().nullsFirst()),
// 提交列表默认视图WHERE contest_id IS NULL ORDER BY create_time DESC专用。
// 上面的 contest_create_time_idx 看着能覆盖,但 Postgres 不把 `contest_id IS NULL`
// 当成能吃掉首列、从而继承第二列有序性的等值条件——把 seqscan/bitmapscan 全关掉逼它
// 也不肯用,只会走单列 contest_id 索引再全量排序。结果是每翻一页都 Parallel Seq Scan
// 扫完整张表 + top-N 排序。改用部分索引后谓词由索引本身保证,排序序就是索引序。
// 生产快照12.3 万条提交实测61.8ms / 18936 blocks → 0.22ms / 34 blocks。
// 这个索引不在 Django 的 migration 里,是 OJ2 单独加的,见 src/db/0001_naive_agent_zero.sql。
// 不写 .desc():这条带 .op(),而 .op() 会吞掉方向(见 CLAUDE.md——写了只会让快照
// (记 asc:false和实际建出来的索引ASC对不上。单列索引本来也无所谓方向Postgres 用
// Index Scan Backward 服务 ORDER BY ... DESC实测同样是 0.08ms。
index("submission_public_create_time_idx").using("btree", table.createTime.op("timestamptz_ops")).where(sql`${table.contestId} is null`),
index("problem_user_idx").using("btree", table.problemId.asc().nullsLast().op("int4_ops"), table.userId.asc().nullsLast().op("int4_ops")),
index("submission_contest_id_775716d5").using("btree", table.contestId.asc().nullsLast().op("int4_ops")),
index("submission_problem_id_76847b55").using("btree", table.problemId.asc().nullsLast().op("int4_ops")),
index("submission_result_37e2f67a").using("btree", table.result.asc().nullsLast().op("int4_ops")),
index("submission_user_id_3779a8c1").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
index("user_create_time_idx").using("btree", table.userId.asc().nullsLast().op("int4_ops"), table.createTime.asc().nullsLast().op("timestamptz_ops")),
foreignKey({
columns: [table.contestId],
foreignColumns: [contest.id],
name: "submission_contest_id_775716d5_fk_contest_id"
}),
foreignKey({
columns: [table.problemId],
foreignColumns: [problem.id],
name: "submission_problem_id_76847b55_fk_problem_id"
}),
]);
export const tutorial = pgTable("tutorial", {
id: integer().primaryKey().generatedByDefaultAsIdentity({ name: "tutorial_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
title: varchar({ length: 128 }).notNull(),
content: text().notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).notNull(),
isPublic: boolean("is_public").notNull(),
order: integer().notNull(),
createdById: integer("created_by_id").notNull(),
code: text(),
type: varchar({ length: 10 }).notNull(),
}, (table) => [
index("tutorial_created_by_id_07973cab").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.createdById],
foreignColumns: [user.id],
name: "tutorial_created_by_id_07973cab_fk_user_id"
}),
]);
export const userStat = pgTable("user_stat", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "user_stat_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
metrics: jsonb().default({}).notNull(),
updateTime: timestamp("update_time", { withTimezone: true, mode: 'string' }).notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "user_stat_user_id_73337fc0_fk_user_id"
}),
unique("user_stat_user_id_key").on(table.userId),
]);
export const userAchievement = pgTable("user_achievement", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "user_achievement_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
unlockTime: timestamp("unlock_time", { withTimezone: true, mode: 'string' }).notNull(),
backfilled: boolean().default(false).notNull(),
notified: boolean().default(false).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
achievementId: bigint("achievement_id", { mode: "number" }).notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("user_achievement_achievement_id_29db600d").using("btree", table.achievementId.asc().nullsLast().op("int8_ops")),
index("user_achievement_user_id_b8ec7d6a").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
index("user_achv_notified_idx").using("btree", table.userId.asc().nullsLast().op("int4_ops"), table.notified.asc().nullsLast().op("bool_ops")),
index("user_achv_time_idx").using("btree", table.userId.asc().nullsLast().op("int4_ops"), table.unlockTime.desc().nullsFirst().op("timestamptz_ops")),
foreignKey({
columns: [table.achievementId],
foreignColumns: [achievement.id],
name: "user_achievement_achievement_id_29db600d_fk_achievement_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "user_achievement_user_id_b8ec7d6a_fk_user_id"
}),
unique("unique_user_achievement").on(table.achievementId, table.userId),
]);
export const userBadge = pgTable("user_badge", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "user_badge_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
earnedTime: timestamp("earned_time", { withTimezone: true, mode: 'string' }).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
badgeId: bigint("badge_id", { mode: "number" }).notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("user_badge_badge_id_92a983e9").using("btree", table.badgeId.asc().nullsLast().op("int8_ops")),
index("user_badge_user_id_a286d718").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.badgeId],
foreignColumns: [problemsetBadge.id],
name: "user_badge_badge_id_92a983e9_fk_problemset_badge_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "user_badge_user_id_a286d718_fk_user_id"
}),
unique("unique_user_badge").on(table.badgeId, table.userId),
]);
export const userProfile = pgTable("user_profile", {
id: serial().primaryKey().notNull(),
acmProblemsStatus: jsonb("acm_problems_status").default({}).notNull(),
avatar: text().notNull(),
blog: varchar({ length: 200 }),
mood: text(),
acceptedNumber: integer("accepted_number").default(0).notNull(),
submissionNumber: integer("submission_number").default(0).notNull(),
github: text(),
school: text(),
major: text(),
userId: integer("user_id").notNull(),
realName: text("real_name"),
language: text(),
}, (table) => [
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "user_profile_user_id_8fdce8e2_fk_user_id"
}),
unique("user_profile_user_id_key").on(table.userId),
]);
export const acmContestRank = pgTable("acm_contest_rank", {
id: serial().primaryKey().notNull(),
submissionNumber: integer("submission_number").default(0).notNull(),
acceptedNumber: integer("accepted_number").default(0).notNull(),
totalTime: integer("total_time").default(0).notNull(),
submissionInfo: jsonb("submission_info").default({}).notNull(),
contestId: integer("contest_id").notNull(),
userId: integer("user_id").notNull(),
}, (table) => [
index("acm_contest_rank_contest_id_21030ccd").using("btree", table.contestId.asc().nullsLast().op("int4_ops")),
index("acm_contest_rank_user_id_40391ab2").using("btree", table.userId.asc().nullsLast().op("int4_ops")),
index("acm_rank_contest_user_idx").using("btree", table.contestId.asc().nullsLast().op("int4_ops"), table.userId.asc().nullsLast().op("int4_ops")),
index("acm_rank_order_idx").using("btree", table.contestId.asc().nullsLast().op("int4_ops"), table.acceptedNumber.asc().nullsLast().op("int4_ops"), table.totalTime.asc().nullsLast().op("int4_ops")),
foreignKey({
columns: [table.contestId],
foreignColumns: [contest.id],
name: "acm_contest_rank_contest_id_21030ccd_fk_contest_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "acm_contest_rank_user_id_40391ab2_fk_user_id"
}),
unique("unique_acm_rank_user_contest").on(table.contestId, table.userId),
]);
export const user = pgTable("user", {
id: serial().primaryKey().notNull(),
password: varchar({ length: 128 }).notNull(),
lastLogin: timestamp("last_login", { withTimezone: true, mode: 'string' }),
username: text().notNull(),
email: text(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }),
adminType: text("admin_type").notNull(),
authToken: text("auth_token"),
openApi: boolean("open_api").default(false).notNull(),
openApiAppkey: text("open_api_appkey"),
isDisabled: boolean("is_disabled").default(false).notNull(),
problemPermission: text("problem_permission").notNull(),
sessionKeys: jsonb("session_keys").default([]).notNull(),
rawPassword: varchar("raw_password", { length: 20 }),
className: text("class_name"),
}, (table) => [
unique("user_username_key").on(table.username),
]);
export const problemsetBadge = pgTable("problemset_badge", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id: bigint({ mode: "number" }).primaryKey().generatedByDefaultAsIdentity({ name: "problemset_badge_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: "9223372036854775807", cache: 1 }),
name: text().notNull(),
description: text().notNull(),
icon: text().notNull(),
conditionType: text("condition_type").notNull(),
conditionValue: integer("condition_value").notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
problemsetId: bigint("problemset_id", { mode: "number" }).notNull(),
}, (table) => [
index("problemset_badge_problemset_id_6cb6c74f").using("btree", table.problemsetId.asc().nullsLast().op("int8_ops")),
foreignKey({
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_badge_problemset_id_6cb6c74f_fk_problemset_id"
}),
]);