use async_trait::async_trait; use chrono::{NaiveDate, NaiveDateTime}; use sql_parse::Expression; use tokio_postgres::Client; use crate::utils::{fix_annotation_text, parse_lang, remove_wrong_chars}; pub trait FromVecExpression { fn from_vec_expression(value: &[Expression]) -> T; } #[async_trait] pub trait Update { async fn before_update(client: &Client) -> Result<(), Box>; async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box>; async fn after_update(client: &Client) -> Result<(), Box>; } #[derive(Debug)] pub struct Author { pub id: u64, pub last_name: String, pub first_name: String, pub middle_name: String, } impl FromVecExpression for Author { fn from_vec_expression(value: &[Expression]) -> Author { Author { id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Author.id"), }, last_name: match &value[3] { sql_parse::Expression::String(v) => remove_wrong_chars(&v.value), _ => panic!("Author.last_name"), }, first_name: match &value[1] { sql_parse::Expression::String(v) => remove_wrong_chars(&v.value), _ => panic!("Author.first_name"), }, middle_name: match &value[2] { sql_parse::Expression::String(v) => remove_wrong_chars(&v.value), _ => panic!("Author.middle_name"), }, } } } #[async_trait] impl Update for Author { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_author( source_ smallint, remote_id_ int, first_name_ varchar, last_name_ varchar, middle_name_ varchar ) RETURNS void AS $$ BEGIN IF EXISTS (SELECT * FROM authors WHERE source = source_ AND remote_id = remote_id_) THEN UPDATE authors SET first_name = first_name_, last_name = last_name_, middle_name = middle_name_ WHERE source = source_ AND remote_id = remote_id_; RETURN; END IF; INSERT INTO authors (source, remote_id, first_name, last_name, middle_name) VALUES (source_, remote_id_, first_name_, last_name_, middle_name_); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client.execute( "SELECT update_author($1, $2, cast($3 as varchar), cast($4 as varchar), cast($5 as varchar));", &[&source_id, &(self.id as i32), &self.first_name, &self.last_name, &self.middle_name] ).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct Book { pub id: u64, pub title: String, pub lang: String, pub file_type: String, pub uploaded: NaiveDate, pub is_deleted: bool, pub pages: u64, pub year: u64, } impl FromVecExpression for Book { fn from_vec_expression(value: &[Expression]) -> Book { Book { id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Book.id"), }, title: match &value[3] { sql_parse::Expression::String(v) => remove_wrong_chars(&v.value), _ => panic!("Book.title"), }, lang: match &value[5] { sql_parse::Expression::String(v) => parse_lang(&v.value), _ => panic!("Book.lang"), }, file_type: match &value[8] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("Book.file_type"), }, uploaded: match &value[2] { sql_parse::Expression::String(v) => { NaiveDateTime::parse_from_str(&v.value, "%Y-%m-%d %H:%M:%S") .unwrap() .date() } _ => panic!("Book.uploaded"), }, is_deleted: match &value[11] { sql_parse::Expression::String(v) => v.value.eq("1"), _ => panic!("Book.is_deleted"), }, pages: match &value[20] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Book.id"), }, year: match &value[10] { sql_parse::Expression::Integer(v) => v.0, sql_parse::Expression::Unary { .. } => 0, _ => panic!("Book.year"), }, } } } #[async_trait] impl Update for Book { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_book( source_ smallint, remote_id_ int, title_ varchar, lang_ varchar, file_type_ varchar, uploaded_ date, is_deleted_ boolean, pages_ int, year_ smallint ) RETURNS void AS $$ BEGIN IF EXISTS (SELECT * FROM books WHERE source = source_ AND remote_id = remote_id_) THEN UPDATE books SET title = title_, lang = lang_, file_type = file_type_, uploaded = uploaded_, is_deleted = is_deleted_, pages = pages_, year = year_ WHERE source = source_ AND remote_id = remote_id_; RETURN; END IF; INSERT INTO books (source, remote_id, title, lang, file_type, uploaded, is_deleted, pages, year) VALUES (source_, remote_id_, title_, lang_, file_type_, uploaded_, is_deleted_, pages_, year_); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client.execute( "SELECT update_book($1, $2, cast($3 as varchar), cast($4 as varchar), cast($5 as varchar), $6, $7, $8, $9);", &[&source_id, &(self.id as i32), &self.title, &self.lang, &self.file_type, &self.uploaded, &self.is_deleted, &(self.pages as i32), &(self.year as i16)] ).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(client: &Client) -> Result<(), Box> { match client .execute( "UPDATE books SET is_deleted = 't' WHERE lang NOT IN ('ru', 'be', 'uk');", &[], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } } #[derive(Debug)] pub struct BookAuthor { pub book_id: u64, pub author_id: u64, // TODO: position } impl FromVecExpression for BookAuthor { fn from_vec_expression(value: &[Expression]) -> BookAuthor { BookAuthor { book_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("BookAuthor.book_id"), }, author_id: match &value[1] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("BookAuthor.author_id"), }, } } } #[async_trait] impl Update for BookAuthor { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_book_author(source_ smallint, book_ integer, author_ integer) RETURNS void AS $$ DECLARE book_id integer := -1; author_id integer := -1; BEGIN SELECT id INTO book_id FROM books WHERE source = source_ AND remote_id = book_; SELECT id INTO author_id FROM authors WHERE source = source_ AND remote_id = author_; IF book_id IS NULL OR author_id IS NULL THEN RETURN; END IF; IF EXISTS (SELECT * FROM book_authors WHERE book = book_id AND author = author_id) THEN RETURN; END IF; INSERT INTO book_authors (book, author) VALUES (book_id, author_id); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_book_author($1, $2, $3);", &[&source_id, &(self.book_id as i32), &(self.author_id as i32)], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct Translator { pub book_id: u64, pub author_id: u64, pub position: u64, } impl FromVecExpression for Translator { fn from_vec_expression(value: &[Expression]) -> Translator { Translator { book_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Translator.book_id"), }, author_id: match &value[1] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Translator.author_id"), }, position: match &value[2] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Translator.pos"), }, } } } #[async_trait] impl Update for Translator { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_translation(source_ smallint, book_ integer, author_ integer, position_ smallint) RETURNS void AS $$ DECLARE book_id integer := -1; author_id integer := -1; BEGIN SELECT id INTO book_id FROM books WHERE source = source_ AND remote_id = book_; IF book_id IS NULL OR author_id IS NULL THEN RETURN; END IF; SELECT id INTO author_id FROM authors WHERE source = source_ AND remote_id = author_; IF EXISTS (SELECT * FROM translations WHERE book = book_id AND author = author_id) THEN UPDATE translations SET position = position_ WHERE book = book_id AND author = author_id; RETURN; END IF; INSERT INTO translations (book, author, position) VALUES (book_id, author_id, position_); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_translation($1, $2, $3, $4);", &[ &source_id, &(self.book_id as i32), &(self.author_id as i32), &(self.position as i16), ], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct Sequence { pub id: u64, pub name: String, } impl FromVecExpression for Sequence { fn from_vec_expression(value: &[Expression]) -> Sequence { Sequence { id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Sequence.id"), }, name: match &value[1] { sql_parse::Expression::String(v) => remove_wrong_chars(&v.value), _ => panic!("Sequence.name"), }, } } } #[async_trait] impl Update for Sequence { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_sequences(source_ smallint, remote_id_ int, name_ varchar) RETURNS void AS $$ BEGIN IF EXISTS (SELECT * FROM sequences WHERE source = source_ AND remote_id = remote_id_) THEN UPDATE sequences SET name = name_ WHERE source = source_ AND remote_id = remote_id_; RETURN; END IF; INSERT INTO sequences (source, remote_id, name) VALUES (source_, remote_id_, name_); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_sequences($1, $2, cast($3 as varchar));", &[&source_id, &(self.id as i32), &self.name], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct SequenceInfo { pub book_id: u64, pub sequence_id: u64, pub position: u64, } impl FromVecExpression for SequenceInfo { fn from_vec_expression(value: &[Expression]) -> SequenceInfo { SequenceInfo { book_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("SequenceInfo.book_id"), }, sequence_id: match &value[1] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("SequenceInfo.sequence_id"), }, position: match &value[2] { sql_parse::Expression::Integer(v) => v.0, sql_parse::Expression::Unary { op, op_span: _, operand, } => match (op, operand.as_ref()) { (sql_parse::UnaryOperator::Minus, Expression::Integer(v)) => v.0, (_, _) => panic!("SequenceInfo.position = {:?}", &value[2]), }, _ => panic!("SequenceInfo.position = {:?}", &value[2]), }, } } } #[async_trait] impl Update for SequenceInfo { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_book_sequence(source_ smallint, book_ integer, sequence_ integer, position_ smallint) RETURNS void AS $$ DECLARE book_id integer := -1; sequence_id integer := -1; BEGIN SELECT id INTO book_id FROM books WHERE source = source_ AND remote_id = book_; IF book_id IS NULL THEN RETURN; END IF; SELECT id INTO sequence_id FROM sequences WHERE source = source_ AND remote_id = sequence_; IF sequence_id IS NULL THEN RETURN; END IF; IF EXISTS (SELECT * FROM book_sequences WHERE book = book_id AND sequence = sequence_id) THEN UPDATE book_sequences SET position = ABS(position_) WHERE book = book_id AND sequence = sequence_id; RETURN; END IF; INSERT INTO book_sequences (book, sequence, position) VALUES (book_id, sequence_id, ABS(position_)); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_book_sequence($1, $2, $3, $4);", &[ &source_id, &(self.book_id as i32), &(self.sequence_id as i32), &(self.position as i16), ], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct BookAnnotation { pub book_id: u64, pub title: String, pub body: Option, } impl FromVecExpression for BookAnnotation { fn from_vec_expression(value: &[Expression]) -> BookAnnotation { BookAnnotation { book_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("BookAnnotation.book_id"), }, title: match &value[2] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("BookAnnotation.title"), }, body: match &value[3] { sql_parse::Expression::String(v) => Some(fix_annotation_text(&v.value)), sql_parse::Expression::Null(_) => None, _ => panic!("BookAnnotation.body"), }, } } } #[async_trait] impl Update for BookAnnotation { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_book_annotation(source_ smallint, book_ integer, title_ varchar, text_ text) RETURNS void AS $$ DECLARE book_id integer := -1; BEGIN SELECT id INTO book_id FROM books WHERE source = source_ AND remote_id = book_; IF EXISTS (SELECT * FROM book_annotations WHERE book = book_id) THEN UPDATE book_annotations SET title = title_, text = text_ WHERE book = book_id; RETURN; END IF; IF book_id IS NULL THEN RETURN; END IF; INSERT INTO book_annotations (book, title, text) VALUES (book_id, title_, text_); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_book_annotation($1, $2, cast($3 as varchar), cast($4 as text));", &[&source_id, &(self.book_id as i32), &self.title, &self.body], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct BookAnnotationPic { pub book_id: u64, pub file: String, } impl FromVecExpression for BookAnnotationPic { fn from_vec_expression(value: &[Expression]) -> BookAnnotationPic { BookAnnotationPic { book_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("BookAnnotationPic.book_id"), }, file: match &value[2] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("BookAnnotationPic.file"), }, } } } #[async_trait] impl Update for BookAnnotationPic { async fn before_update(_client: &Client) -> Result<(), Box> { Ok(()) } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "\ UPDATE book_annotations \ SET file = cast($3 as varchar) \ FROM (SELECT id FROM books WHERE source = $1 AND remote_id = $2) as books \ WHERE book = books.id;\ ", &[&source_id, &(self.book_id as i32), &self.file], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct AuthorAnnotation { pub author_id: u64, pub title: String, pub body: Option, } impl FromVecExpression for AuthorAnnotation { fn from_vec_expression(value: &[Expression]) -> AuthorAnnotation { AuthorAnnotation { author_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("AuthorAnnotation.author_id"), }, title: match &value[2] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("AuthorAnnotation.title"), }, body: match &value[3] { sql_parse::Expression::String(v) => Some(fix_annotation_text(&v.value)), sql_parse::Expression::Null(_) => None, _ => panic!("AuthorAnnotation.body"), }, } } } #[async_trait] impl Update for AuthorAnnotation { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_author_annotation(source_ smallint, author_ integer, title_ varchar, text_ text) RETURNS void AS $$ DECLARE author_id integer := -1; BEGIN SELECT id INTO author_id FROM authors WHERE source = source_ AND remote_id = author_; IF EXISTS (SELECT * FROM author_annotations WHERE author = author_id) THEN UPDATE author_annotations SET title = title_, text = text_ WHERE author = author_id; RETURN; END IF; INSERT INTO author_annotations (author, title, text) VALUES (author_id, title_, text_); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_author_annotation($1, $2, cast($3 as varchar), cast($4 as text));", &[ &source_id, &(self.author_id as i32), &self.title, &self.body, ], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct AuthorAnnotationPic { pub author_id: u64, pub file: String, } impl FromVecExpression for AuthorAnnotationPic { fn from_vec_expression(value: &[Expression]) -> AuthorAnnotationPic { AuthorAnnotationPic { author_id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("AuthorAnnotationPic.book_id"), }, file: match &value[2] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("AuthorAnnotationPic.file"), }, } } } #[async_trait] impl Update for AuthorAnnotationPic { async fn before_update(_client: &Client) -> Result<(), Box> { Ok(()) } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "\ UPDATE author_annotations \ SET file = cast($3 as varchar) \ FROM (SELECT id FROM authors WHERE source = $1 AND remote_id = $2) as authors \ WHERE author = authors.id;", &[&source_id, &(self.author_id as i32), &self.file], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct Genre { pub id: u64, pub code: String, pub description: String, pub meta: String, } impl FromVecExpression for Genre { fn from_vec_expression(value: &[Expression]) -> Genre { Genre { id: match &value[0] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("Genre.id"), }, code: match &value[1] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("Genre.code = {:?}", &value[1]), }, description: match &value[2] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("Genre.description = {:?}", &value[2]), }, meta: match &value[3] { sql_parse::Expression::String(v) => v.value.to_string(), _ => panic!("Genre.meta"), }, } } } #[async_trait] impl Update for Genre { async fn before_update(client: &Client) -> Result<(), Box> { match client.execute( " CREATE OR REPLACE FUNCTION update_book_sequence(source_ smallint, book_ integer, genre_ integer) RETURNS void AS $$ DECLARE book_id integer := -1; genre_id integer := -1; BEGIN SELECT id INTO book_id FROM books WHERE source = source_ AND remote_id = book_; IF book_id IS NULL THEN RETURN; END IF; SELECT id INTO genre_id FROM genres WHERE source = source_ AND remote_id = genre_; IF EXISTS (SELECT * FROM book_genres WHERE book = book_id AND genre = genre_id) THEN RETURN; END IF; INSERT INTO book_genres (book, genre) VALUES (book_id, genre_id); END; $$ LANGUAGE plpgsql; " , &[]).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_genre($1, $2, cast($3 as varchar), cast($4 as varchar), cast($5 as varchar));", &[&source_id, &(self.id as i32), &self.code, &self.description, &self.meta] ).await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } } #[derive(Debug)] pub struct BookGenre { pub book_id: u64, pub genre_id: u64, } impl FromVecExpression for BookGenre { fn from_vec_expression(value: &[Expression]) -> BookGenre { BookGenre { book_id: match &value[1] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("BookGenre.book_id"), }, genre_id: match &value[2] { sql_parse::Expression::Integer(v) => v.0, _ => panic!("BookGenre.genre_id"), }, } } } #[async_trait] impl Update for BookGenre { async fn before_update(_client: &Client) -> Result<(), Box> { Ok(()) } async fn update( &self, client: &Client, source_id: i16, ) -> Result<(), Box> { match client .execute( "SELECT update_book_sequence($1, $2, $3);", &[&source_id, &(self.book_id as i32), &(self.genre_id as i32)], ) .await { Ok(_) => Ok(()), Err(err) => Err(Box::new(err)), } } async fn after_update(_client: &Client) -> Result<(), Box> { Ok(()) } }