This commit is contained in:
Sam Samai 2021-08-21 21:58:20 +10:00
parent 73b72e3a31
commit c6480635c0
6 changed files with 216 additions and 109 deletions

View File

@ -5,15 +5,17 @@ edition = "2018"
publish = false publish = false
[workspace] [workspace]
[dependencies] [dependencies]
# rocket = { path = "../../../Rocket/core/lib", features = ["json"] } rocket = { path = "../../../Rocket/core/lib", features = ["json"] }
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [ # rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [
"json", # "json",
] } # ] }
# async-std = { version = "^1.9", features = ["attributes"] } # async-std = { version = "^1.9", features = ["attributes"] }
sea-orm = { path = "../../", features = [ sea-orm = { path = "../../", features = [
"sqlx-all", "sqlx-all",
# "runtime-async-std-native-tls", # "runtime-async-std-native-tls",
] } ] }
sea-query = { version = "^0.12.8" }
serde_json = { version = "^1" } serde_json = { version = "^1" }
futures = { version = "^0.3" } futures = { version = "^0.3" }
async-stream = { version = "^0.3" } async-stream = { version = "^0.3" }
@ -24,7 +26,10 @@ version = "0.5.1"
default-features = false default-features = false
features = ["macros", "offline", "migrate"] features = ["macros", "offline", "migrate"]
# [dependencies.rocket_db_pools]
# git = "https://github.com/SergioBenitez/Rocket"
# branch = "master"
# features = ["sea_orm"]
[dependencies.rocket_db_pools] [dependencies.rocket_db_pools]
git = "https://github.com/SergioBenitez/Rocket" path = "../../../Rocket/contrib/db_pools/lib"
branch = "master"
features = ["sqlx_sqlite"] features = ["sqlx_sqlite"]

View File

@ -1,2 +1,5 @@
[default.databases.sqlx] # [default.databases.sqlx]
url = "db/sqlx/db.sqlite" # url = "sqlite::memory:"
[global.databases]
blog = { url = "sqlite::memory:" }

View File

@ -6,17 +6,21 @@ use rocket::{futures, Build, Rocket};
use rocket_db_pools::{sqlx, Connection, Database}; use rocket_db_pools::{sqlx, Connection, Database};
use futures::{future::TryFutureExt, stream::TryStreamExt}; use futures::{future::TryFutureExt, stream::TryStreamExt};
use sea_orm::entity::*;
use sea_orm::QueryFilter;
mod setup;
#[derive(Database, Debug)]
#[database("blog")]
struct Db(rocket_db_pools::sqlx::SqlitePool);
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
// use post::*; // use post::*;
mod post; mod post;
pub use post::Entity as Post; pub use post::Entity as Post;
use sea_orm::DatabaseConnection;
#[derive(Database)]
#[database("sqlx")]
struct Db(sqlx::SqlitePool);
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
// #[derive(Debug, Clone, Deserialize, Serialize)] // #[derive(Debug, Clone, Deserialize, Serialize)]
// #[serde(crate = "rocket::serde")] // #[serde(crate = "rocket::serde")]
// struct Post { // struct Post {
@ -47,17 +51,58 @@ async fn list(mut db: Connection<Db>) -> Result<Json<Vec<i64>>> {
// .map_ok(|record| record.id) // .map_ok(|record| record.id)
// .try_collect::<Vec<_>>() // .try_collect::<Vec<_>>()
// .await?; // .await?;
let ids = vec![]; // // let ids: Vec<i64> = vec![];
// let ids = sqlx::query(
// r#"
// SELECT id FROM posts
// "#,
// )
// .execute(&mut *db)
// .await?;
// // .map_ok(|record| record.id);
// // .try_collect::<Vec<_>>();
// println!("ids: {:#?}", ids);
// let ids: Vec<i64> = vec![];
// Ok(Json(ids))
// let mut conn = db.acquire().await?;
// println!("conn: {:#?}", conn);
// let ids = sqlx::query("SELECT id FROM posts")
// .fetch(&mut *db)
// .map_ok(|record| record.id)
// .try_collect::<Vec<_>>()
// .await?;
// Ok(Json(ids))
// let recs = sqlx::query(
// r#"
// SELECT id FROM posts
// "#,
// )
// .fetch_all(&mut *db)
// .await?;
// let ids: Vec<i64> = recs.into();
// println!("recs: {:#?}", ids);
let posts = Post::find().all(&mut *db).await.unwrap();
assert_eq!(posts.len(), 0);
let ids: Vec<i64> = vec![];
Ok(Json(ids)) Ok(Json(ids))
} }
#[get("/<id>")] #[get("/<id>")]
async fn read(mut db: Connection<Db>, id: i64) -> Option<Json<Post>> { async fn read(mut db: Connection<Db>, id: i64) -> Option<Json<Post>> {
let post: Option<post::Model> = Post::find_by_id(id) // let post: Option<post::Model> = Post::find_by_id(id)
.one(db) // .one(db)
.await // .await
.expect("could not find baker"); // .expect("could not find baker");
println!("post: {:#?}", post); // println!("post: {:#?}", post);
// sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id) // sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id)
// .fetch_one(&mut *db) // .fetch_one(&mut *db)
@ -90,24 +135,105 @@ async fn read(mut db: Connection<Db>, id: i64) -> Option<Json<Post>> {
// Ok(()) // Ok(())
// } // }
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result { // async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
match Db::fetch(&rocket) { // use crate::rocket_db_pools::Pool;
Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { // // match Db::fetch(&rocket) {
Ok(_) => Ok(rocket), // // Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await {
Err(e) => { // // Ok(_) => Ok(rocket),
error!("Failed to initialize SQLx database: {}", e); // // Err(e) => {
Err(rocket) // // error!("Failed to initialize SQLx database: {}", e);
} // // Err(rocket)
}, // // }
None => Err(rocket), // // },
} // // None => Err(rocket),
} // // }
// // let conn = Db::get(&rocket).await.expect("database connection");
// match Db::fetch(&rocket) {
// Some(db) => match setup::create_post_table(db.get().await().expect("database connection")).await {
// Ok(_) => {
// println!("rocket: {:#?}", rocket);
// Ok(rocket)
// }
// Err(e) => {
// error!("Failed to initialize SQLx database: {}", e);
// Err(rocket)
// }
// },
// None => Err(rocket),
// }
// // Ok(rocket)
// }
pub fn stage() -> AdHoc { pub fn stage() -> AdHoc {
AdHoc::on_ignite("SQLx Stage", |rocket| async { AdHoc::on_ignite("SQLx Stage", |rocket| async {
rocket rocket
.attach(Db::init()) .attach(Db::init())
.attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) .attach(AdHoc::try_on_ignite("Create init post", |rocket| async {
let db = Db::fetch(&rocket).expect("database mounted");
let res = sqlx::query(
r#"
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR NOT NULL,
text VARCHAR NOT NULL,
published BOOLEAN NOT NULL DEFAULT 0
)"#,
)
.execute(&**db)
.await;
println!("res: {:#?}", res);
let res2 = sqlx::query(
r#"
INSERT INTO posts (title, text) VALUES ('a post', 'content of a post')
"#,
)
.execute(&**db)
.await;
println!("res2: {:#?}", res2);
// Db::fetch(&rocket)
// .run(|db| {
// sqlx::query("DELETE FROM table").execute(&pool).await;
// // conn.execute(
// // r#"
// // CREATE TABLE posts (
// // id INTEGER PRIMARY KEY AUTOINCREMENT,
// // title VARCHAR NOT NULL,
// // text VARCHAR NOT NULL,
// // published BOOLEAN NOT NULL DEFAULT 0
// // )"#,
// // params![],
// // )
// })
// .await
// .expect("can init rusqlite DB");
Ok(rocket)
// match Db::fetch(&rocket) {
// Some(db) => {
// println!("db: {:#?}", db);
// println!("&**db: {:#?}", &**db);
// Ok(rocket)
// }
// None => Err(rocket),
// }
}))
.mount("/sqlx", routes![list, read]) .mount("/sqlx", routes![list, read])
}) })
} }
// pub async fn create_post(db: &DbConn) {
// let post = post::ActiveModel {
// title: Set("Post One".to_owned()),
// text: Set("post content 1".to_owned()),
// ..Default::default()
// }
// .save(db)
// .await
// .expect("could not insert post");
// }

View File

@ -1,7 +1,8 @@
use rocket::serde::{json::Json, Deserialize, Serialize}; use rocket::serde::{json::Json, Deserialize, Serialize};
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)] #[derive(Copy, Clone, Default, Debug, DeriveEntity, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Entity; pub struct Entity;
impl EntityName for Entity { impl EntityName for Entity {
@ -13,6 +14,7 @@ impl EntityName for Entity {
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct Model { pub struct Model {
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
pub id: Option<i64>, pub id: Option<i64>,
pub title: String, pub title: String,
pub text: String, pub text: String,
@ -36,6 +38,9 @@ impl PrimaryKeyTrait for PrimaryKey {
} }
} }
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl ColumnTrait for Column { impl ColumnTrait for Column {
type EntityName = Entity; type EntityName = Entity;
@ -48,4 +53,9 @@ impl ColumnTrait for Column {
} }
} }
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,37 @@
use sea_orm::{error::*, sea_query, DbConn, ExecResult};
use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement};
// mod post;
pub use super::post::*;
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> {
let builder = db.get_database_backend();
db.execute(builder.build(stmt)).await
}
pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
.table(super::post::Entity)
.if_not_exists()
.col(
ColumnDef::new(super::post::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(super::post::Column::Title)
.string()
.not_null(),
)
.col(
ColumnDef::new(super::post::Column::Text)
.string()
.not_null(),
)
.to_owned();
create_table(db, &stmt).await
}

View File

@ -1,74 +0,0 @@
use rocket::fairing::AdHoc;
use rocket::http::Status;
use rocket::local::blocking::Client;
use rocket::serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Post {
title: String,
text: String,
}
fn test(base: &str, stage: AdHoc) {
// Number of posts we're going to create/read/delete.
const N: usize = 20;
// NOTE: If we had more than one test running concurently that dispatches
// DB-accessing requests, we'd need transactions or to serialize all tests.
let client = Client::tracked(rocket::build().attach(stage)).unwrap();
// Clear everything from the database.
assert_eq!(client.delete(base).dispatch().status(), Status::Ok);
assert_eq!(
client.get(base).dispatch().into_json::<Vec<i64>>(),
Some(vec![])
);
// Add some random posts, ensure they're listable and readable.
for i in 1..=N {
let title = format!("My Post - {}", i);
let text = format!("Once upon a time, at {}'o clock...", i);
let post = Post {
title: title.clone(),
text: text.clone(),
};
// Create a new post.
let response = client.post(base).json(&post).dispatch().into_json::<Post>();
assert_eq!(response.unwrap(), post);
// Ensure the index shows one more post.
let list = client.get(base).dispatch().into_json::<Vec<i64>>().unwrap();
assert_eq!(list.len(), i);
// The last in the index is the new one; ensure contents match.
let last = list.last().unwrap();
let response = client.get(format!("{}/{}", base, last)).dispatch();
assert_eq!(response.into_json::<Post>().unwrap(), post);
}
// Now delete all of the posts.
for _ in 1..=N {
// Get a valid ID from the index.
let list = client.get(base).dispatch().into_json::<Vec<i64>>().unwrap();
let id = list.get(0).expect("have post");
// Delete that post.
let response = client.delete(format!("{}/{}", base, id)).dispatch();
assert_eq!(response.status(), Status::Ok);
}
// Ensure they're all gone.
let list = client.get(base).dispatch().into_json::<Vec<i64>>().unwrap();
assert!(list.is_empty());
// Trying to delete should now 404.
let response = client.delete(format!("{}/{}", base, 1)).dispatch();
assert_eq!(response.status(), Status::NotFound);
}
#[test]
fn test_sqlx() {
test("/sqlx", crate::sqlx::stage())
}