REFACTOR A solid final draft.
This commit is contained in:
		
							parent
							
								
									013ca18c62
								
							
						
					
					
						commit
						e3fe863235
					
				| 
						 | 
					@ -7,7 +7,6 @@ pub use crate::errors::NoteStoreError;
 | 
				
			||||||
pub use crate::store::NoteStore;
 | 
					pub use crate::store::NoteStore;
 | 
				
			||||||
pub use crate::structs::{Note, NoteKind, NoteRelationship, PageRelationship};
 | 
					pub use crate::structs::{Note, NoteKind, NoteRelationship, PageRelationship};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
| 
						 | 
					@ -113,6 +112,4 @@ mod tests {
 | 
				
			||||||
        assert_eq!(newpages[1].parent_id, Some(newroot.id.clone()));
 | 
					        assert_eq!(newpages[1].parent_id, Some(newroot.id.clone()));
 | 
				
			||||||
        assert_eq!(newpages[2].parent_id, Some(newpages[1].id.clone()));
 | 
					        assert_eq!(newpages[2].parent_id, Some(newpages[1].id.clone()));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,8 @@ use lazy_static::lazy_static;
 | 
				
			||||||
use regex::Regex;
 | 
					use regex::Regex;
 | 
				
			||||||
use slug::slugify;
 | 
					use slug::slugify;
 | 
				
			||||||
use sqlx::{sqlite::Sqlite, Acquire, Done, Executor, Transaction};
 | 
					use sqlx::{sqlite::Sqlite, Acquire, Done, Executor, Transaction};
 | 
				
			||||||
use std::collections::HashSet;
 | 
					 | 
				
			||||||
use std::cmp;
 | 
					use std::cmp;
 | 
				
			||||||
 | 
					use std::collections::HashSet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SqlResult<T> = sqlx::Result<T>;
 | 
					type SqlResult<T> = sqlx::Result<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,25 +20,24 @@ type SqlResult<T> = sqlx::Result<T>;
 | 
				
			||||||
// of the SQL queries.
 | 
					// of the SQL queries.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static! {
 | 
					lazy_static! {
 | 
				
			||||||
	static ref SELECT_PAGE_BY_TITLE_SQL: String = str::replace(
 | 
					    static ref SELECT_PAGE_BY_TITLE_SQL: String = str::replace(
 | 
				
			||||||
		include_str!("sql/select_notes_by_parameter.sql"),
 | 
					        include_str!("sql/select_notes_by_parameter.sql"),
 | 
				
			||||||
		"QUERYPARAMETER",
 | 
					        "QUERYPARAMETER",
 | 
				
			||||||
		"notes.content"
 | 
					        "notes.content"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static! {
 | 
					lazy_static! {
 | 
				
			||||||
	static ref SELECT_PAGE_BY_ID_SQL: String = str::replace(
 | 
					    static ref SELECT_PAGE_BY_ID_SQL: String = str::replace(
 | 
				
			||||||
		include_str!("sql/select_notes_by_parameter.sql"),
 | 
					        include_str!("sql/select_notes_by_parameter.sql"),
 | 
				
			||||||
		"QUERYPARAMETER",
 | 
					        "QUERYPARAMETER",
 | 
				
			||||||
		"notes.id"
 | 
					        "notes.id"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
lazy_static! {
 | 
					lazy_static! {
 | 
				
			||||||
	static ref SELECT_NOTES_BACKREFERENCING_PAGE_SQL: &'static str =
 | 
					    static ref SELECT_NOTES_BACKREFERENCING_PAGE_SQL: &'static str =
 | 
				
			||||||
		include_str!("sql/select_notes_backreferencing_page.sql");
 | 
					        include_str!("sql/select_notes_backreferencing_page.sql");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ___             _
 | 
					//  ___             _
 | 
				
			||||||
| 
						 | 
					@ -49,13 +48,10 @@ lazy_static! {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn reset_database<'a, E>(executor: E) -> SqlResult<()>
 | 
					pub(crate) async fn reset_database<'a, E>(executor: E) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let initialize_sql = include_str!("sql/initialize_database.sql");
 | 
					    let initialize_sql = include_str!("sql/initialize_database.sql");
 | 
				
			||||||
	sqlx::query(initialize_sql)
 | 
					    sqlx::query(initialize_sql).execute(executor).await.map(|_| ())
 | 
				
			||||||
		.execute(executor)
 | 
					 | 
				
			||||||
		.await
 | 
					 | 
				
			||||||
		.map(|_| ())
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ___    _      _      _  __        _
 | 
					//  ___    _      _      _  __        _
 | 
				
			||||||
| 
						 | 
					@ -68,16 +64,12 @@ where
 | 
				
			||||||
// SQL operations are quite different between the first two and the last.
 | 
					// SQL operations are quite different between the first two and the last.
 | 
				
			||||||
async fn select_object_by_query<'a, E>(executor: E, query: &str, field: &str) -> SqlResult<Vec<Note>>
 | 
					async fn select_object_by_query<'a, E>(executor: E, query: &str, field: &str) -> SqlResult<Vec<Note>>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let r: Vec<RowNote> = sqlx::query_as(query)
 | 
					    let r: Vec<RowNote> = sqlx::query_as(query).bind(field).fetch_all(executor).await?;
 | 
				
			||||||
		.bind(field)
 | 
					    Ok(r.into_iter().map(|z| Note::from(z)).collect())
 | 
				
			||||||
		.fetch_all(executor)
 | 
					 | 
				
			||||||
		.await?;
 | 
					 | 
				
			||||||
	Ok(r.into_iter().map(|z| Note::from(z)).collect())
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Select the requested page via its id.  This is fairly rare;
 | 
					// Select the requested page via its id.  This is fairly rare;
 | 
				
			||||||
// pages should usually be picked up via their title, but if you're
 | 
					// pages should usually be picked up via their title, but if you're
 | 
				
			||||||
// navigating to an instance, this is how you specify the page in a
 | 
					// navigating to an instance, this is how you specify the page in a
 | 
				
			||||||
| 
						 | 
					@ -89,9 +81,9 @@ where
 | 
				
			||||||
// page.
 | 
					// page.
 | 
				
			||||||
pub(crate) async fn select_page_by_slug<'a, E>(executor: E, slug: &str) -> SqlResult<Vec<Note>>
 | 
					pub(crate) async fn select_page_by_slug<'a, E>(executor: E, slug: &str) -> SqlResult<Vec<Note>>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	select_object_by_query(executor, &SELECT_PAGE_BY_ID_SQL, &slug).await
 | 
					    select_object_by_query(executor, &SELECT_PAGE_BY_ID_SQL, &slug).await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Fetch the page by title.  The return value is an array of Note
 | 
					// Fetch the page by title.  The return value is an array of Note
 | 
				
			||||||
| 
						 | 
					@ -99,23 +91,20 @@ where
 | 
				
			||||||
// these into a tree-like object.
 | 
					// these into a tree-like object.
 | 
				
			||||||
pub(crate) async fn select_page_by_title<'a, E>(executor: E, title: &str) -> SqlResult<Vec<Note>>
 | 
					pub(crate) async fn select_page_by_title<'a, E>(executor: E, title: &str) -> SqlResult<Vec<Note>>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	select_object_by_query(executor, &SELECT_PAGE_BY_TITLE_SQL, &title).await
 | 
					    select_object_by_query(executor, &SELECT_PAGE_BY_TITLE_SQL, &title).await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Fetch all backreferences to a page.  The return value is an array
 | 
					// Fetch all backreferences to a page.  The return value is an array
 | 
				
			||||||
// of arrays, and inside each array is a list from a root page to
 | 
					// of arrays, and inside each array is a list from a root page to
 | 
				
			||||||
// the note that references the give page.  Clients may choose how
 | 
					// the note that references the give page.  Clients may choose how
 | 
				
			||||||
// they want to display that collection.
 | 
					// they want to display that collection.
 | 
				
			||||||
pub(crate) async fn select_backreferences_for_page<'a, E>(
 | 
					pub(crate) async fn select_backreferences_for_page<'a, E>(executor: E, page_id: &str) -> SqlResult<Vec<Note>>
 | 
				
			||||||
	executor: E,
 | 
					 | 
				
			||||||
	page_id: &str,
 | 
					 | 
				
			||||||
) -> SqlResult<Vec<Note>>
 | 
					 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	select_object_by_query(executor, &SELECT_NOTES_BACKREFERENCING_PAGE_SQL, &page_id).await
 | 
					    select_object_by_query(executor, &SELECT_NOTES_BACKREFERENCING_PAGE_SQL, &page_id).await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ___                  _      ___             _  _     _
 | 
					//  ___                  _      ___             _  _     _
 | 
				
			||||||
| 
						 | 
					@ -127,53 +116,52 @@ where
 | 
				
			||||||
// Inserts a single note into the notes table.  That is all.
 | 
					// Inserts a single note into the notes table.  That is all.
 | 
				
			||||||
pub(crate) async fn insert_note<'a, E>(executor: E, note: &NewNote) -> SqlResult<String>
 | 
					pub(crate) async fn insert_note<'a, E>(executor: E, note: &NewNote) -> SqlResult<String>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let insert_one_note_sql = concat!(
 | 
					    let insert_one_note_sql = concat!(
 | 
				
			||||||
		"INSERT INTO notes (id, content, kind, ",
 | 
					        "INSERT INTO notes (id, content, kind, ",
 | 
				
			||||||
		"                   creation_date, updated_date, lastview_date) ",
 | 
					        "                   creation_date, updated_date, lastview_date) ",
 | 
				
			||||||
		"VALUES (?, ?, ?, ?, ?, ?);"
 | 
					        "VALUES (?, ?, ?, ?, ?, ?);"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let _ = sqlx::query(insert_one_note_sql)
 | 
					    let _ = sqlx::query(insert_one_note_sql)
 | 
				
			||||||
		.bind(¬e.id)
 | 
					        .bind(¬e.id)
 | 
				
			||||||
		.bind(¬e.content)
 | 
					        .bind(¬e.content)
 | 
				
			||||||
		.bind(note.kind.to_string())
 | 
					        .bind(note.kind.to_string())
 | 
				
			||||||
		.bind(¬e.creation_date)
 | 
					        .bind(¬e.creation_date)
 | 
				
			||||||
		.bind(¬e.updated_date)
 | 
					        .bind(¬e.updated_date)
 | 
				
			||||||
		.bind(¬e.lastview_date)
 | 
					        .bind(¬e.lastview_date)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	Ok(note.id.clone())
 | 
					    Ok(note.id.clone())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Inserts a single note into the notes table.  That is all.
 | 
					// Inserts a single note into the notes table.  That is all.
 | 
				
			||||||
pub(crate) async fn bulk_insert_notes<'a, E>(executor: E, notes: &[NewNote]) -> SqlResult<()>
 | 
					pub(crate) async fn insert_bulk_notes<'a, E>(executor: E, notes: &[NewNote]) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if notes.is_empty() {
 | 
					    if notes.is_empty() {
 | 
				
			||||||
		return Ok(());
 | 
					        return Ok(());
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let insert_pattern = "VALUES (?, ?, ?, ?, ?, ?)".to_string();
 | 
					 | 
				
			||||||
	let insert_bulk_notes_sql = 
 | 
					 | 
				
			||||||
		"INSERT INTO notes (id, content, kind, creation_date, updated_date, lastview_date) ".to_string()
 | 
					 | 
				
			||||||
		+ &[insert_pattern.as_str()]
 | 
					 | 
				
			||||||
		.repeat(notes.len())
 | 
					 | 
				
			||||||
		.join(", ") + &";".to_string();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let mut request = sqlx::query(&insert_bulk_notes_sql);
 | 
					    let insert_pattern = "VALUES (?, ?, ?, ?, ?, ?)".to_string();
 | 
				
			||||||
	for note in notes {
 | 
					    let insert_bulk_notes_sql = "INSERT INTO notes (id, content, kind, creation_date, updated_date, lastview_date) "
 | 
				
			||||||
		request = request
 | 
					        .to_string()
 | 
				
			||||||
			.bind(¬e.id)
 | 
					        + &[insert_pattern.as_str()].repeat(notes.len()).join(", ")
 | 
				
			||||||
			.bind(¬e.content)
 | 
					        + &";".to_string();
 | 
				
			||||||
			.bind(note.kind.to_string())
 | 
					
 | 
				
			||||||
			.bind(¬e.creation_date)
 | 
					    let mut request = sqlx::query(&insert_bulk_notes_sql);
 | 
				
			||||||
			.bind(¬e.updated_date)
 | 
					    for note in notes {
 | 
				
			||||||
			.bind(¬e.lastview_date);
 | 
					        request = request
 | 
				
			||||||
	}
 | 
					            .bind(¬e.id)
 | 
				
			||||||
	request.execute(executor).await.map(|_| ())
 | 
					            .bind(¬e.content)
 | 
				
			||||||
 | 
					            .bind(note.kind.to_string())
 | 
				
			||||||
 | 
					            .bind(¬e.creation_date)
 | 
				
			||||||
 | 
					            .bind(¬e.updated_date)
 | 
				
			||||||
 | 
					            .bind(¬e.lastview_date);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    request.execute(executor).await.map(|_| ())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ___      _ _    _   _  __        _
 | 
					//  ___      _ _    _   _  __        _
 | 
				
			||||||
| 
						 | 
					@ -185,21 +173,21 @@ where
 | 
				
			||||||
// Given a possible slug, find the slug with the highest
 | 
					// Given a possible slug, find the slug with the highest
 | 
				
			||||||
// uniquification number, and return that number, if any.
 | 
					// uniquification number, and return that number, if any.
 | 
				
			||||||
pub(crate) fn find_maximal_slug_number(slugs: &[JustId]) -> Option<u32> {
 | 
					pub(crate) fn find_maximal_slug_number(slugs: &[JustId]) -> Option<u32> {
 | 
				
			||||||
	lazy_static! {
 | 
					    lazy_static! {
 | 
				
			||||||
		static ref RE_CAP_NUM: Regex = Regex::new(r"-(\d+)$").unwrap();
 | 
					        static ref RE_CAP_NUM: Regex = Regex::new(r"-(\d+)$").unwrap();
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if slugs.is_empty() {
 | 
					    if slugs.is_empty() {
 | 
				
			||||||
		return None;
 | 
					        return None;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let mut slug_counters: Vec<u32> = slugs
 | 
					    let mut slug_counters: Vec<u32> = slugs
 | 
				
			||||||
		.iter()
 | 
					        .iter()
 | 
				
			||||||
		.filter_map(|slug| RE_CAP_NUM.captures(&slug.id))
 | 
					        .filter_map(|slug| RE_CAP_NUM.captures(&slug.id))
 | 
				
			||||||
		.map(|cap| cap.get(1).unwrap().as_str().parse::<u32>().unwrap())
 | 
					        .map(|cap| cap.get(1).unwrap().as_str().parse::<u32>().unwrap())
 | 
				
			||||||
		.collect();
 | 
					        .collect();
 | 
				
			||||||
	slug_counters.sort_unstable();
 | 
					    slug_counters.sort_unstable();
 | 
				
			||||||
	slug_counters.pop()
 | 
					    slug_counters.pop()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Given an initial string and an existing collection of slugs,
 | 
					// Given an initial string and an existing collection of slugs,
 | 
				
			||||||
| 
						 | 
					@ -208,38 +196,38 @@ pub(crate) fn find_maximal_slug_number(slugs: &[JustId]) -> Option<u32> {
 | 
				
			||||||
// isn't all that.
 | 
					// isn't all that.
 | 
				
			||||||
pub(crate) async fn generate_slug<'a, E>(executor: E, title: &str) -> SqlResult<String>
 | 
					pub(crate) async fn generate_slug<'a, E>(executor: E, title: &str) -> SqlResult<String>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	lazy_static! {
 | 
					    lazy_static! {
 | 
				
			||||||
		static ref RE_STRIP_NUM: Regex = Regex::new(r"-\d+$").unwrap();
 | 
					        static ref RE_STRIP_NUM: Regex = Regex::new(r"-\d+$").unwrap();
 | 
				
			||||||
		static ref SLUG_FINDER_SQL: String = format!(
 | 
					        static ref SLUG_FINDER_SQL: String = format!(
 | 
				
			||||||
			"SELECT id FROM notes WHERE kind = '{}' AND id LIKE '?%';",
 | 
					            "SELECT id FROM notes WHERE kind = '{}' AND id LIKE '?%';",
 | 
				
			||||||
			NoteKind::Page.to_string()
 | 
					            NoteKind::Page.to_string()
 | 
				
			||||||
		);
 | 
					        );
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let initial_slug = slugify(title);
 | 
					    let initial_slug = slugify(title);
 | 
				
			||||||
	let sample_slug = RE_STRIP_NUM.replace_all(&initial_slug, "");
 | 
					    let sample_slug = RE_STRIP_NUM.replace_all(&initial_slug, "");
 | 
				
			||||||
	let similar_slugs: Vec<JustId> = sqlx::query_as(&SLUG_FINDER_SQL)
 | 
					    let similar_slugs: Vec<JustId> = sqlx::query_as(&SLUG_FINDER_SQL)
 | 
				
			||||||
		.bind(&*sample_slug)
 | 
					        .bind(&*sample_slug)
 | 
				
			||||||
		.fetch_all(executor)
 | 
					        .fetch_all(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	let maximal_slug_number = find_maximal_slug_number(&similar_slugs);
 | 
					    let maximal_slug_number = find_maximal_slug_number(&similar_slugs);
 | 
				
			||||||
	Ok(match maximal_slug_number {
 | 
					    Ok(match maximal_slug_number {
 | 
				
			||||||
		None => initial_slug,
 | 
					        None => initial_slug,
 | 
				
			||||||
		Some(slug_number) => format!("{}-{}", initial_slug, slug_number + 1),
 | 
					        Some(slug_number) => format!("{}-{}", initial_slug, slug_number + 1),
 | 
				
			||||||
	})
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A helper function: given a title and a slug, create a PageType
 | 
					// A helper function: given a title and a slug, create a PageType
 | 
				
			||||||
// note.
 | 
					// note.
 | 
				
			||||||
pub(crate) fn create_page(title: &str, slug: &str) -> NewNote {
 | 
					pub(crate) fn create_page(title: &str, slug: &str) -> NewNote {
 | 
				
			||||||
	NewNoteBuilder::default()
 | 
					    NewNoteBuilder::default()
 | 
				
			||||||
		.id(slug.to_string())
 | 
					        .id(slug.to_string())
 | 
				
			||||||
		.content(title.to_string())
 | 
					        .content(title.to_string())
 | 
				
			||||||
		.kind(NoteKind::Page)
 | 
					        .kind(NoteKind::Page)
 | 
				
			||||||
		.build()
 | 
					        .build()
 | 
				
			||||||
		.unwrap()
 | 
					        .unwrap()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  _   _          _      _          ___             _  _     _
 | 
					//  _   _          _      _          ___             _  _     _
 | 
				
			||||||
| 
						 | 
					@ -248,26 +236,22 @@ pub(crate) fn create_page(title: &str, slug: &str) -> NewNote {
 | 
				
			||||||
//  \___/| .__/\__,_\__,_|\__\___|  \___/|_||_\___| |_|\_\___/\__\___|
 | 
					//  \___/| .__/\__,_\__,_|\__\___|  \___/|_||_\___| |_|\_\___/\__\___|
 | 
				
			||||||
//       |_|
 | 
					//       |_|
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn update_note_content<'a, E>(
 | 
					pub(crate) async fn update_note_content<'a, E>(executor: E, note_id: &str, content: &str) -> SqlResult<()>
 | 
				
			||||||
	executor: E,
 | 
					 | 
				
			||||||
	note_id: &NoteId,
 | 
					 | 
				
			||||||
	content: &str,
 | 
					 | 
				
			||||||
) -> SqlResult<()>
 | 
					 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let update_note_content_sql = "UPDATE notes SET content = ? WHERE note_id = ?";
 | 
					    let update_note_content_sql = "UPDATE notes SET content = ? WHERE note_id = ?";
 | 
				
			||||||
	let count = sqlx::query(update_note_content_sql)
 | 
					    let count = sqlx::query(update_note_content_sql)
 | 
				
			||||||
		.bind(content)
 | 
					        .bind(content)
 | 
				
			||||||
		.bind(&**note_id)
 | 
					        .bind(&**note_id)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?
 | 
					        .await?
 | 
				
			||||||
		.rows_affected();
 | 
					        .rows_affected();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	match count {
 | 
					    match count {
 | 
				
			||||||
		1 => Ok(()),
 | 
					        1 => Ok(()),
 | 
				
			||||||
		_ => Err(sqlx::Error::RowNotFound),
 | 
					        _ => Err(sqlx::Error::RowNotFound),
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ___    _      _       ___             _  _     _         ___     _      _   _             _    _
 | 
					//  ___    _      _       ___             _  _     _         ___     _      _   _             _    _
 | 
				
			||||||
| 
						 | 
					@ -277,25 +261,25 @@ where
 | 
				
			||||||
//                                                                                                  |_|
 | 
					//                                                                                                  |_|
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn select_note_to_note_relationship<'a, E>(
 | 
					pub(crate) async fn select_note_to_note_relationship<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	parent_id: &str,
 | 
					    parent_id: &str,
 | 
				
			||||||
	note_id: &str,
 | 
					    note_id: &str,
 | 
				
			||||||
) -> SqlResult<NoteRelationship>
 | 
					) -> SqlResult<NoteRelationship>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let get_note_to_note_relationship_sql = concat!(
 | 
					    let get_note_to_note_relationship_sql = concat!(
 | 
				
			||||||
		"SELECT parent_id, note_id, location, kind ",
 | 
					        "SELECT parent_id, note_id, location, kind ",
 | 
				
			||||||
		"FROM note_relationships ",
 | 
					        "FROM note_relationships ",
 | 
				
			||||||
		"WHERE parent_id = ? and note_id = ? ",
 | 
					        "WHERE parent_id = ? and note_id = ? ",
 | 
				
			||||||
		"LIMIT 1"
 | 
					        "LIMIT 1"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
	let s: NoteRelationshipRow = sqlx::query_as(get_note_to_note_relationship_sql)
 | 
					    let s: NoteRelationshipRow = sqlx::query_as(get_note_to_note_relationship_sql)
 | 
				
			||||||
		.bind(parent_id)
 | 
					        .bind(parent_id)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.fetch_one(executor)
 | 
					        .fetch_one(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	Ok(NoteRelationship::from(s))
 | 
					    Ok(NoteRelationship::from(s))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  _  _     _         _         _  _     _         ___     _      _   _             _    _
 | 
					//  _  _     _         _         _  _     _         ___     _      _   _             _    _
 | 
				
			||||||
| 
						 | 
					@ -305,83 +289,80 @@ where
 | 
				
			||||||
//                                                                                         |_|
 | 
					//                                                                                         |_|
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn insert_note_to_note_relationship<'a, E>(
 | 
					pub(crate) async fn insert_note_to_note_relationship<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	parent_id: &str,
 | 
					    parent_id: &str,
 | 
				
			||||||
	note_id: &str,
 | 
					    note_id: &str,
 | 
				
			||||||
	location: i64,
 | 
					    location: i64,
 | 
				
			||||||
	kind: &RelationshipKind,
 | 
					    kind: &RelationshipKind,
 | 
				
			||||||
) -> SqlResult<()>
 | 
					) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let insert_note_to_note_relationship_sql = concat!(
 | 
					    let insert_note_to_note_relationship_sql = concat!(
 | 
				
			||||||
		"INSERT INTO note_relationships (parent_id, note_id, location, kind) ",
 | 
					        "INSERT INTO note_relationships (parent_id, note_id, location, kind) ",
 | 
				
			||||||
		"values (?, ?, ?, ?)"
 | 
					        "values (?, ?, ?, ?)"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let _ = sqlx::query(insert_note_to_note_relationship_sql)
 | 
					    let _ = sqlx::query(insert_note_to_note_relationship_sql)
 | 
				
			||||||
		.bind(parent_id)
 | 
					        .bind(parent_id)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.bind(&location)
 | 
					        .bind(&location)
 | 
				
			||||||
		.bind(kind.to_string())
 | 
					        .bind(kind.to_string())
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn make_room_for_new_note<'a, E>(
 | 
					pub(crate) async fn make_room_for_new_note_relationship<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	parent_id: &str,
 | 
					    parent_id: &str,
 | 
				
			||||||
	location: i64,
 | 
					    location: i64,
 | 
				
			||||||
) -> SqlResult<()>
 | 
					) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let make_room_for_new_note_sql = concat!(
 | 
					    let make_room_for_new_note_sql = concat!(
 | 
				
			||||||
		"UPDATE note_relationships ",
 | 
					        "UPDATE note_relationships ",
 | 
				
			||||||
		"SET location = location + 1 ",
 | 
					        "SET location = location + 1 ",
 | 
				
			||||||
		"WHERE location >= ? and parent_id = ?;"
 | 
					        "WHERE location >= ? and parent_id = ?;"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let _ = sqlx::query(make_room_for_new_note_sql)
 | 
					    let _ = sqlx::query(make_room_for_new_note_sql)
 | 
				
			||||||
		.bind(&location)
 | 
					        .bind(&location)
 | 
				
			||||||
		.bind(parent_id)
 | 
					        .bind(parent_id)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn determine_max_child_location_for_note<'a, E>(
 | 
					pub(crate) async fn determine_max_child_location_for_note<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	note_id: &str,
 | 
					    note_id: &str,
 | 
				
			||||||
	comp_loc: Option<i64>,
 | 
					    comp_loc: Option<i64>,
 | 
				
			||||||
) -> SqlResult<i64>
 | 
					) -> SqlResult<i64>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let row_count = assert_max_child_location_for_note(executor, note_id).await? + 1;
 | 
					    let row_count = assert_max_child_location_for_note(executor, note_id).await? + 1;
 | 
				
			||||||
	Ok(match comp_loc {
 | 
					    Ok(match comp_loc {
 | 
				
			||||||
		Some(location) => cmp::min(row_count, location),
 | 
					        Some(location) => cmp::min(row_count, location),
 | 
				
			||||||
		None => row_count
 | 
					        None => row_count,
 | 
				
			||||||
	})
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn assert_max_child_location_for_note<'a, E>(
 | 
					pub(crate) async fn assert_max_child_location_for_note<'a, E>(executor: E, note_id: &str) -> SqlResult<i64>
 | 
				
			||||||
	executor: E,
 | 
					 | 
				
			||||||
	note_id: &str,
 | 
					 | 
				
			||||||
) -> SqlResult<i64>
 | 
					 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let assert_max_child_location_for_note_sql =
 | 
					    let assert_max_child_location_for_note_sql =
 | 
				
			||||||
		"SELECT MAX(location) AS count FROM note_relationships WHERE parent_id = ?;";
 | 
					        "SELECT MAX(location) AS count FROM note_relationships WHERE parent_id = ?;";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let count: RowCount = sqlx::query_as(assert_max_child_location_for_note_sql)
 | 
					    let count: RowCount = sqlx::query_as(assert_max_child_location_for_note_sql)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.fetch_one(executor)
 | 
					        .fetch_one(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Ok(count.count)
 | 
					    Ok(count.count)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  _  _     _         _         _  __        _              ___     _      _   _             _    _
 | 
					//  _  _     _         _         _  __        _              ___     _      _   _             _    _
 | 
				
			||||||
| 
						 | 
					@ -391,57 +372,49 @@ where
 | 
				
			||||||
//                                                                                                  |_|
 | 
					//                                                                                                  |_|
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn insert_bulk_note_to_page_relationships<'a, E>(
 | 
					pub(crate) async fn insert_bulk_note_to_page_relationships<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	note_id: &str,
 | 
					    note_id: &str,
 | 
				
			||||||
	references: &[String],
 | 
					    references: &[String],
 | 
				
			||||||
) -> SqlResult<()>
 | 
					) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if references.is_empty() {
 | 
					    if references.is_empty() {
 | 
				
			||||||
		return Ok(());
 | 
					        return Ok(());
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let insert_pattern = format!("(?, ?, '{}')", PageRelationshipKind::Page.to_string());
 | 
					    let insert_pattern = format!("(?, ?, '{}')", PageRelationshipKind::Page.to_string());
 | 
				
			||||||
	let insert_note_page_references_sql =
 | 
					    let insert_note_page_references_sql = "INSERT INTO note_page_relationships (note_id, page_id, kind) VALUES "
 | 
				
			||||||
		"INSERT INTO note_page_relationships (note_id, page_id, kind) VALUES ".to_string()
 | 
					        .to_string()
 | 
				
			||||||
			+ &[insert_pattern.as_str()]
 | 
					        + &[insert_pattern.as_str()].repeat(references.len()).join(", ")
 | 
				
			||||||
				.repeat(references.len())
 | 
					        + &";".to_string();
 | 
				
			||||||
				.join(", ") + &";".to_string();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let mut request = sqlx::query(&insert_note_page_references_sql);
 | 
					    let mut request = sqlx::query(&insert_note_page_references_sql);
 | 
				
			||||||
	for reference in references {
 | 
					    for reference in references {
 | 
				
			||||||
		request = request.bind(note_id).bind(reference);
 | 
					        request = request.bind(note_id).bind(reference);
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	request.execute(executor).await.map(|_| ())
 | 
					    request.execute(executor).await.map(|_| ())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn delete_bulk_note_to_page_relationships<'a, E>(
 | 
					pub(crate) async fn delete_bulk_note_to_page_relationships<'a, E>(executor: E, note_id: &str) -> SqlResult<()>
 | 
				
			||||||
	executor: E,
 | 
					 | 
				
			||||||
	note_id: &str,
 | 
					 | 
				
			||||||
) -> SqlResult<()>
 | 
					 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let delete_note_to_page_relationship_sql =
 | 
					    let delete_note_to_page_relationship_sql = "DELETE FROM note_page_relationships WHERE and note_id = ?;";
 | 
				
			||||||
		"DELETE FROM note_page_relationships WHERE and note_id = ?;";
 | 
					    let _ = sqlx::query(delete_note_to_page_relationship_sql)
 | 
				
			||||||
	let _ = sqlx::query(delete_note_to_page_relationship_sql)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .execute(executor)
 | 
				
			||||||
		.execute(executor)
 | 
					        .await?;
 | 
				
			||||||
		.await?;
 | 
					    Ok(())
 | 
				
			||||||
	Ok(())
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Given the references supplied, and the references found in the datastore,
 | 
					// Given the references supplied, and the references found in the datastore,
 | 
				
			||||||
// return a list of the references not found in the datastore.
 | 
					// return a list of the references not found in the datastore.
 | 
				
			||||||
pub(crate) fn diff_references(
 | 
					pub(crate) fn diff_references(references: &[String], found_references: &[PageTitle]) -> Vec<String> {
 | 
				
			||||||
	references: &[String],
 | 
					    let all: HashSet<String> = references.iter().cloned().collect();
 | 
				
			||||||
	found_references: &[PageTitle],
 | 
					    let found: HashSet<String> = found_references.iter().map(|r| r.content.clone()).collect();
 | 
				
			||||||
) -> Vec<String> {
 | 
					    all.difference(&found).cloned().collect()
 | 
				
			||||||
	let all: HashSet<String> = references.iter().cloned().collect();
 | 
					 | 
				
			||||||
	let found: HashSet<String> = found_references.iter().map(|r| r.content.clone()).collect();
 | 
					 | 
				
			||||||
	all.difference(&found).cloned().collect()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//   ___         _           _     _         _  __        _              ___     _      _   _             _    _
 | 
					//   ___         _           _     _         _  __        _              ___     _      _   _             _    _
 | 
				
			||||||
| 
						 | 
					@ -454,32 +427,31 @@ pub(crate) fn diff_references(
 | 
				
			||||||
// list of titles.  Used by insert_note and update_note_content to
 | 
					// list of titles.  Used by insert_note and update_note_content to
 | 
				
			||||||
// find the ids of all the references in a given document.
 | 
					// find the ids of all the references in a given document.
 | 
				
			||||||
pub(crate) async fn find_all_page_from_list_of_references<'a, E>(
 | 
					pub(crate) async fn find_all_page_from_list_of_references<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	references: &[String],
 | 
					    references: &[String],
 | 
				
			||||||
) -> SqlResult<Vec<PageTitle>>
 | 
					) -> SqlResult<Vec<PageTitle>>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if references.is_empty() {
 | 
					    if references.is_empty() {
 | 
				
			||||||
		return Ok(vec![]);
 | 
					        return Ok(vec![]);
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	lazy_static! {
 | 
					    lazy_static! {
 | 
				
			||||||
		static ref SELECT_ALL_REFERENCES_FOR_SQL_BASE: String = format!(
 | 
					        static ref SELECT_ALL_REFERENCES_FOR_SQL_BASE: String = format!(
 | 
				
			||||||
			"SELECT id, content FROM notes WHERE kind = '{}' AND content IN (",
 | 
					            "SELECT id, content FROM notes WHERE kind = '{}' AND content IN (",
 | 
				
			||||||
			NoteKind::Page.to_string()
 | 
					            NoteKind::Page.to_string()
 | 
				
			||||||
		);
 | 
					        );
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let find_all_references_for_sql = SELECT_ALL_REFERENCES_FOR_SQL_BASE.to_string()
 | 
					    let find_all_references_for_sql =
 | 
				
			||||||
		+ &["?"].repeat(references.len()).join(",")
 | 
					        SELECT_ALL_REFERENCES_FOR_SQL_BASE.to_string() + &["?"].repeat(references.len()).join(",") + &");".to_string();
 | 
				
			||||||
		+ &");".to_string();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let mut request = sqlx::query_as(&find_all_references_for_sql);
 | 
					    let mut request = sqlx::query_as(&find_all_references_for_sql);
 | 
				
			||||||
	for id in references.iter() {
 | 
					    for id in references.iter() {
 | 
				
			||||||
		request = request.bind(id);
 | 
					        request = request.bind(id);
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	request.fetch_all(executor).await
 | 
					    request.fetch_all(executor).await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ___      _     _
 | 
					//  ___      _     _
 | 
				
			||||||
| 
						 | 
					@ -489,121 +461,116 @@ where
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn delete_note_to_note_relationship<'a, E>(
 | 
					pub(crate) async fn delete_note_to_note_relationship<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	parent_id: &str,
 | 
					    parent_id: &str,
 | 
				
			||||||
	note_id: &str,
 | 
					    note_id: &str,
 | 
				
			||||||
) -> SqlResult<()>
 | 
					) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let delete_note_to_note_relationship_sql = concat!(
 | 
					    let delete_note_to_note_relationship_sql = concat!(
 | 
				
			||||||
		"DELETE FROM note_relationships ",
 | 
					        "DELETE FROM note_relationships ",
 | 
				
			||||||
		"WHERE parent_id = ? and note_id = ? "
 | 
					        "WHERE parent_id = ? and note_id = ? "
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let count = sqlx::query(delete_note_to_note_relationship_sql)
 | 
					    let count = sqlx::query(delete_note_to_note_relationship_sql)
 | 
				
			||||||
		.bind(parent_id)
 | 
					        .bind(parent_id)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?
 | 
					        .await?
 | 
				
			||||||
		.rows_affected();
 | 
					        .rows_affected();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	match count {
 | 
					    match count {
 | 
				
			||||||
		1 => Ok(()),
 | 
					        1 => Ok(()),
 | 
				
			||||||
		_ => Err(sqlx::Error::RowNotFound),
 | 
					        _ => Err(sqlx::Error::RowNotFound),
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn delete_note_to_page_relationships<'a, E>(
 | 
					pub(crate) async fn delete_note_to_page_relationships<'a, E>(executor: E, note_id: &str) -> SqlResult<()>
 | 
				
			||||||
	executor: E,
 | 
					 | 
				
			||||||
	note_id: &str,
 | 
					 | 
				
			||||||
) -> SqlResult<()>
 | 
					 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	lazy_static! {
 | 
					    lazy_static! {
 | 
				
			||||||
		static ref DELETE_NOTE_TO_PAGE_RELATIONSHIPS_SQL: String = format!(
 | 
					        static ref DELETE_NOTE_TO_PAGE_RELATIONSHIPS_SQL: String = format!(
 | 
				
			||||||
			"DELETE FROM note_relationships WHERE kind in ('{}', '{}') AND parent_id = ?;",
 | 
					            "DELETE FROM note_relationships WHERE kind in ('{}', '{}') AND parent_id = ?;",
 | 
				
			||||||
			PageRelationshipKind::Page.to_string(),
 | 
					            PageRelationshipKind::Page.to_string(),
 | 
				
			||||||
			PageRelationshipKind::Unacked.to_string()
 | 
					            PageRelationshipKind::Unacked.to_string()
 | 
				
			||||||
		);
 | 
					        );
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let _ = sqlx::query(&DELETE_NOTE_TO_PAGE_RELATIONSHIPS_SQL)
 | 
					    let _ = sqlx::query(&DELETE_NOTE_TO_PAGE_RELATIONSHIPS_SQL)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn delete_note<'a, E>(executor: E, note_id: &str) -> SqlResult<()>
 | 
					pub(crate) async fn delete_note<'a, E>(executor: E, note_id: &str) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let delete_note_sql = "DELETE FROM notes WHERE note_id = ?";
 | 
					    let delete_note_sql = "DELETE FROM notes WHERE note_id = ?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let count = sqlx::query(delete_note_sql)
 | 
					    let count = sqlx::query(delete_note_sql)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?
 | 
					        .await?
 | 
				
			||||||
		.rows_affected();
 | 
					        .rows_affected();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	match count {
 | 
					    match count {
 | 
				
			||||||
		1 => Ok(()),
 | 
					        1 => Ok(()),
 | 
				
			||||||
		_ => Err(sqlx::Error::RowNotFound),
 | 
					        _ => Err(sqlx::Error::RowNotFound),
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// After removing a note, recalculate the position of all notes under
 | 
					// After removing a note, recalculate the position of all notes under
 | 
				
			||||||
// the parent note, such that there order is now completely
 | 
					// the parent note, such that there order is now completely
 | 
				
			||||||
// sequential.
 | 
					// sequential.
 | 
				
			||||||
pub(crate) async fn close_hole_for_deleted_note<'a, E>(
 | 
					pub(crate) async fn close_hole_for_deleted_note_relationship<'a, E>(
 | 
				
			||||||
	executor: E,
 | 
					    executor: E,
 | 
				
			||||||
	parent_id: &str,
 | 
					    parent_id: &str,
 | 
				
			||||||
	location: i64,
 | 
					    location: i64,
 | 
				
			||||||
) -> SqlResult<()>
 | 
					) -> SqlResult<()>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let close_hole_for_deleted_note_sql = concat!(
 | 
					    let close_hole_for_deleted_note_sql = concat!(
 | 
				
			||||||
		"UPDATE note_relationships ",
 | 
					        "UPDATE note_relationships ",
 | 
				
			||||||
		"SET location = location - 1 ",
 | 
					        "SET location = location - 1 ",
 | 
				
			||||||
		"WHERE location > ? and parent_id = ?;"
 | 
					        "WHERE location > ? and parent_id = ?;"
 | 
				
			||||||
	);
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let _ = sqlx::query(close_hole_for_deleted_note_sql)
 | 
					    let _ = sqlx::query(close_hole_for_deleted_note_sql)
 | 
				
			||||||
		.bind(&location)
 | 
					        .bind(&location)
 | 
				
			||||||
		.bind(parent_id)
 | 
					        .bind(parent_id)
 | 
				
			||||||
		.execute(executor)
 | 
					        .execute(executor)
 | 
				
			||||||
		.await?;
 | 
					        .await?;
 | 
				
			||||||
	Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Given a list of references found in the content, generate the
 | 
					// Given a list of references found in the content, generate the
 | 
				
			||||||
// references that do not previously exist, returning all found
 | 
					// references that do not previously exist, returning all found
 | 
				
			||||||
// references.  NOTE: The function signature for this is for a
 | 
					// references.  NOTE: The function signature for this is for a
 | 
				
			||||||
// transaction, and uses a nested transaction.
 | 
					// transaction, and uses a nested transaction.
 | 
				
			||||||
pub(crate) async fn validate_or_generate_all_found_references(
 | 
					pub(crate) async fn validate_or_generate_all_found_references(
 | 
				
			||||||
	txi: &mut Transaction<'_, Sqlite>,
 | 
					    txi: &mut Transaction<'_, Sqlite>,
 | 
				
			||||||
	references: &[String]
 | 
					    references: &[String],
 | 
				
			||||||
) -> SqlResult<Vec<String>> {
 | 
					) -> SqlResult<Vec<String>> {
 | 
				
			||||||
	let mut tx = txi.begin().await?;
 | 
					    let mut tx = txi.begin().await?;
 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let found_references =
 | 
					 | 
				
			||||||
		find_all_page_from_list_of_references(&mut tx, &references).await?;
 | 
					 | 
				
			||||||
	let new_references = diff_references(&references, &found_references);
 | 
					 | 
				
			||||||
	let mut new_page: Vec<NewNote> = vec![];
 | 
					 | 
				
			||||||
	for one_reference in new_references.iter() {
 | 
					 | 
				
			||||||
		let slug = generate_slug(&mut tx, one_reference).await?;
 | 
					 | 
				
			||||||
		new_page.push(create_page(&one_reference, &slug));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	let _ = bulk_insert_notes(&mut tx, &new_page).await?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let mut all_reference_ids: Vec<String> = found_references.iter().map(|r| r.id.clone()).collect();
 | 
					    let found_references = find_all_page_from_list_of_references(&mut tx, &references).await?;
 | 
				
			||||||
	all_reference_ids.append(&mut new_page.iter().map(|r| r.id.clone()).collect());
 | 
					    let new_references = diff_references(&references, &found_references);
 | 
				
			||||||
	tx.commit().await?;
 | 
					    let mut new_page: Vec<NewNote> = vec![];
 | 
				
			||||||
	Ok(all_reference_ids)
 | 
					    for one_reference in new_references.iter() {
 | 
				
			||||||
 | 
					        let slug = generate_slug(&mut tx, one_reference).await?;
 | 
				
			||||||
 | 
					        new_page.push(create_page(&one_reference, &slug));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let _ = insert_bulk_notes(&mut tx, &new_page).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut all_reference_ids: Vec<String> = found_references.iter().map(|r| r.id.clone()).collect();
 | 
				
			||||||
 | 
					    all_reference_ids.append(&mut new_page.iter().map(|r| r.id.clone()).collect());
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
 | 
					    Ok(all_reference_ids)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  __  __ _
 | 
					//  __  __ _
 | 
				
			||||||
| 
						 | 
					@ -616,13 +583,12 @@ pub(crate) async fn validate_or_generate_all_found_references(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) async fn count_existing_note_relationships<'a, E>(executor: E, note_id: &str) -> SqlResult<i64>
 | 
					pub(crate) async fn count_existing_note_relationships<'a, E>(executor: E, note_id: &str) -> SqlResult<i64>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
	E: Executor<'a, Database = Sqlite>,
 | 
					    E: Executor<'a, Database = Sqlite>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	let count_existing_note_relationships_sql =
 | 
					    let count_existing_note_relationships_sql = "SELECT COUNT(*) as count FROM note_relationships WHERE note_id = ?;";
 | 
				
			||||||
		"SELECT COUNT(*) as count FROM note_relationships WHERE note_id = ?;";
 | 
					    let count: RowCount = sqlx::query_as(&count_existing_note_relationships_sql)
 | 
				
			||||||
	let count: RowCount = sqlx::query_as(&count_existing_note_relationships_sql)
 | 
					        .bind(note_id)
 | 
				
			||||||
		.bind(note_id)
 | 
					        .fetch_one(executor)
 | 
				
			||||||
		.fetch_one(executor)
 | 
					        .await?;
 | 
				
			||||||
		.await?;
 | 
					    Ok(count.count)
 | 
				
			||||||
	Ok(count.count)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,204 +69,187 @@ pub type NoteResult<T> = core::result::Result<T, NoteStoreError>;
 | 
				
			||||||
// (box).
 | 
					// (box).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl NoteStore {
 | 
					impl NoteStore {
 | 
				
			||||||
	/// Initializes a new instance of the note store.  Note that the
 | 
					    /// Initializes a new instance of the note store.  Note that the
 | 
				
			||||||
	/// note store holds an Arc internally; this code is (I think)
 | 
					    /// note store holds an Arc internally; this code is (I think)
 | 
				
			||||||
	/// safe to Send.
 | 
					    /// safe to Send.
 | 
				
			||||||
	pub async fn new(url: &str) -> NoteResult<Self> {
 | 
					    pub async fn new(url: &str) -> NoteResult<Self> {
 | 
				
			||||||
		let pool = SqlitePool::connect(url).await?;
 | 
					        let pool = SqlitePool::connect(url).await?;
 | 
				
			||||||
		Ok(NoteStore(Arc::new(pool)))
 | 
					        Ok(NoteStore(Arc::new(pool)))
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
	/// Erase all the data in the database and restore it
 | 
					    /// Erase all the data in the database and restore it
 | 
				
			||||||
	/// to its original empty form.  Do not use unless you
 | 
					    /// to its original empty form.  Do not use unless you
 | 
				
			||||||
	/// really, really want that to happen.
 | 
					    /// really, really want that to happen.
 | 
				
			||||||
	pub async fn reset_database(&self) -> NoteResult<()> {
 | 
					    pub async fn reset_database(&self) -> NoteResult<()> {
 | 
				
			||||||
		reset_database(&*self.0)
 | 
					        reset_database(&*self.0).await.map_err(NoteStoreError::DBError)
 | 
				
			||||||
			.await
 | 
					    }
 | 
				
			||||||
			.map_err(NoteStoreError::DBError)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Fetch page by slug
 | 
					    /// Fetch page by slug
 | 
				
			||||||
	///
 | 
					    ///
 | 
				
			||||||
	/// Supports the use case of the user navigating to a known place
 | 
					    /// Supports the use case of the user navigating to a known place
 | 
				
			||||||
	/// via a bookmark or other URL.  Since the title isn't clear from
 | 
					    /// via a bookmark or other URL.  Since the title isn't clear from
 | 
				
			||||||
	/// the slug, the slug is insufficient to generate a new page, so
 | 
					    /// the slug, the slug is insufficient to generate a new page, so
 | 
				
			||||||
	/// this use case says that in the event of a failure to find the
 | 
					    /// this use case says that in the event of a failure to find the
 | 
				
			||||||
	/// requested page, return a basic NotFound.
 | 
					    /// requested page, return a basic NotFound.
 | 
				
			||||||
	pub async fn get_page_by_slug(&self, slug: &str) -> NoteResult<(Vec<Note>, Vec<Note>)> {
 | 
					    pub async fn get_page_by_slug(&self, slug: &str) -> NoteResult<(Vec<Note>, Vec<Note>)> {
 | 
				
			||||||
		let page = select_page_by_slug(&*self.0, slug).await?;
 | 
					        let page = select_page_by_slug(&*self.0, slug).await?;
 | 
				
			||||||
		if page.is_empty() {
 | 
					        if page.is_empty() {
 | 
				
			||||||
			return Err(NoteStoreError::NotFound);
 | 
					            return Err(NoteStoreError::NotFound);
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let note_id = &page[0].id;
 | 
					        let note_id = &page[0].id;
 | 
				
			||||||
		let backreferences = select_backreferences_for_page(&*self.0, ¬e_id).await?;
 | 
					        let backreferences = select_backreferences_for_page(&*self.0, ¬e_id).await?;
 | 
				
			||||||
		Ok((page, backreferences))
 | 
					        Ok((page, backreferences))
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Fetch page by title
 | 
					    /// Fetch page by title
 | 
				
			||||||
	///
 | 
					    ///
 | 
				
			||||||
	/// The most common use case: the user is navigating by requesting
 | 
					    /// The most common use case: the user is navigating by requesting
 | 
				
			||||||
	/// a page.  The page either exists or it doesn't.  If it
 | 
					    /// a page.  The page either exists or it doesn't.  If it
 | 
				
			||||||
	/// doesn't, we go out and make it.  Since we know it doesn't exist,
 | 
					    /// doesn't, we go out and make it.  Since we know it doesn't exist,
 | 
				
			||||||
	/// we also know no backreferences to it exist, so in that case you
 | 
					    /// we also know no backreferences to it exist, so in that case you
 | 
				
			||||||
	/// get back two empty vecs.
 | 
					    /// get back two empty vecs.
 | 
				
			||||||
	pub async fn get_page_by_title(&self, title: &str) -> NoteResult<(Vec<Note>, Vec<Note>)> {
 | 
					    pub async fn get_page_by_title(&self, title: &str) -> NoteResult<(Vec<Note>, Vec<Note>)> {
 | 
				
			||||||
		if title.len() == 0 {
 | 
					        if title.len() == 0 {
 | 
				
			||||||
			return Err(NoteStoreError::NotFound);
 | 
					            return Err(NoteStoreError::NotFound);
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let page = select_page_by_title(&*self.0, title).await?;
 | 
					        let page = select_page_by_title(&*self.0, title).await?;
 | 
				
			||||||
		if page.len() > 0 {
 | 
					        if page.len() > 0 {
 | 
				
			||||||
			let note_id = &page[0].id;
 | 
					            let note_id = &page[0].id;
 | 
				
			||||||
			let backreferences = select_backreferences_for_page(&*self.0, ¬e_id).await?;
 | 
					            let backreferences = select_backreferences_for_page(&*self.0, ¬e_id).await?;
 | 
				
			||||||
			return Ok((page, backreferences));
 | 
					            return Ok((page, backreferences));
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Sanity check!
 | 
					        // Sanity check!
 | 
				
			||||||
		let references = build_references(&title);
 | 
					        let references = build_references(&title);
 | 
				
			||||||
		if references.len() > 0 {
 | 
					        if references.len() > 0 {
 | 
				
			||||||
			return Err(NoteStoreError::InvalidNoteStructure(
 | 
					            return Err(NoteStoreError::InvalidNoteStructure(
 | 
				
			||||||
				"Titles may not contain nested references.".to_string(),
 | 
					                "Titles may not contain nested references.".to_string(),
 | 
				
			||||||
			));
 | 
					            ));
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let mut tx = self.0.begin().await?;
 | 
					        let mut tx = self.0.begin().await?;
 | 
				
			||||||
		let slug = generate_slug(&mut tx, title).await?;
 | 
					        let slug = generate_slug(&mut tx, title).await?;
 | 
				
			||||||
		let page = create_page(&title, &slug);
 | 
					        let page = create_page(&title, &slug);
 | 
				
			||||||
		let _ = insert_note(&mut tx, &page).await?;
 | 
					        let _ = insert_note(&mut tx, &page).await?;
 | 
				
			||||||
		tx.commit().await?;
 | 
					        tx.commit().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Ok((vec![Note::from(page)], vec![]))
 | 
					        Ok((vec![Note::from(page)], vec![]))
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub async fn add_note(
 | 
					    pub async fn add_note(&self, note: &NewNote, parent_id: &str, location: Option<i64>) -> NoteResult<String> {
 | 
				
			||||||
		&self,
 | 
					        let kind = RelationshipKind::Direct;
 | 
				
			||||||
		note: &NewNote,
 | 
					        let new_id = self.insert_note(note, parent_id, location, kind).await?;
 | 
				
			||||||
		parent_id: &str,
 | 
					        Ok(new_id)
 | 
				
			||||||
		location: Option<i64>,
 | 
					    }
 | 
				
			||||||
	) -> NoteResult<String> {
 | 
					 | 
				
			||||||
		let kind = RelationshipKind::Direct;
 | 
					 | 
				
			||||||
		let new_id = self.insert_note(note,	parent_id, location, kind).await?;
 | 
					 | 
				
			||||||
		Ok(new_id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Move a note from one location to another.
 | 
					    /// Move a note from one location to another.
 | 
				
			||||||
	pub async fn move_note(
 | 
					    pub async fn move_note(
 | 
				
			||||||
		&self,
 | 
					        &self,
 | 
				
			||||||
		note_id: &str,
 | 
					        note_id: &str,
 | 
				
			||||||
		old_parent_id: &str,
 | 
					        old_parent_id: &str,
 | 
				
			||||||
		new_parent_id: &str,
 | 
					        new_parent_id: &str,
 | 
				
			||||||
		new_location: i64,
 | 
					        new_location: i64,
 | 
				
			||||||
	) -> NoteResult<()> {
 | 
					    ) -> NoteResult<()> {
 | 
				
			||||||
		let mut tx = self.0.begin().await?;
 | 
					        let mut tx = self.0.begin().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let old_note = select_note_to_note_relationship(&mut tx, &old_parent_id, ¬e_id).await?;
 | 
					        let old_note = select_note_to_note_relationship(&mut tx, &old_parent_id, ¬e_id).await?;
 | 
				
			||||||
		let old_note_location = old_note.location;
 | 
					        let old_note_location = old_note.location;
 | 
				
			||||||
		let old_note_kind = old_note.kind;
 | 
					        let old_note_kind = old_note.kind;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let _ = delete_note_to_note_relationship(&mut tx, &old_parent_id, ¬e_id).await?;
 | 
					        let _ = delete_note_to_note_relationship(&mut tx, &old_parent_id, ¬e_id).await?;
 | 
				
			||||||
		let _ = close_hole_for_deleted_note(&mut tx, &old_parent_id, old_note_location).await?;
 | 
					        let _ = close_hole_for_deleted_note_relationship(&mut tx, &old_parent_id, old_note_location).await?;
 | 
				
			||||||
		let new_location = determine_max_child_location_for_note(&mut tx, &new_parent_id, Some(new_location)).await?;
 | 
					        let new_location = determine_max_child_location_for_note(&mut tx, &new_parent_id, Some(new_location)).await?;
 | 
				
			||||||
		let _ = make_room_for_new_note(&mut tx, &new_parent_id, new_location).await?;
 | 
					        let _ = make_room_for_new_note_relationship(&mut tx, &new_parent_id, new_location).await?;
 | 
				
			||||||
		let _ = insert_note_to_note_relationship(
 | 
					        let _ =
 | 
				
			||||||
			&mut tx,
 | 
					            insert_note_to_note_relationship(&mut tx, &new_parent_id, ¬e_id, new_location, &old_note_kind).await?;
 | 
				
			||||||
			&new_parent_id,
 | 
					        tx.commit().await?;
 | 
				
			||||||
			¬e_id,
 | 
					        Ok(())
 | 
				
			||||||
			new_location,
 | 
					    }
 | 
				
			||||||
			&old_note_kind,
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		.await?;
 | 
					 | 
				
			||||||
		tx.commit().await?;
 | 
					 | 
				
			||||||
		Ok(())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Updates a note's content.  Completely rebuilds the note's
 | 
					    /// Updates a note's content.  Completely rebuilds the note's
 | 
				
			||||||
	/// outgoing edge reference list every time.
 | 
					    /// outgoing edge reference list every time.
 | 
				
			||||||
	pub async fn update_note_content(&self, note_id: &str, content: &str) -> NoteResult<()> {
 | 
					    pub async fn update_note_content(&self, note_id: &str, content: &str) -> NoteResult<()> {
 | 
				
			||||||
		let references = build_references(&content);
 | 
					        let references = build_references(&content);
 | 
				
			||||||
		let note_id = NoteId(note_id.to_string());
 | 
					        let mut tx = self.0.begin().await?;
 | 
				
			||||||
 | 
					        let _ = update_note_content(&mut tx, ¬e_id, &content).await?;
 | 
				
			||||||
 | 
					        let _ = delete_bulk_note_to_page_relationships(&mut tx, ¬e_id).await?;
 | 
				
			||||||
 | 
					        let known_reference_ids = validate_or_generate_all_found_references(&mut tx, &references).await?;
 | 
				
			||||||
 | 
					        let _ = insert_bulk_note_to_page_relationships(&mut tx, ¬e_id, &known_reference_ids).await?;
 | 
				
			||||||
 | 
					        tx.commit().await?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let mut tx = self.0.begin().await?;
 | 
					    /// Deletes a note.  If the note's relationship drops to zero, all
 | 
				
			||||||
		let _ = update_note_content(&mut tx, ¬e_id, &content).await?;
 | 
					    /// references from that note to pages are also deleted.
 | 
				
			||||||
		let _ = delete_bulk_note_to_page_relationships(&mut tx, ¬e_id).await?;
 | 
					    pub async fn delete_note(&self, note_id: &str, note_parent_id: &str) -> NoteResult<()> {
 | 
				
			||||||
		let known_reference_ids = validate_or_generate_all_found_references(&mut tx, &references).await?;
 | 
					        let mut tx = self.0.begin().await?;
 | 
				
			||||||
		let _ = insert_bulk_note_to_page_relationships(&mut tx, ¬e_id, &known_reference_ids)
 | 
					 | 
				
			||||||
			.await?;
 | 
					 | 
				
			||||||
		tx.commit().await?;
 | 
					 | 
				
			||||||
		Ok(())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Deletes a note.  If the note's relationship drops to zero, all
 | 
					        let note_id = note_id.to_string();
 | 
				
			||||||
	/// references from that note to pages are also deleted.
 | 
					        let parent_id = note_parent_id.to_string();
 | 
				
			||||||
	pub async fn delete_note(&self, note_id: &str, note_parent_id: &str) -> NoteResult<()> {
 | 
					 | 
				
			||||||
		let mut tx = self.0.begin().await?;
 | 
					 | 
				
			||||||
		let note_id = NoteId(note_id.to_string());
 | 
					 | 
				
			||||||
		let parent_id = ParentId(note_parent_id.to_string());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if *parent_id != *note_id {
 | 
					        if parent_id != note_id {
 | 
				
			||||||
			let _ = delete_note_to_note_relationship(&mut tx, &parent_id, ¬e_id);
 | 
					            let _ = delete_note_to_note_relationship(&mut tx, &parent_id, ¬e_id);
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
		// The big one: if zero parents report having an interest in this note, then it,
 | 
					        // The big one: if zero parents report having an interest in this note, then it,
 | 
				
			||||||
		// *and any sub-relationships*, go away.
 | 
					        // *and any sub-relationships*, go away.
 | 
				
			||||||
		if count_existing_note_relationships(&mut tx, ¬e_id).await? == 0 {
 | 
					        if count_existing_note_relationships(&mut tx, ¬e_id).await? == 0 {
 | 
				
			||||||
			let _ = delete_note_to_page_relationships(&mut tx, ¬e_id).await?;
 | 
					            let _ = delete_note_to_page_relationships(&mut tx, ¬e_id).await?;
 | 
				
			||||||
			let _ = delete_note(&mut tx, ¬e_id).await?;
 | 
					            let _ = delete_note(&mut tx, ¬e_id).await?;
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
		tx.commit().await?;
 | 
					        tx.commit().await?;
 | 
				
			||||||
		Ok(())
 | 
					        Ok(())
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The Private stuff
 | 
					// The Private stuff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl NoteStore {
 | 
					impl NoteStore {
 | 
				
			||||||
	// Pretty much the most dangerous function in our system. Has to
 | 
					    // Pretty much the most dangerous function in our system. Has to
 | 
				
			||||||
	// have ALL the error checking.
 | 
					    // have ALL the error checking.
 | 
				
			||||||
	async fn insert_note(
 | 
					    async fn insert_note(
 | 
				
			||||||
		&self,
 | 
					        &self,
 | 
				
			||||||
		note: &NewNote,
 | 
					        note: &NewNote,
 | 
				
			||||||
		parent_id: &str,
 | 
					        parent_id: &str,
 | 
				
			||||||
		location: Option<i64>,
 | 
					        location: Option<i64>,
 | 
				
			||||||
		kind: RelationshipKind,
 | 
					        kind: RelationshipKind,
 | 
				
			||||||
	) -> NoteResult<String> {
 | 
					    ) -> NoteResult<String> {
 | 
				
			||||||
		if let Some(location) = location {
 | 
					        if let Some(location) = location {
 | 
				
			||||||
			if location < 0 {
 | 
					            if location < 0 {
 | 
				
			||||||
				return Err(NoteStoreError::InvalidNoteStructure(
 | 
					                return Err(NoteStoreError::InvalidNoteStructure(
 | 
				
			||||||
					"Add note: A negative location is not valid.".to_string(),
 | 
					                    "Add note: A negative location is not valid.".to_string(),
 | 
				
			||||||
				));
 | 
					                ));
 | 
				
			||||||
			}
 | 
					            }
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if parent_id.is_empty() {
 | 
					        if parent_id.is_empty() {
 | 
				
			||||||
			return Err(NoteStoreError::InvalidNoteStructure(
 | 
					            return Err(NoteStoreError::InvalidNoteStructure(
 | 
				
			||||||
				"Add note: A parent note ID is required.".to_string(),
 | 
					                "Add note: A parent note ID is required.".to_string(),
 | 
				
			||||||
			));
 | 
					            ));
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if note.id.is_empty() {
 | 
					        if note.id.is_empty() {
 | 
				
			||||||
			return Err(NoteStoreError::InvalidNoteStructure(
 | 
					            return Err(NoteStoreError::InvalidNoteStructure(
 | 
				
			||||||
				"Add note: Your note should have an id already".to_string(),
 | 
					                "Add note: Your note should have an id already".to_string(),
 | 
				
			||||||
			));
 | 
					            ));
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if note.content.is_empty() {
 | 
					        if note.content.is_empty() {
 | 
				
			||||||
			return Err(NoteStoreError::InvalidNoteStructure(
 | 
					            return Err(NoteStoreError::InvalidNoteStructure(
 | 
				
			||||||
				"Add note: Empty notes are not supported.".to_string(),
 | 
					                "Add note: Empty notes are not supported.".to_string(),
 | 
				
			||||||
			));
 | 
					            ));
 | 
				
			||||||
		}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let references = build_references(¬e.content);
 | 
					        let references = build_references(¬e.content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let mut tx = self.0.begin().await?;
 | 
					        let mut tx = self.0.begin().await?;
 | 
				
			||||||
		let location = determine_max_child_location_for_note(&mut tx, parent_id, location).await?;
 | 
					        let location = determine_max_child_location_for_note(&mut tx, parent_id, location).await?;
 | 
				
			||||||
		let note_id = NoteId(note.id.clone());
 | 
					        insert_note(&mut tx, ¬e).await?;
 | 
				
			||||||
		insert_note(&mut tx, ¬e).await?;
 | 
					        make_room_for_new_note_relationship(&mut tx, &parent_id, location).await?;
 | 
				
			||||||
		make_room_for_new_note(&mut tx, &parent_id, location).await?;
 | 
					        insert_note_to_note_relationship(&mut tx, &parent_id, ¬e.id, location, &kind).await?;
 | 
				
			||||||
		insert_note_to_note_relationship(&mut tx, &parent_id, ¬e_id, location, &kind).await?;
 | 
					        let known_reference_ids = validate_or_generate_all_found_references(&mut tx, &references).await?;
 | 
				
			||||||
		let known_reference_ids = validate_or_generate_all_found_references(&mut tx, &references).await?;
 | 
					        let _ = insert_bulk_note_to_page_relationships(&mut tx, ¬e.id, &known_reference_ids).await?;
 | 
				
			||||||
		let _ = insert_bulk_note_to_page_relationships(&mut tx, ¬e_id, &known_reference_ids)
 | 
					        tx.commit().await?;
 | 
				
			||||||
			.await?;
 | 
					        Ok(note.id.to_string())
 | 
				
			||||||
		tx.commit().await?;
 | 
					    }
 | 
				
			||||||
		Ok(note_id.to_string())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue