use std::rc::Rc;
use rusqlite::{params, types::Value, Connection, OptionalExtension, Result};
use crate::tag::{PartialTag, Tag, TagSuggestion};
#[cfg_attr(feature = "napi", napi(object))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, Clone)]
pub struct PlatformAppPath {
pub app_path: String,
pub count: i64,
}
pub fn count(conn: &Connection) -> Result<i64> {
conn.query_row("SELECT COUNT(*) FROM platform", (), |row| {
row.get::<_, i64>(0)
})
}
pub fn find(conn: &Connection) -> Result<Vec<Tag>> {
let mut stmt = conn.prepare(
"SELECT p.id, pa.name, p.description, p.dateModified FROM platform_alias pa
INNER JOIN platform p ON p.id = pa.platformId
WHERE pa.id == p.primaryAliasId")?;
let platform_iter = stmt.query_map((), |row| {
Ok(Tag {
id: row.get(0)?,
name: row.get(1)?,
description: row.get(2)?,
date_modified: row.get(3)?,
aliases: vec![],
category: None,
})
})?;
let mut platforms = vec![];
for platform in platform_iter {
let mut platform = platform?;
let mut platform_alias_stmt = conn.prepare(
"SELECT ta.name FROM platform_alias ta WHERE ta.platformId = ?")?;
let platform_alias_iter = platform_alias_stmt.query_map(params![&platform.id], |row| row.get(0))?;
for alias in platform_alias_iter {
platform.aliases.push(alias.unwrap());
}
platforms.push(platform);
}
Ok(platforms)
}
pub fn create(conn: &Connection, name: &str, id: Option<i64>) -> Result<Tag> {
let mut stmt = "INSERT INTO platform_alias (name, platformId) VALUES(?, ?) RETURNING id";
let alias_id: i64 = conn.query_row(stmt, params![name, -1], |row| row.get(0))?;
match id {
Some(id) => {
stmt = "INSERT INTO platform (id, primaryAliasId, description) VALUES (?, ?, ?)";
conn.execute(stmt, params![id, alias_id, ""])?;
stmt = "UPDATE platform_alias SET platformId = ? WHERE id = ?";
conn.execute(stmt, params![id, alias_id])?;
}
None => {
stmt = "INSERT INTO platform (primaryAliasId, description) VALUES (?, ?) RETURNING id";
let tag_id: i64 = conn.query_row(stmt, params![alias_id, ""], |row| row.get(0))?;
stmt = "UPDATE platform_alias SET platformId = ? WHERE id = ?";
conn.execute(stmt, params![tag_id, alias_id])?;
}
}
let new_tag_result = find_by_name(conn, name)?;
if let Some(tag) = new_tag_result {
Ok(tag)
} else {
Err(rusqlite::Error::QueryReturnedNoRows)
}
}
pub fn find_or_create(conn: &Connection, name: &str, id: Option<i64>) -> Result<Tag> {
let platform_result = find_by_name(conn, name)?;
if let Some(platform) = platform_result {
Ok(platform)
} else {
conn.execute("DELETE FROM platform_alias WHERE name = ?", params![name])?;
create(conn, name, id)
}
}
pub fn find_by_name(conn: &Connection, name: &str) -> Result<Option<Tag>> {
let mut stmt = conn.prepare(
"SELECT p.id, pa.name, p.description, p.dateModified FROM platform_alias pa
INNER JOIN platform p ON p.id = pa.platformId
WHERE pa.name = ? AND p.primaryAliasId == pa.id")?;
let platform_result = stmt.query_row(params![name], |row| {
Ok(Tag {
id: row.get(0)?,
name: row.get(1)?,
description: row.get(2)?,
date_modified: row.get(3)?,
category: None,
aliases: vec![],
})
});
match platform_result {
Ok(mut platform) => {
let mut platform_alias_stmt = conn.prepare(
"SELECT pa.name FROM platform_alias pa WHERE pa.platformId = ?")?;
let platform_alias_iter = platform_alias_stmt.query_map(params![&platform.id], |row| row.get(0))?;
for alias in platform_alias_iter {
platform.aliases.push(alias.unwrap());
}
Ok(Some(platform))
},
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e),
}
}
pub fn find_by_id(conn: &Connection, id: i64) -> Result<Option<Tag>> {
let mut stmt = conn.prepare(
"SELECT p.id, pa.name, p.description, p.dateModified FROM platform_alias pa
INNER JOIN platform p ON p.id = pa.platformId
WHERE p.id = ? AND p.primaryAliasId == pa.id")?;
let platform_result = stmt.query_row(params![id], |row| {
Ok(Tag {
id: row.get(0)?,
name: row.get(1)?,
description: row.get(2)?,
date_modified: row.get(3)?,
category: None,
aliases: vec![],
})
});
match platform_result {
Ok(mut platform) => {
let mut platform_alias_stmt = conn.prepare(
"SELECT pa.name FROM platform_alias pa WHERE pa.platformId = ?")?;
let platform_alias_iter = platform_alias_stmt.query_map(params![&platform.id], |row| row.get(0))?;
for alias in platform_alias_iter {
platform.aliases.push(alias.unwrap());
}
Ok(Some(platform))
},
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e),
}
}
pub fn save(conn: &Connection, partial: &PartialTag) -> Result<Tag> {
rusqlite::vtab::array::load_module(conn)?;
let mut tag = match find_by_id(conn, partial.id)? {
Some(t) => t,
None => return Err(rusqlite::Error::QueryReturnedNoRows)
};
let mut new_tag_aliases = vec![];
if tag.name != partial.name {
let stmt = "UPDATE game
SET platformName = ?
WHERE game.id IN (
SELECT gameId FROM game_platforms_platform WHERE platformId = ?
)";
conn.execute(stmt, params![partial.name, tag.id])?;
}
tag.apply_partial(partial);
let mut stmt = conn.prepare("SELECT platformId FROM platform_alias WHERE name = ?")?;
for alias in tag.aliases.clone() {
let existing_platform_id = stmt.query_row(params![alias], |row| row.get::<_, i64>(0)).optional()?;
match existing_platform_id {
Some(id) => {
if id != tag.id {
return Err(rusqlite::Error::QueryReturnedNoRows) }
},
None => {
new_tag_aliases.push(alias);
}
}
}
stmt = conn.prepare("UPDATE platform SET description = ?, dateModified = ? WHERE id = ?")?;
stmt.execute(params![tag.description, tag.date_modified, tag.id])?;
let mut stmt = "DELETE FROM platform_alias WHERE platformId = ? AND name NOT IN rarray(?)";
let alias_rc = Rc::new(tag.aliases.iter().map(|v| Value::from(v.clone())).collect::<Vec<Value>>());
conn.execute(stmt, params![tag.id, alias_rc])?;
for alias in new_tag_aliases {
stmt = "INSERT INTO platform_alias (name, platformId) VALUES (?, ?)";
conn.execute(stmt, params![alias, tag.id])?;
}
stmt = "UPDATE platform SET primaryAliasId = (SELECT id FROM platform_alias WHERE name = ?) WHERE id = ?";
conn.execute(stmt, params![tag.name, tag.id])?;
stmt = "UPDATE game
SET platformsStr = (
SELECT IFNULL(string_agg(pa.name, '; '), '')
FROM game_platforms_platform gpp
JOIN platform p ON gpp.platformId = p.id
JOIN platform_alias pa ON p.primaryAliasId = pa.id
WHERE gpp.gameId = game.id
) WHERE game.id IN (
SELECT gameId FROM game_platforms_platform WHERE platformId = ?
)";
conn.execute(stmt, params![tag.id])?;
match find_by_id(&conn, tag.id)? {
Some(t) => Ok(t),
None => return Err(rusqlite::Error::QueryReturnedNoRows)
}
}
pub fn delete(conn: &Connection, name: &str) -> Result<()> {
let tag = find_by_name(conn, name)?;
match tag {
Some(tag) => {
let mut stmt = "DELETE FROM platform_alias WHERE platformId = ?";
conn.execute(stmt, params![tag.id])?;
stmt = "DELETE FROM platform WHERE id = ?";
conn.execute(stmt, params![tag.id])?;
stmt = "UPDATE game
SET platformName = ?
WHERE game.id IN (
SELECT gameId FROM game_platforms_platform WHERE platformId = ?
)";
conn.execute(stmt, params!["", tag.id])?;
stmt = "UPDATE game
SET platformsStr = (
SELECT IFNULL(string_agg(pa.name, '; '), '')
FROM game_platforms_platform gpp
JOIN platform p ON gpp.platformId = p.id
JOIN platform_alias pa ON p.primaryAliasId = pa.id
WHERE gpp.gameId = game.id
) WHERE game.id IN (
SELECT gameId FROM game_platforms_platform WHERE platformId = ?
)";
conn.execute(stmt, params![tag.id])?;
stmt = "DELETE FROM game_platforms_platform WHERE platformId = ?";
conn.execute(stmt, params![tag.id])?;
Ok(())
},
None => Err(rusqlite::Error::QueryReturnedNoRows),
}
}
pub fn search_platform_suggestions(
conn: &Connection,
partial: &str,
) -> Result<Vec<TagSuggestion>> {
let mut suggestions = vec![];
let query = "SELECT sugg.tagId, sugg.matched_alias, count(game_tag.gameId) as gameCount, sugg.primary_alias FROM (
SELECT
ta1.platformId as tagId,
ta1.name AS matched_alias,
ta2.name AS primary_alias
FROM
platform_alias ta1
JOIN
platform t ON ta1.platformId = t.id
JOIN
platform_alias ta2 ON t.primaryAliasId = ta2.id
WHERE
ta1.name LIKE ?
) sugg
LEFT JOIN game_platforms_platform game_tag ON game_tag.platformId = sugg.tagId
GROUP BY sugg.matched_alias
ORDER BY COUNT(game_tag.gameId) DESC, sugg.matched_alias ASC";
let mut stmt = conn.prepare(&query)?;
let mut likeable = String::from(partial);
likeable.push_str("%");
let results = stmt.query_map(params![&likeable], |row| {
Ok(TagSuggestion {
id: row.get(0)?,
matched_from: row.get(1)?,
games_count: row.get(2)?,
name: row.get(3)?,
category: None,
})
})?;
for sugg in results {
suggestions.push(sugg?);
}
Ok(suggestions)
}