1use crate::qp::Exam;
6
7#[allow(dead_code)]
9const INIT_DB: &str = "
10CREATE TABLE IF NOT EXISTS iqps (
11 id integer primary key GENERATED ALWAYS AS identity,
12 course_code TEXT NOT NULL DEFAULT '',
13 course_name TEXT NOT NULL DEFAULT '',
14 year INTEGER NOT NULL,
15 exam TEXT NOT NULL DEFAULT '',
16 semester TEXT NOT NULL DEFAULT '',
17 note TEXT NOT NULL DEFAULT '',
18 filelink TEXT NOT NULL,
19 from_library BOOLEAN DEFAULT FALSE,
20 upload_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
21 approve_status BOOLEAN DEFAULT FALSE,
22 approved_by TEXT DEFAULT '',
23 is_deleted BOOLEAN DEFAULT FALSE,
24 fts_course_details tsvector GENERATED ALWAYS AS (to_tsvector('english', course_code || ' ' || course_name)) stored
25);
26CREATE INDEX IF NOT EXISTS iqps_fts ON iqps USING gin (fts_course_details);
27CREATE EXTENSION pg_trgm;
28CREATE INDEX IF NOT EXISTS idx_course_name_trgm ON iqps USING gin (course_name gin_trgm_ops);";
29
30pub fn get_similar_papers_query(year: bool, semester: bool, exam: bool) -> String {
38 let mut param_num = 1;
39
40 format!(
41 "SELECT {} from iqps where is_deleted=false and course_code = $1 {} {} {}",
42 ADMIN_DASHBOARD_QP_FIELDS,
43 if year {
44 param_num += 1;
45 format!("AND year=${}", param_num)
46 } else {
47 "".to_string()
48 },
49 if semester {
50 param_num += 1;
51 format!("AND semester=${}", param_num)
52 } else {
53 "".to_string()
54 },
55 if exam {
56 param_num += 1;
57 format!("AND exam=${}", param_num)
58 } else {
59 "".to_string()
60 },
61 )
62}
63
64pub const SOFT_DELETE_BY_ID: &str =
66 "UPDATE iqps SET approve_status=false, is_deleted = true WHERE id=$1 AND from_library = false";
67
68pub const SOFT_DELETE_ANY_BY_ID: &str =
70 "UPDATE iqps SET approve_status=false, is_deleted = true WHERE id=$1";
71
72pub const HARD_DELETE_BY_ID: &str =
74 "DELETE FROM iqps WHERE id=$1";
75
76pub fn get_get_soft_deleted_papers_query() -> String {
78 format!("SELECT {} FROM iqps WHERE is_deleted=true", ADMIN_DASHBOARD_QP_FIELDS)
79}
80
81pub fn get_get_paper_by_id_query() -> String {
83 format!(
84 "SELECT {} FROM iqps WHERE id = $1",
85 ADMIN_DASHBOARD_QP_FIELDS
86 )
87}
88
89pub fn get_edit_paper_query(approval: bool) -> String {
105 format!(
106 "UPDATE iqps set course_code=$2, course_name=$3, year=$4, semester=$5, exam=$6, note=$7, approve_status=$8, filelink=$9{} WHERE id=$1 AND is_deleted=false RETURNING {}",
107 if approval {", approved_by=$10"} else {""},
108 ADMIN_DASHBOARD_QP_FIELDS
109 )
110}
111
112pub fn get_all_unapproved_query() -> String {
114 format!("SELECT {} FROM iqps WHERE approve_status = false and is_deleted=false ORDER BY upload_timestamp ASC", ADMIN_DASHBOARD_QP_FIELDS)
115}
116
117pub const GET_UNAPPROVED_COUNT: &str =
119 "SELECT COUNT(*) FROM iqps WHERE approve_status = false AND is_deleted = false";
120
121pub fn get_qp_search_query(exam_filter: Vec<Exam>) -> String {
130 let exam_filter_clause = exam_filter
131 .iter()
132 .map(|&exam| {
133 if let Exam::CT(_) = exam {
134 "exam LIKE 'ct%'".into()
135 } else {
136 format!("exam = '{}'", String::from(exam))
137 }
138 })
139 .collect::<Vec<String>>()
140 .join(" OR ");
141
142 let exam_clause_str = if exam_filter_clause.is_empty() {
143 "".into()
144 } else {
145 format!("WHERE ({} OR exam = '')", exam_filter_clause)
146 };
147
148 format!("
149 WITH filtered AS (
150 SELECT * from iqps {exam_filter} ORDER BY year DESC
151 ),
152 fuzzy AS (
153 SELECT id,
154 similarity(course_code || ' ' || course_name, $1) AS sim_score,
155 row_number() OVER (ORDER BY similarity(course_code || ' ' || course_name, $1) DESC) AS rank_ix
156 FROM filtered
157 WHERE (course_code || ' ' || course_name) %>> $1 AND approve_status = true
158 ORDER BY rank_ix
159 LIMIT 30
160 ),
161 full_text AS (
162 SELECT id,
163 ts_rank_cd(fts_course_details, websearch_to_tsquery($1)) AS rank_score,
164 row_number() OVER (ORDER BY ts_rank_cd(fts_course_details, websearch_to_tsquery($1)) DESC) AS rank_ix
165 FROM filtered
166 WHERE fts_course_details @@ websearch_to_tsquery($1) AND approve_status = true
167 ORDER BY rank_ix
168 LIMIT 30
169 ),
170 partial_search AS (
171 SELECT id,
172 ts_rank_cd(fts_course_details, {to_tsquery}) AS rank_score,
173 row_number() OVER (ORDER BY ts_rank_cd(fts_course_details, {to_tsquery}) DESC) as rank_ix
174 FROM filtered
175 WHERE fts_course_details @@ {to_tsquery} AND approve_status = true
176 LIMIT 30
177 ),
178 result AS (
179 SELECT {intermediate_fields}
180 FROM fuzzy
181 FULL OUTER JOIN full_text ON fuzzy.id = full_text.id
182 FULL OUTER JOIN partial_search ON coalesce(fuzzy.id, full_text.id) = partial_search.id
183 JOIN filtered ON coalesce(fuzzy.id, full_text.id, partial_search.id) = filtered.id
184 ORDER BY
185 coalesce(1.0 / (50 + fuzzy.rank_ix), 0.0) * 1 +
186 coalesce(1.0 / (50 + full_text.rank_ix), 0.0) * 1 +
187 coalesce(1.0 / (50 + partial_search.rank_ix), 0.0) * 1
188 DESC
189 ) SELECT {search_qp_fields} FROM result",
190 search_qp_fields = SEARCH_QP_FIELDS,
191 to_tsquery = "to_tsquery('simple', websearch_to_tsquery('simple', $1)::text || ':*')",
192 exam_filter = exam_clause_str,
193 intermediate_fields = ADMIN_DASHBOARD_QP_FIELDS.split(", ").map(|field| format!("filtered.{}", field)).collect::<Vec<String>>().join(", ")
194 )
195}
196
197pub const ADMIN_DASHBOARD_QP_FIELDS: &str = "id, filelink, from_library, course_code, course_name, year, semester, exam, note, upload_timestamp, approve_status";
199
200pub const SEARCH_QP_FIELDS: &str =
202 "id, filelink, from_library, course_code, course_name, year, semester, exam, note";
203
204pub const INSERT_NEW_QP: &str = "INSERT INTO iqps (course_code, course_name, year, exam, semester, note, filelink, from_library) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id";
207
208pub const UPDATE_FILELINK: &str = "UPDATE iqps SET filelink=$2 WHERE id=$1";
210
211pub const INSERT_NEW_LIBRARY_QP: &str = "INSERT INTO iqps (course_code, course_name, year, exam, semester, note, filelink, from_library, approve_status) VALUES ($1, $2, $3, $4, $5, $6, $7, true, $8) RETURNING id";