FEAT Can initialize the database and fail to retrieve a page.
In the great tradition of TPP, this is a win. We've gone through the test driven development, and there is so much *learning* here: - tokio::test NEEDS the threaded_schedular feature to report errors correctly - thiserror can't do enum variants the way I expected - Different error types for different returns is not kosher - Serde's configuration NEEDS a type, such as JSON, to work, - Rust has include_str!(), to embed text in a Rust program from an external source - SQLX is still a pain, but it's managable.
This commit is contained in:
parent
2bd7c0aaad
commit
8ee71c76a3
|
@ -6,11 +6,19 @@ edition = "2018"
|
||||||
description = "Datastore interface layer for notesmachine."
|
description = "Datastore interface layer for notesmachine."
|
||||||
readme = "./README.org"
|
readme = "./README.org"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = ["nm-store-cli"]
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nm-store-cli = { path: "../nm-store-cli" }
|
thiserror = "1.0.20"
|
||||||
|
tokio = { version = "0.2.22", features = ["rt-threaded"] }
|
||||||
|
serde = { version = "1.0.116", features = ["derive"] }
|
||||||
|
serde_json = "1.0.56"
|
||||||
|
sqlx = { version = "0.4.0-beta.1", default-features = false, features = [
|
||||||
|
"runtime-tokio",
|
||||||
|
"sqlite",
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
use sqlx;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// All the ways looking up objects can fail
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum NoteStoreError {
|
||||||
|
/// When building a new note for the back-end, it failed to parse
|
||||||
|
/// in some critical way.
|
||||||
|
#[error("Invalid Note Structure")]
|
||||||
|
InvalidNoteStructure(String),
|
||||||
|
|
||||||
|
/// All other errors from the database.
|
||||||
|
#[error(transparent)]
|
||||||
|
DBError(#[from] sqlx::Error),
|
||||||
|
}
|
|
@ -1,7 +1,23 @@
|
||||||
|
mod errors;
|
||||||
|
mod store;
|
||||||
|
mod structs;
|
||||||
|
|
||||||
|
pub use crate::store::NoteStore;
|
||||||
|
pub use crate::errors::NoteStoreError;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
use super::*;
|
||||||
fn it_works() {
|
use tokio;
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
|
#[tokio::test(threaded_scheduler)]
|
||||||
|
async fn it_works() {
|
||||||
|
let storagepool = NoteStore::new("sqlite://:memory:").await;
|
||||||
|
assert!(storagepool.is_ok());
|
||||||
|
let storagepool = storagepool.unwrap();
|
||||||
|
assert!(storagepool.reset_database().await.is_ok());
|
||||||
|
let unfoundpage = storagepool.fetch_page("nonexistent-page").await;
|
||||||
|
assert!(unfoundpage.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
DROP TABLE IF EXISTS notes;
|
||||||
|
DROP TABLE IF EXISTS note_relationships;
|
||||||
|
DROP TABLE IF EXISTS pages;
|
||||||
|
DROP TABLE IF EXISTS favorites;
|
||||||
|
DROP TABLE IF EXISTS page_relationships;
|
||||||
|
|
||||||
|
CREATE TABLE notes (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
uuid TEXT NOT NULL UNIQUE,
|
||||||
|
content TEXT NULL,
|
||||||
|
notetype TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX notes_uuids ON notes (uuid);
|
||||||
|
|
||||||
|
CREATE TABLE pages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title text NOT NULL UNIQUE,
|
||||||
|
slug text NOT NULL UNIQUE,
|
||||||
|
note_id INTEGER,
|
||||||
|
FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX pages_slugs ON pages (slug);
|
||||||
|
|
||||||
|
CREATE TABLE favorites (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
position INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE note_relationships (
|
||||||
|
note_id INTEGER NOT NULL,
|
||||||
|
parent_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
nature TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
|
FOREIGN KEY (parent_id) REFERENCES notes (id) ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE page_relationships (
|
||||||
|
note_id INTEGER NOT NULL,
|
||||||
|
page_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (note_id) references notes (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
|
FOREIGN KEY (page_id) references pages (id) ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
|
);
|
|
@ -0,0 +1 @@
|
||||||
|
SELECT id, title, slug, note_id FROM pages WHERE slug=?;
|
|
@ -0,0 +1,37 @@
|
||||||
|
use sqlx;
|
||||||
|
use sqlx::sqlite::SqlitePool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use crate::errors::NoteStoreError;
|
||||||
|
use crate::structs::RawPage;
|
||||||
|
|
||||||
|
/// A handle to our Sqlite database.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NoteStore(Arc<SqlitePool>);
|
||||||
|
|
||||||
|
type NoteResult<T> = core::result::Result<T, NoteStoreError>;
|
||||||
|
type SqlResult<T> = sqlx::Result<T>;
|
||||||
|
|
||||||
|
impl NoteStore {
|
||||||
|
pub async fn new(url: &str) -> NoteResult<Self> {
|
||||||
|
let pool = SqlitePool::connect(url).await?;
|
||||||
|
Ok(NoteStore(Arc::new(pool)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This will erase all the data in the database. Only use this
|
||||||
|
/// if you're sure that's what you want.
|
||||||
|
pub async fn reset_database(&self) -> NoteResult<()> {
|
||||||
|
let initialize_sql = include_str!("sql/initialize_database.sql");
|
||||||
|
sqlx::query(initialize_sql)
|
||||||
|
.execute(&*self.0)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_page(&self, id: &str) -> SqlResult<RawPage> {
|
||||||
|
let select_one_page_sql = include_str!("sql/select_one_page.sql");
|
||||||
|
sqlx::query_as(select_one_page_sql)
|
||||||
|
.bind(&id)
|
||||||
|
.fetch_one(&*self.0)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{self, FromRow};
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug, FromRow)]
|
||||||
|
pub struct RawPage {
|
||||||
|
id: i64,
|
||||||
|
slug: String,
|
||||||
|
title: String,
|
||||||
|
note_id: i64,
|
||||||
|
}
|
Loading…
Reference in New Issue