From 3fb98188dc2ecaebba8b608634f0a24b37338b6f Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 18 Nov 2021 20:59:43 +0100 Subject: [PATCH] Implement filter expressions supporting AND and OR Signed-off-by: trivernis --- mediarepo-daemon/Cargo.lock | 4 +- .../mediarepo-database/src/queries/tags.rs | 7 +- mediarepo-daemon/mediarepo-model/src/file.rs | 74 ++++++++++++------- mediarepo-daemon/mediarepo-model/src/repo.rs | 25 ++++--- mediarepo-daemon/mediarepo-socket/Cargo.lock | 4 +- mediarepo-daemon/mediarepo-socket/Cargo.toml | 2 +- .../mediarepo-socket/src/namespaces/files.rs | 19 ++++- 7 files changed, 87 insertions(+), 48 deletions(-) diff --git a/mediarepo-daemon/Cargo.lock b/mediarepo-daemon/Cargo.lock index 2a5650e..2c22cda 100644 --- a/mediarepo-daemon/Cargo.lock +++ b/mediarepo-daemon/Cargo.lock @@ -855,8 +855,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo-api" -version = "0.10.0" -source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85#3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85" +version = "0.11.0" +source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=6fbc72699a565b1ca3f76f58c3dafbe9790c758e#6fbc72699a565b1ca3f76f58c3dafbe9790c758e" dependencies = [ "chrono", "serde", diff --git a/mediarepo-daemon/mediarepo-database/src/queries/tags.rs b/mediarepo-daemon/mediarepo-database/src/queries/tags.rs index 5d4dc99..431e34d 100644 --- a/mediarepo-daemon/mediarepo-database/src/queries/tags.rs +++ b/mediarepo-daemon/mediarepo-database/src/queries/tags.rs @@ -65,6 +65,9 @@ pub async fn get_hashes_with_tag_count( db: &DatabaseConnection, hash_ids: Vec, ) -> RepoResult> { + if hash_ids.is_empty() { + return Ok(HashMap::new()); + } let hash_tag_counts: Vec = HashTagCount::find_by_statement(Statement::from_sql_and_values( DbBackend::Sqlite, @@ -94,7 +97,9 @@ fn vec_to_query_list(input: Vec) -> String { let mut entries = input .into_iter() .fold(String::new(), |acc, val| format!("{}{},", acc, val)); - entries.remove(entries.len() - 1); + if entries.len() > 0 { + entries.remove(entries.len() - 1); + } entries } diff --git a/mediarepo-daemon/mediarepo-model/src/file.rs b/mediarepo-daemon/mediarepo-model/src/file.rs index 71a0528..7f2485b 100644 --- a/mediarepo-daemon/mediarepo-model/src/file.rs +++ b/mediarepo-daemon/mediarepo-model/src/file.rs @@ -90,36 +90,13 @@ impl File { #[tracing::instrument(level = "debug", skip(db))] pub(crate) async fn find_by_tags( db: DatabaseConnection, - tag_ids: Vec<(i64, bool)>, + tag_ids: Vec>, ) -> RepoResult> { - let mut condition = Condition::all(); - - for (tag, negated) in tag_ids { - condition = if negated { - condition.add( - hash::Column::Id.not_in_subquery( - Query::select() - .expr(Expr::col(hash_tag::Column::HashId)) - .from(hash_tag::Entity) - .cond_where(hash_tag::Column::TagId.eq(tag)) - .to_owned(), - ), - ) - } else { - condition.add( - hash::Column::Id.in_subquery( - Query::select() - .expr(Expr::col(hash_tag::Column::HashId)) - .from(hash_tag::Entity) - .cond_where(hash_tag::Column::TagId.eq(tag)) - .to_owned(), - ), - ) - } - } + let main_condition = build_find_filter_conditions(tag_ids); + let results: Vec<(hash::Model, Option)> = hash::Entity::find() .find_also_related(file::Entity) - .filter(condition) + .filter(main_condition) .group_by(file::Column::Id) .all(&db) .await?; @@ -373,3 +350,46 @@ impl File { } } } + +fn build_find_filter_conditions(tag_ids: Vec>) -> Condition { + let mut main_condition = Condition::all(); + + for mut expression in tag_ids { + if expression.len() == 1 { + let (tag_id, negated) = expression.pop().unwrap(); + main_condition = add_single_filter_expression(main_condition, tag_id, negated) + } else if !expression.is_empty() { + let mut sub_condition = Condition::any(); + + for (tag, negated) in expression { + sub_condition = add_single_filter_expression(sub_condition, tag, negated); + } + main_condition = main_condition.add(sub_condition); + } + } + main_condition +} + +fn add_single_filter_expression(condition: Condition, tag_id: i64, negated: bool) -> Condition { + if negated { + condition.add( + hash::Column::Id.not_in_subquery( + Query::select() + .expr(Expr::col(hash_tag::Column::HashId)) + .from(hash_tag::Entity) + .cond_where(hash_tag::Column::TagId.eq(tag_id)) + .to_owned(), + ), + ) + } else { + condition.add( + hash::Column::Id.in_subquery( + Query::select() + .expr(Expr::col(hash_tag::Column::HashId)) + .from(hash_tag::Entity) + .cond_where(hash_tag::Column::TagId.eq(tag_id)) + .to_owned(), + ), + ) + } +} diff --git a/mediarepo-daemon/mediarepo-model/src/repo.rs b/mediarepo-daemon/mediarepo-model/src/repo.rs index b90f6e2..918164d 100644 --- a/mediarepo-daemon/mediarepo-model/src/repo.rs +++ b/mediarepo-daemon/mediarepo-model/src/repo.rs @@ -108,25 +108,26 @@ impl Repo { /// Finds all files by a list of tags #[tracing::instrument(level = "debug", skip(self))] - pub async fn find_files_by_tags(&self, tags: Vec<(String, bool)>) -> RepoResult> { + pub async fn find_files_by_tags( + &self, + tags: Vec>, + ) -> RepoResult> { let parsed_tags = tags .iter() - .map(|t| parse_namespace_and_tag(t.0.clone())) + .flat_map(|e| e.into_iter().map(|t| parse_namespace_and_tag(t.0.clone()))) + .unique() .collect(); let db_tags = self.tags_by_names(parsed_tags).await?; - let tag_map: HashMap = HashMap::from_iter(tags.into_iter()); + let tag_map: HashMap = + HashMap::from_iter(db_tags.into_iter().map(|t| (t.normalized_name(), t.id()))); - let tag_ids: Vec<(i64, bool)> = db_tags + let tag_ids: Vec> = tags .into_iter() - .map(|tag| { - ( - tag.id(), - tag_map - .get(&tag.normalized_name()) - .cloned() - .unwrap_or(false), - ) + .map(|expr| { + expr.into_iter() + .filter_map(|(tag, negated)| Some((*tag_map.get(&tag)?, negated))) + .collect_vec() }) .collect(); diff --git a/mediarepo-daemon/mediarepo-socket/Cargo.lock b/mediarepo-daemon/mediarepo-socket/Cargo.lock index 64ec03c..88789e0 100644 --- a/mediarepo-daemon/mediarepo-socket/Cargo.lock +++ b/mediarepo-daemon/mediarepo-socket/Cargo.lock @@ -802,8 +802,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo-api" -version = "0.10.0" -source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85#3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85" +version = "0.11.0" +source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=6fbc72699a565b1ca3f76f58c3dafbe9790c758e#6fbc72699a565b1ca3f76f58c3dafbe9790c758e" dependencies = [ "chrono", "serde", diff --git a/mediarepo-daemon/mediarepo-socket/Cargo.toml b/mediarepo-daemon/mediarepo-socket/Cargo.toml index a8b0d51..8dac018 100644 --- a/mediarepo-daemon/mediarepo-socket/Cargo.toml +++ b/mediarepo-daemon/mediarepo-socket/Cargo.toml @@ -34,4 +34,4 @@ features = ["tokio-executor"] [dependencies.mediarepo-api] git = "https://github.com/Trivernis/mediarepo-api.git" -rev = "3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85" \ No newline at end of file +rev = "6fbc72699a565b1ca3f76f58c3dafbe9790c758e" \ No newline at end of file diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/files.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/files.rs index e2e48f5..d6a47cd 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/files.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/files.rs @@ -3,7 +3,7 @@ use crate::utils::{file_by_identifier, get_repo_from_context, hash_by_identifier use chrono::NaiveDateTime; use compare::Compare; use mediarepo_api::types::files::{ - AddFileRequestHeader, FileMetadataResponse, FindFilesByTagsRequest, + AddFileRequestHeader, FileMetadataResponse, FilterExpression, FindFilesRequest, GetFileThumbnailOfSizeRequest, GetFileThumbnailsRequest, ReadFileRequest, SortDirection, SortKey, ThumbnailMetadataResponse, UpdateFileNameRequest, }; @@ -87,9 +87,22 @@ impl FilesNamespace { /// Searches for files by tags #[tracing::instrument(skip_all)] async fn find_files(ctx: &Context, event: Event) -> IPCResult<()> { - let req = event.data::()?; + let req = event.data::()?; let repo = get_repo_from_context(ctx).await; - let tags = req.tags.into_iter().map(|t| (t.name, t.negate)).collect(); + + let tags = req + .filters + .into_iter() + .map(|e| match e { + FilterExpression::OrExpression(tags) => { + tags.into_iter().map(|t| (t.tag, t.negate)).collect_vec() + } + FilterExpression::Query(tag) => { + vec![(tag.tag, tag.negate)] + } + }) + .collect(); + let mut files = repo.find_files_by_tags(tags).await?; let hash_ids: Vec = files.iter().map(|f| f.hash_id()).collect();