From 72a887b33a820c27f2593e3bf921d7738de36fd0 Mon Sep 17 00:00:00 2001 From: Fleeym <61891787+Fleeym@users.noreply.github.com> Date: Sat, 17 Feb 2024 02:21:38 +0200 Subject: [PATCH] dependency rework --- .../20240102213218_first_migration.up.sql | 10 +- src/types/mod_json.rs | 105 ++-------- src/types/models/dependency.rs | 185 +++++++++++++----- src/types/models/incompatibility.rs | 34 ++-- src/types/models/mod_entity.rs | 53 ++++- src/types/models/mod_version.rs | 10 +- 6 files changed, 232 insertions(+), 165 deletions(-) diff --git a/migrations/20240102213218_first_migration.up.sql b/migrations/20240102213218_first_migration.up.sql index d4f2823..224c4c9 100644 --- a/migrations/20240102213218_first_migration.up.sql +++ b/migrations/20240102213218_first_migration.up.sql @@ -60,22 +60,24 @@ CREATE TABLE mod_gd_versions ( CREATE TABLE dependencies ( dependent_id INTEGER NOT NULL, - dependency_id INTEGER NOT NULL, + dependency_id TEXT NOT NULL, + version TEXT NOT NULL, compare version_compare NOT NULL, importance dependency_importance NOT NULL, PRIMARY KEY (dependent_id, dependency_id), FOREIGN KEY (dependent_id) REFERENCES mod_versions(id), - FOREIGN KEY (dependency_id) REFERENCES mod_versions(id) + FOREIGN KEY (dependency_id) REFERENCES mods(id) ); CREATE TABLE incompatibilities ( mod_id INTEGER NOT NULL, - incompatibility_id INTEGER NOT NULL, + incompatibility_id TEXT NOT NULL, + version TEXT NOT NULL, compare version_compare NOT NULL, importance incompatibility_importance NOT NULL, PRIMARY KEY (mod_id, incompatibility_id), FOREIGN KEY (mod_id) REFERENCES mod_versions(id), - FOREIGN KEY (incompatibility_id) REFERENCES mod_versions(id) + FOREIGN KEY (incompatibility_id) REFERENCES mods(id) ); CREATE TABLE developers ( diff --git a/src/types/mod_json.rs b/src/types/mod_json.rs index 7dcef9b..5bd320f 100644 --- a/src/types/mod_json.rs +++ b/src/types/mod_json.rs @@ -187,22 +187,18 @@ impl ModJson { Ok(json) } - pub async fn query_dependencies( - &self, - pool: &mut PgConnection, - ) -> Result, ApiError> { + pub fn prepare_dependencies_for_create(&self) -> Result, ApiError> { let deps = match self.dependencies.as_ref() { - None => return Err(ApiError::InternalError), + None => return Ok(vec![]), Some(d) => d, }; if deps.is_empty() { - return Err(ApiError::InternalError); + return Ok(vec![]); } let mut ret: Vec = vec![]; - // I am going to n+1 this, I am sorry, will optimize later for i in deps { let (dependency_ver, compare) = match split_version_and_compare(i.version.as_str()) { Err(_) => { @@ -213,61 +209,29 @@ impl ModJson { } Ok((ver, compare)) => (ver, compare), }; - - let versions = sqlx::query!( - "SELECT id, version FROM mod_versions WHERE mod_id = $1 and validated = true", - i.id - ) - .fetch_all(&mut *pool) - .await; - let versions = match versions { - Err(_) => return Err(ApiError::DbError), - Ok(v) => v, - }; - if versions.is_empty() { - return Err(ApiError::BadRequest(format!( - "Couldn't find dependency {} on the index", - i.id - ))); - } - let mut found = false; - for j in versions { - // This should never fail (I hope) - let parsed = semver::Version::parse(&j.version).unwrap(); - if compare_versions(&parsed, &dependency_ver, &compare) { - ret.push(DependencyCreate { - dependency_id: j.id, - compare, - importance: i.importance, - }); - found = true; - break; - } - } - if !found { - return Err(ApiError::BadRequest(format!( - "Couldn't find dependency version that satisfies semver compare {}", - i.version - ))); - } + ret.push(DependencyCreate { + dependency_id: i.id.clone(), + version: dependency_ver.to_string(), + compare, + importance: i.importance, + }); } Ok(ret) } - pub async fn query_incompatibilities( + pub fn prepare_incompatibilities_for_create( &self, - pool: &mut PgConnection, ) -> Result, ApiError> { let incompat = match self.incompatibilities.as_ref() { - None => return Err(ApiError::InternalError), + None => return Ok(vec![]), Some(d) => d, }; if incompat.is_empty() { - return Err(ApiError::InternalError); + return Ok(vec![]); } - let mut ret: Vec<_> = vec![]; + let mut ret: Vec = vec![]; for i in incompat { let (ver, compare) = match split_version_and_compare(i.version.as_str()) { @@ -279,43 +243,12 @@ impl ModJson { } Ok((ver, compare)) => (ver, compare), }; - - let versions = sqlx::query!( - "SELECT id, version FROM mod_versions WHERE mod_id = $1", - i.id - ) - .fetch_all(&mut *pool) - .await; - let versions = match versions { - Err(_) => return Err(ApiError::DbError), - Ok(v) => v, - }; - if versions.is_empty() { - return Err(ApiError::BadRequest(format!( - "Couldn't find incompatibility {} on the index", - i.id - ))); - } - let mut found = false; - for j in versions { - // This should never fail (I hope) - let parsed = semver::Version::parse(&j.version).unwrap(); - if compare_versions(&parsed, &ver, &compare) { - ret.push(IncompatibilityCreate { - incompatibility_id: j.id, - compare, - importance: i.importance, - }); - found = true; - break; - } - } - if !found { - return Err(ApiError::BadRequest(format!( - "Couldn't find incompatibility version that satisfies semver compare {}", - i.version - ))); - } + ret.push(IncompatibilityCreate { + incompatibility_id: i.id.clone(), + version: ver.to_string(), + compare, + importance: i.importance, + }); } Ok(ret) diff --git a/src/types/models/dependency.rs b/src/types/models/dependency.rs index 5b461aa..5a34efc 100644 --- a/src/types/models/dependency.rs +++ b/src/types/models/dependency.rs @@ -5,16 +5,19 @@ use sqlx::{PgConnection, Postgres, QueryBuilder}; use crate::types::api::ApiError; -#[derive(sqlx::FromRow, Clone, Copy)] +use super::mod_version::ModVersion; + +#[derive(sqlx::FromRow, Clone)] pub struct Dependency { pub dependent_id: i32, - pub dependency_id: i32, + pub dependency_id: String, pub compare: ModVersionCompare, pub importance: DependencyImportance, } pub struct DependencyCreate { - pub dependency_id: i32, + pub dependency_id: String, + pub version: String, pub compare: ModVersionCompare, pub importance: DependencyImportance, } @@ -28,9 +31,9 @@ pub struct ResponseDependency { #[derive(sqlx::FromRow, Clone, Debug)] pub struct FetchedDependency { - pub mod_id: String, + pub mod_version_id: i32, pub version: String, - pub dependency_id: i32, + pub dependency_id: String, pub compare: ModVersionCompare, pub importance: DependencyImportance, } @@ -83,13 +86,14 @@ impl Dependency { pool: &mut PgConnection, ) -> Result<(), ApiError> { let mut builder: QueryBuilder = QueryBuilder::new( - "INSERT INTO dependencies (dependent_id, dependency_id, compare, importance) VALUES ", + "INSERT INTO dependencies (dependent_id, dependency_id, version, compare, importance) VALUES ", ); for (index, i) in deps.iter().enumerate() { let mut separated = builder.separated(", "); separated.push_unseparated("("); separated.push_bind(id); - separated.push_bind(i.dependency_id); + separated.push_bind(&i.dependency_id); + separated.push_bind(&i.version); separated.push_bind(i.compare); separated.push_bind(i.importance); separated.push_unseparated(")"); @@ -108,56 +112,131 @@ impl Dependency { } pub async fn get_for_mod_version( - id: i32, + ver: &ModVersion, pool: &mut PgConnection, ) -> Result, ApiError> { - let mut ret: Vec = vec![]; - let mut modifiable_ids = vec![id]; - loop { - if modifiable_ids.is_empty() { - break; - } - let mut builder: QueryBuilder = QueryBuilder::new("SELECT dp.dependency_id, dp.compare, dp.importance, mv.version, mv.mod_id FROM dependencies dp - INNER JOIN mod_versions mv ON dp.dependency_id = mv.id - WHERE mv.validated = true AND dp.dependent_id IN ("); - let mut separated = builder.separated(","); - let copy = ret.clone(); - for i in &modifiable_ids { - separated.push_bind(i); - } - separated.push_unseparated(")"); - let result = builder - .build_query_as::() - .fetch_all(&mut *pool) - .await; - if result.is_err() { - log::error!("{}", result.err().unwrap()); - return Err(ApiError::DbError); - } - let result = result.unwrap(); - if result.is_empty() { - break; - } - modifiable_ids.clear(); - for i in result { - let doubled = copy.iter().find(|x| x.dependency_id == i.dependency_id); - if doubled.is_none() { - modifiable_ids.push(i.dependency_id); - ret.push(i); - continue; - } - // this is a sketchy bit - let doubled = doubled.unwrap(); - if should_add(doubled, &i) { - modifiable_ids.push(i.dependency_id); - ret.push(i); - } + match sqlx::query_as!(FetchedDependency, + r#"SELECT dp.dependent_id as mod_version_id, dp.dependency_id, dp.version, dp.compare AS "compare: _", dp.importance AS "importance: _" FROM dependencies dp + WHERE dp.dependent_id = $1"#, + ver.id + ) + .fetch_all(&mut *pool) + .await + { + Ok(d) => Ok(d), + Err(e) => { + log::error!("{}", e); + Err(ApiError::DbError) } } - Ok(ret) } } -fn should_add(old: &FetchedDependency, new: &FetchedDependency) -> bool { - old.compare != new.compare || old.version != new.version -} +// This is a graveyard of my hopes and dreams wasted on trying to resolve a dependency graph serverside + +// async fn merge_version_cache( +// existing: &mut HashMap>, +// mod_ids_to_fetch: Vec, +// pool: &mut PgConnection, +// ) -> Result<(), ApiError> { +// struct Fetched { +// mod_version_id: i32, +// version: String, +// } +// let versions = match sqlx::query_as!(Fetched, +// "SELECT id as mod_version_id, version FROM mod_versions WHERE mod_id = ANY($1) AND validated = true", +// &mod_ids_to_fetch +// ).fetch_all(&mut *pool).await { +// Ok(d) => d, +// Err(e) => { +// log::error!("{}", e); +// return Err(ApiError::DbError); +// } +// }; + +// let versions = versions +// .iter() +// .map(|x| FetchedModVersionDep { +// mod_version_id: x.mod_version_id, +// version: semver::Version::parse(&x.version).unwrap(), +// }) +// .collect::>(); + +// let mut fetched: HashMap> = HashMap::new(); +// for i in versions { +// if !fetched.contains_key(&i.mod_version_id.to_string()) { +// fetched.insert(i.mod_version_id.to_string(), vec![]); +// } +// fetched +// .get_mut(&i.mod_version_id.to_string()) +// .unwrap() +// .push(i); +// } + +// for (id, versions) in fetched { +// if existing.contains_key(&id) { +// continue; +// } +// existing.insert(id, versions); +// } +// Ok(()) +// } + +// fn get_matches_for_dependencies( +// deps: Vec, +// cached: &HashMap>, +// ) -> Vec { +// let mut ret: Vec = vec![]; +// for i in deps { +// let versions = match cached.get(&i.dependency_id) { +// Some(v) => v, +// None => continue, +// }; +// let dependency_parsed = semver::Version::parse(&i.version).unwrap(); +// let mut max: Option; +// let mut best: Option; + +// let valids: Vec = match i.compare { +// ModVersionCompare::Exact => versions +// .iter() +// .filter(|x| x.version == dependency_parsed) +// .map(|x| x.clone()) +// .collect(), +// ModVersionCompare::More => versions +// .iter() +// .filter(|x| x.version > dependency_parsed) +// .map(|x| x.clone()) +// .collect(), +// ModVersionCompare::MoreEq => versions +// .iter() +// .filter(|x| x.version >= dependency_parsed) +// .map(|x| x.clone()) +// .collect(), +// ModVersionCompare::Less => versions +// .iter() +// .filter(|x| x.version < dependency_parsed) +// .map(|x| x.clone()) +// .collect(), +// ModVersionCompare::LessEq => versions +// .iter() +// .filter(|x| x.version <= dependency_parsed) +// .map(|x| x.clone()) +// .collect(), +// }; +// for i in valids { +// if let Some(m) = max { +// if i.version > m { +// max = Some(i.version); +// best = Some(i.clone()); +// } +// } else { +// max = Some(i.version); +// best = Some(i.clone()); +// } +// } +// if let Some(b) = best { +// ret.push(b); +// } +// } +// ret +// } diff --git a/src/types/models/incompatibility.rs b/src/types/models/incompatibility.rs index 59d2a75..195083a 100644 --- a/src/types/models/incompatibility.rs +++ b/src/types/models/incompatibility.rs @@ -5,15 +5,16 @@ use sqlx::{PgConnection, Postgres, QueryBuilder}; #[derive(sqlx::FromRow, Clone, Debug)] pub struct FetchedIncompatibility { - pub mod_id: String, + pub mod_id: i32, pub version: String, - pub incompatibility_id: i32, + pub incompatibility_id: String, pub compare: ModVersionCompare, pub importance: IncompatibilityImportance, } pub struct IncompatibilityCreate { - pub incompatibility_id: i32, + pub incompatibility_id: String, + pub version: String, pub compare: ModVersionCompare, pub importance: IncompatibilityImportance, } @@ -21,7 +22,7 @@ pub struct IncompatibilityCreate { #[derive(sqlx::FromRow)] pub struct Incompatibility { pub mod_id: i32, - pub incompatibility_id: i32, + pub incompatibility_id: String, pub compare: ModVersionCompare, pub importance: IncompatibilityImportance, } @@ -54,10 +55,9 @@ impl Incompatibility { let mut separated = builder.separated(", "); separated.push_unseparated("("); separated.push_bind(id); - separated.push_bind(i.incompatibility_id); + separated.push_bind(&i.incompatibility_id); separated.push_bind(i.compare); separated.push_bind(i.importance); - log::info!("{}", index); separated.push_unseparated(")"); if index != incompats.len() - 1 { separated.push_unseparated(", "); @@ -77,21 +77,23 @@ impl Incompatibility { id: i32, pool: &mut PgConnection, ) -> Result, ApiError> { - let result = sqlx::query_as!( + match sqlx::query_as!( FetchedIncompatibility, - r#"SELECT icp.compare as "compare: ModVersionCompare", - icp.importance as "importance: IncompatibilityImportance", - icp.incompatibility_id, mv.mod_id, mv.version FROM incompatibilities icp - INNER JOIN mod_versions mv ON icp.mod_id = mv.id + r#"SELECT icp.compare as "compare: _", + icp.importance as "importance: _", + icp.incompatibility_id, icp.mod_id, icp.version FROM incompatibilities icp + INNER JOIN mod_versions mv ON mv.id = icp.mod_id WHERE mv.id = $1 AND mv.validated = true"#, id ) .fetch_all(&mut *pool) - .await; - if result.is_err() { - log::info!("{}", result.err().unwrap()); - return Err(ApiError::DbError); + .await + { + Ok(d) => Ok(d), + Err(e) => { + log::error!("{}", e); + Err(ApiError::DbError) + } } - Ok(result.unwrap()) } } diff --git a/src/types/models/mod_entity.rs b/src/types/models/mod_entity.rs index 6da3726..8efcf71 100644 --- a/src/types/models/mod_entity.rs +++ b/src/types/models/mod_entity.rs @@ -9,7 +9,7 @@ use crate::{ use actix_web::web::Bytes; use serde::Serialize; use sqlx::{PgConnection, Postgres, QueryBuilder}; -use std::{io::Cursor, str::FromStr}; +use std::{collections::HashMap, io::Cursor, str::FromStr}; use super::{ developer::{Developer, FetchedDeveloper, ModDeveloper}, @@ -292,6 +292,57 @@ impl Mod { Ok(()) } + pub async fn get_all_version_strings_for_mod( + id: &str, + pool: &mut PgConnection, + ) -> Result, ApiError> { + let result = match sqlx::query!( + "SELECT version FROM mod_versions WHERE mod_id = $1 AND validated = true", + id + ) + .fetch_all(&mut *pool) + .await + { + Ok(r) => r, + Err(e) => { + log::error!("{}", e); + return Err(ApiError::DbError); + } + }; + Ok(result.iter().map(|x| x.version.clone()).collect()) + } + + pub async fn get_all_version_strings_for_mods( + ids: Vec, + pool: &mut PgConnection, + ) -> Result>, ApiError> { + let mut ret: HashMap> = HashMap::new(); + if ids.is_empty() { + return Ok(ret); + } + let result = match sqlx::query!( + "SELECT version, mod_id FROM mod_versions WHERE mod_id = ANY($1) AND validated = true", + &ids + ) + .fetch_all(&mut *pool) + .await + { + Ok(r) => r, + Err(e) => { + log::error!("{}", e); + return Err(ApiError::DbError); + } + }; + for i in result { + if ret.contains_key(&i.mod_id) { + ret.get_mut(&i.mod_id).unwrap().push(i.version); + } else { + ret.insert(i.mod_id.clone(), vec![i.version]); + } + } + Ok(ret) + } + pub async fn new_version( json: &ModJson, developer: FetchedDeveloper, diff --git a/src/types/models/mod_version.rs b/src/types/models/mod_version.rs index f0951a0..311441c 100644 --- a/src/types/models/mod_version.rs +++ b/src/types/models/mod_version.rs @@ -217,7 +217,7 @@ impl ModVersion { } } if json.dependencies.as_ref().is_some_and(|x| !x.is_empty()) { - let dependencies = json.query_dependencies(pool).await?; + let dependencies = json.prepare_dependencies_for_create()?; if !dependencies.is_empty() { Dependency::create_for_mod_version(id, dependencies, pool).await?; } @@ -227,7 +227,7 @@ impl ModVersion { .as_ref() .is_some_and(|x| !x.is_empty()) { - let incompat = json.query_incompatibilities(pool).await?; + let incompat = json.prepare_incompatibilities_for_create()?; if !incompat.is_empty() { Incompatibility::create_for_mod_version(id, incompat, pool).await?; } @@ -266,11 +266,11 @@ impl ModVersion { let mut version = result.unwrap().into_mod_version(); version.gd = ModGDVersion::get_for_mod_version(version.id, pool).await?; - let deps = Dependency::get_for_mod_version(version.id, pool).await?; + let deps = Dependency::get_for_mod_version(&version, pool).await?; version.dependencies = Some( deps.into_iter() .map(|x| ResponseDependency { - mod_id: x.mod_id.clone(), + mod_id: x.dependency_id.clone(), version: format!("{}{}", x.compare, x.version.trim_start_matches('v')), importance: x.importance, }) @@ -281,7 +281,7 @@ impl ModVersion { incompat .into_iter() .map(|x| ResponseIncompatibility { - mod_id: x.mod_id.clone(), + mod_id: x.incompatibility_id.clone(), version: format!("{}{}", x.compare, x.version.trim_start_matches('v')), importance: x.importance, })