use rusqlite::OptionalExtension;
use crate::{
FileOrigin, RootId, block::FileId as StoreFileId, path::NormalizedPath, sql::SqlStore,
};
impl<'conn> SqlStore<'conn> {
pub fn unlink_file_from_path(
&self,
root_id: RootId,
path: &NormalizedPath,
) -> crate::Result<StoreFileId> {
let root_row_id: i64 = self.db.query_row(
r#"
SELECT
id
FROM
liteboxfs_roots
WHERE
uuid = ?;
"#,
rusqlite::params![root_id.to_string()],
|row| row.get(0),
)?;
let path_row_id = self.resolve_path_id(root_row_id, root_id, path)?;
let file_id: i64 = self.db.query_row(
r#"
DELETE FROM
liteboxfs_paths
WHERE
id = ?
RETURNING
file;
"#,
rusqlite::params![path_row_id],
|row| row.get(0),
)?;
Ok(StoreFileId::from(file_id))
}
pub fn list_unlinked_files(&self) -> crate::Result<Vec<StoreFileId>> {
let mut stmt = self.db.prepare_cached(
r#"
SELECT
liteboxfs_files.id
FROM
liteboxfs_files
WHERE
NOT EXISTS (
SELECT
1
FROM
liteboxfs_paths
WHERE
liteboxfs_paths.file = liteboxfs_files.id
);
"#,
)?;
Ok(stmt
.query_map([], |row| row.get(0))?
.collect::<Result<Vec<_>, rusqlite::Error>>()?)
}
pub fn delete_file_if_unlinked(&self, file_id: StoreFileId) -> crate::Result<()> {
let mut stmt = self.db.prepare_cached(
r#"
DELETE FROM
liteboxfs_files
WHERE
id = ?
AND NOT EXISTS (
SELECT
1
FROM
liteboxfs_paths
WHERE
liteboxfs_paths.file = liteboxfs_files.id
);
"#,
)?;
stmt.execute(rusqlite::params![file_id])?;
Ok(())
}
pub fn path_has_children(&self, root_id: RootId, path: &NormalizedPath) -> crate::Result<bool> {
let root_row_id: i64 = self.db.query_row(
r#"
SELECT
id
FROM
liteboxfs_roots
WHERE
uuid = ?;
"#,
rusqlite::params![root_id.to_string()],
|row| row.get(0),
)?;
let path_row_id = self.resolve_path_id(root_row_id, root_id, path)?;
let has_children = self
.db
.query_row(
r#"
SELECT
1
FROM
liteboxfs_paths
WHERE
parent = ?
LIMIT 1;
"#,
rusqlite::params![path_row_id],
|_| Ok(()),
)
.optional()?
.is_some();
Ok(has_children)
}
pub fn unlink_tree(
&self,
root_id: RootId,
path: &NormalizedPath,
) -> crate::Result<Vec<StoreFileId>> {
let root_row_id: i64 = self.db.query_row(
r#"
SELECT
id
FROM
liteboxfs_roots
WHERE
uuid = ?;
"#,
rusqlite::params![root_id.to_string()],
|row| row.get(0),
)?;
let path_row_id = self.resolve_path_id(root_row_id, root_id, path)?;
let mut stmt = self.db.prepare(
r#"
WITH RECURSIVE subtree(id) AS (
SELECT ?1
UNION ALL
SELECT
liteboxfs_paths.id
FROM
liteboxfs_paths
JOIN
subtree ON liteboxfs_paths.parent = subtree.id
)
DELETE FROM
liteboxfs_paths
WHERE
id IN (SELECT id FROM subtree)
RETURNING
file;
"#,
)?;
let file_ids: Vec<StoreFileId> = stmt
.query_map(rusqlite::params![path_row_id], |row| row.get::<_, i64>(0))?
.map(|r| r.map(StoreFileId::from))
.collect::<Result<_, _>>()?;
if file_ids.is_empty() {
return Err(crate::Error::FileNotFound {
file: FileOrigin::Litebox {
root: root_id,
locator: path.to_owned().into(),
},
});
}
Ok(file_ids)
}
}