Implement filter expressions supporting AND and OR

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent 699698a251
commit 3fb98188dc

@ -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",

@ -65,6 +65,9 @@ pub async fn get_hashes_with_tag_count(
db: &DatabaseConnection,
hash_ids: Vec<i64>,
) -> RepoResult<HashMap<i64, u32>> {
if hash_ids.is_empty() {
return Ok(HashMap::new());
}
let hash_tag_counts: Vec<HashTagCount> =
HashTagCount::find_by_statement(Statement::from_sql_and_values(
DbBackend::Sqlite,
@ -94,7 +97,9 @@ fn vec_to_query_list<D: Display>(input: Vec<D>) -> 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
}

@ -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<Vec<(i64, bool)>>,
) -> RepoResult<Vec<Self>> {
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<file::Model>)> = 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<Vec<(i64, bool)>>) -> 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(),
),
)
}
}

@ -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<Vec<File>> {
pub async fn find_files_by_tags(
&self,
tags: Vec<Vec<(String, bool)>>,
) -> RepoResult<Vec<File>> {
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<String, bool> = HashMap::from_iter(tags.into_iter());
let tag_map: HashMap<String, i64> =
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<Vec<(i64, bool)>> = 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();

@ -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",

@ -34,4 +34,4 @@ features = ["tokio-executor"]
[dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git"
rev = "3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85"
rev = "6fbc72699a565b1ca3f76f58c3dafbe9790c758e"

@ -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<S: AsyncProtocolStream>(ctx: &Context<S>, event: Event) -> IPCResult<()> {
let req = event.data::<FindFilesByTagsRequest>()?;
let req = event.data::<FindFilesRequest>()?;
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<i64> = files.iter().map(|f| f.hash_id()).collect();

Loading…
Cancel
Save