fix add/fetch all questions

This commit is contained in:
David Westgate 2024-06-02 18:08:22 -07:00
parent 036f4c4779
commit 094496961a
3 changed files with 102 additions and 54 deletions

View File

@ -15,7 +15,9 @@ CREATE TABLE IF NOT EXISTS tags (
CREATE TABLE IF NOT EXISTS question_tag (
question_id INT REFERENCES questions(id),
tag_id INT REFERENCES tags(id),
PRIMARY KEY (question_id, tag_id)
PRIMARY KEY (question_id, tag_id),
FOREIGN KEY (question_id) REFERENCES questions(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
-- Create answers table

View File

@ -5,8 +5,9 @@ use self::{
question::{NewQuestion, QuestionDTO},
};
use crate::*;
const DEFAULT_PAGE: usize = 0;
const DEFAULT_PAGE_SIZE: usize = 10;
// sqlx wants 'bigint' = i32
const DEFAULT_PAGE: i32 = 0;
const DEFAULT_PAGE_SIZE: i32 = 10;
/// Fetches a single question using a provided id. First we fetch the question, then the questions tags, then build that into a response DTO
/// # Parameters
@ -43,23 +44,28 @@ pub async fn read_questions(
State(store): State<Arc<RwLock<Store>>>,
Query(pagination): Query<Pagination>,
) -> Response {
let page: usize = pagination.page.unwrap_or(DEFAULT_PAGE);
let size: usize = pagination.size.unwrap_or(DEFAULT_PAGE_SIZE);
let start: usize = page * size;
let page: i32 = pagination.page.unwrap_or(DEFAULT_PAGE);
let size: i32 = pagination.size.unwrap_or(DEFAULT_PAGE_SIZE);
let start: i32 = page * size;
let questions_option = store.read().await.fetch_many_questions(start, size).await;
match questions_option {
Some(questions) => {
println!("Num question {}", questions.len());
let mut response_vec_dto: Vec<QuestionDTO> = vec![];
for question in questions {
//Not ideal - hitting the database serially for the tags of each invidual question. Can be optimized with more complex sql
let tags = store
.read()
.await
.fetch_tags_by_property(question.id, "question_id")
.await
.unwrap_or(vec![]);
let question_dto: QuestionDTO = QuestionDTO::new(question, tags);
response_vec_dto.push(question_dto)
let tags_option = store.read().await.get_tags_for_question(question.id).await;
match tags_option {
Some(tags) => {
println!("tags {:?}", tags);
let question_dto: QuestionDTO = QuestionDTO::new(question, tags);
response_vec_dto.push(question_dto)
}
None => {
let question_dto: QuestionDTO = QuestionDTO::new(question, vec![]);
response_vec_dto.push(question_dto)
}
}
}
(StatusCode::OK, Json(response_vec_dto)).into_response()
}
@ -179,6 +185,6 @@ pub async fn create_answer(
#[derive(Debug, Deserialize)]
pub struct Pagination {
page: Option<usize>,
size: Option<usize>,
page: Option<i32>,
size: Option<i32>,
}

View File

@ -29,7 +29,7 @@ impl Store {
.await
{
Ok(pool) => pool,
Err(_) => panic!("Could not estable database connection"),
Err(_) => panic!("Could not establish database connection"),
};
sqlx::migrate!().run(&db_pool).await.unwrap();
Store {
@ -39,9 +39,12 @@ impl Store {
//Get the question tags for a particular question
pub async fn get_tags_for_question(&self, question_id: u8) -> Option<Vec<Tag>> {
let query = "SELECT label FROM tag JOIN question_tag on tag.id = question_tag.tag_id JOIN question on tag.question_id = $1";
let query = "SELECT tags.id, tags.label FROM tags
JOIN question_tag on tags.id = question_tag.tag_id
JOIN questions on question_tag.question_id = questions.id
WHERE questions.id = ($1)";
let result = sqlx::query(query)
.bind(question_id.to_string())
.bind(i32::from(question_id))
.fetch_all(&self.connection)
.await;
match result {
@ -52,7 +55,10 @@ impl Store {
.collect::<Vec<Tag>>();
Some(tags)
}
_ => None,
Err(e) => {
println!("{}", e);
None
}
}
}
@ -97,47 +103,68 @@ impl Store {
// Add new tags to tags table, only if tags with existing label do not exist. If they already exist, just ignore that
// Returns list of tags
pub async fn add_tags(&mut self, tag_labels: Vec<String>) -> Result<Vec<Tag>, String> {
let insert_values = tag_labels.join(",");
let query =
"INSERT INTO tags (label) VALUES ($1) ON CONFLICT (label) DO NOTHING RETURNING *";
let result = sqlx::query(query)
.bind(insert_values)
.fetch_all(&self.connection)
let insert_query = "
INSERT INTO tags (label)
SELECT * FROM UNNEST(($1)::text[]) AS label
ON CONFLICT (label) DO NOTHING
";
//First run the insert query on the new labels
let insert_result = sqlx::query(insert_query)
.bind(&tag_labels)
.execute(&self.connection)
.await;
match result {
Ok(pg_rows) => {
let tags: Vec<Tag> = pg_rows
.iter()
.map(|pg_row| Tag::new(Store::id_to_u8(pg_row, "id"), pg_row.get("label")))
.collect();
Ok(tags)
match insert_result {
Ok(_) => {
//Then run the select query on the new labels (which may include already existing labels)
let select_query = "
SELECT id, label FROM tags
WHERE label = ANY($1::text[]);
";
match sqlx::query(select_query)
.bind(&tag_labels)
.fetch_all(&self.connection)
.await
{
Ok(pg_rows) => {
let tags: Vec<Tag> = pg_rows
.iter()
.map(|pg_row| {
Tag::new(Store::id_to_u8(pg_row, "id"), pg_row.get("label"))
})
.collect();
Ok(tags)
}
Err(e) => Err(e.to_string()),
}
}
Err(e) => Err(e.to_string()),
}
}
//Takes a question id and list of tags, and creates question_tag join associations
//Ignores if association already exists
//Returns list of the question tag associations
/// Takes a question id and list of tags, and creates question_tag join associations
/// Ignores if association already exists
/// Returns list of the question tag associations
pub async fn associate_tags(
&mut self,
question_id: u8,
tags: &[Tag],
) -> Result<Vec<QuestionTag>, String> {
//for the query binding we must convert the tag vector to a comma seperated string list of questionid/tagid tuples
let question_id_tag_id_tuple_string = tags
.iter()
.map(|tag| format!("({},{})", question_id, tag.id))
.collect::<Vec<String>>()
.join(",");
let tag_ids_string: Vec<String> = tags.iter().map(|tag| tag.id.to_string()).collect();
let query = "INSERT INTO question_tag VALUES ($1) ON CONFLICT (question_id, tag_id) DO NOTHING RETURNING *";
let query = "
INSERT INTO question_tag (question_id, tag_id)
SELECT $1::smallint, UNNEST($2::smallint[])
ON CONFLICT DO NOTHING;
";
let result = sqlx::query(query)
.bind(question_id_tag_id_tuple_string)
.bind(question_id.to_string())
.bind(tag_ids_string)
.fetch_all(&self.connection)
.await;
match result {
Ok(pg_rows) => {
println!("Num rows {}", pg_rows.len());
let question_tags: Vec<QuestionTag> = pg_rows
.iter()
.map(|pg_row| {
@ -178,25 +205,32 @@ impl Store {
}
// Fetch a list of tags by either the tag id, or by the label. Up to the caller
pub async fn fetch_tags_by_property(
pub async fn _fetch_tags_by_property(
&self,
propert_id: u8,
property_type: &str,
) -> Option<Vec<Tag>> {
let result = sqlx::query("SELECT * FROM tags WHERE $1 = $2")
.bind(property_type)
println!("Property type {}", property_type);
println!("Property id {} ", propert_id);
let query = format!("SELECT * FROM tags WHERE {} = ($2);", property_type).to_string(); //looks risky, but user does not get to control property type
let result = sqlx::query(&query)
.bind(property_type.to_string())
.bind(propert_id.to_string())
.fetch_all(&self.connection)
.await;
match result {
Ok(tag_rows) => {
let tags: Vec<Tag> = tag_rows
Ok(pg_rows) => {
println!("num tag rows {}", pg_rows.len());
let tags: Vec<Tag> = pg_rows
.iter()
.map(|pg_row| Tag::new(Store::id_to_u8(pg_row, "id"), pg_row.get("label")))
.collect();
Some(tags)
}
_ => None,
Err(e) => {
println!("err {}", e);
None
}
}
}
@ -292,15 +326,17 @@ impl Store {
}
// Fetch many questions - do not worry about joining the tags
pub async fn fetch_many_questions(&self, start: usize, size: usize) -> Option<Vec<Question>> {
pub async fn fetch_many_questions(&self, start: i32, size: i32) -> Option<Vec<Question>> {
let rows_result: Result<Vec<PgRow>, sqlx::Error> =
sqlx::query("SELECT * FROM questions ORDER BY id LIMIT $1 OFFSET $2")
.bind(size.to_string())
.bind(start.to_string())
.bind(size)
.bind(start)
.fetch_all(&self.connection)
.await;
match rows_result {
Ok(pg_rows) => {
println!("num rows {}", pg_rows.len());
let mut result: Vec<Question> = vec![];
for pg_row in pg_rows {
result.push(Question::new(
@ -311,7 +347,10 @@ impl Store {
}
Some(result)
}
_ => None,
Err(e) => {
println!("{}", e);
None
}
}
}
@ -349,6 +388,7 @@ impl Store {
}
}
// Add an answer entity
pub async fn add_answer(&mut self, new_answer: NewAnswer) -> Result<Answer, String> {
let result =
sqlx::query("INSERT INTO answers VALUES ($1,$2) RETURNING id, content, question_id")