Browse Source

Rust Axum using sqlx and bb8 refactorings

Dragos Varovici 3 years ago
parent
commit
ac4a061f36

+ 22 - 0
frameworks/Rust/axum/Cargo.lock

@@ -133,6 +133,8 @@ dependencies = [
  "serde_json",
  "serde_json",
  "sqlx",
  "sqlx",
  "tokio",
  "tokio",
+ "tokio-pg-mapper",
+ "tokio-pg-mapper-derive",
  "tokio-postgres",
  "tokio-postgres",
  "tower",
  "tower",
  "tower-http",
  "tower-http",
@@ -2038,6 +2040,26 @@ dependencies = [
  "tokio",
  "tokio",
 ]
 ]
 
 
+[[package]]
+name = "tokio-pg-mapper"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f2b78f3566383ffabc553c72bbb2f129962a54886c5c4d8e8c706f84eceab8"
+dependencies = [
+ "tokio-postgres",
+]
+
+[[package]]
+name = "tokio-pg-mapper-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8548f756cd6eb4069c5af0fb0cec57001fb42bd1fb7330d8f24067ee3fa62608"
+dependencies = [
+ "quote",
+ "syn",
+ "tokio-postgres",
+]
+
 [[package]]
 [[package]]
 name = "tokio-postgres"
 name = "tokio-postgres"
 version = "0.7.5"
 version = "0.7.5"

+ 2 - 0
frameworks/Rust/axum/Cargo.toml

@@ -36,6 +36,8 @@ sqlx = { version = "^0.5", features = [ "postgres", "macros", "runtime-tokio-nat
 bb8 = "0.7"
 bb8 = "0.7"
 bb8-postgres = "0.7"
 bb8-postgres = "0.7"
 tokio-postgres = "0.7"
 tokio-postgres = "0.7"
+tokio-pg-mapper = "0.2"
+tokio-pg-mapper-derive = "0.2"
 
 
 [profile.release]
 [profile.release]
 lto = true
 lto = true

+ 2 - 0
frameworks/Rust/axum/benchmark_config.json

@@ -53,6 +53,8 @@
         "plaintext_url": "/plaintext",
         "plaintext_url": "/plaintext",
         "db_url": "/db",
         "db_url": "/db",
         "query_url": "/queries?queries=",
         "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
         "port": 8000,
         "port": 8000,
         "approach": "Realistic",
         "approach": "Realistic",
         "classification": "Fullstack",
         "classification": "Fullstack",

+ 17 - 0
frameworks/Rust/axum/src/common_handlers.rs

@@ -0,0 +1,17 @@
+use axum::http::StatusCode;
+use axum::Json;
+use axum::response::IntoResponse;
+
+use crate::models_common::{Message};
+
+pub async fn plaintext() -> &'static str {
+    "Hello, World!"
+}
+
+pub async fn json() -> impl IntoResponse {
+    let message = Message {
+        message: "Hello, World!",
+    };
+
+    (StatusCode::OK, Json(message))
+}

+ 1 - 1
frameworks/Rust/axum/src/database_bb8.rs

@@ -3,7 +3,7 @@ use axum::http::StatusCode;
 use bb8::{Pool, PooledConnection};
 use bb8::{Pool, PooledConnection};
 use bb8_postgres::PostgresConnectionManager;
 use bb8_postgres::PostgresConnectionManager;
 use bb8_postgres::tokio_postgres::NoTls;
 use bb8_postgres::tokio_postgres::NoTls;
-use crate::common::internal_error;
+use crate::utils::internal_error;
 
 
 pub type ConnectionManager = PostgresConnectionManager<NoTls>;
 pub type ConnectionManager = PostgresConnectionManager<NoTls>;
 pub type ConnectionPool = Pool<ConnectionManager>;
 pub type ConnectionPool = Pool<ConnectionManager>;

+ 1 - 1
frameworks/Rust/axum/src/database_sqlx.rs

@@ -4,7 +4,7 @@ use axum::http::StatusCode;
 use sqlx::{PgPool, Postgres};
 use sqlx::{PgPool, Postgres};
 use sqlx::pool::PoolConnection;
 use sqlx::pool::PoolConnection;
 use sqlx::postgres::PgPoolOptions;
 use sqlx::postgres::PgPoolOptions;
-use crate::common::internal_error;
+use crate::utils::internal_error;
 
 
 pub async fn create_pool(database_url: String) -> PgPool {
 pub async fn create_pool(database_url: String) -> PgPool {
     PgPoolOptions::new().max_connections(100).connect(&*database_url).await.unwrap()
     PgPoolOptions::new().max_connections(100).connect(&*database_url).await.unwrap()

+ 5 - 6
frameworks/Rust/axum/src/main.rs

@@ -1,11 +1,11 @@
 extern crate serde_derive;
 extern crate serde_derive;
 extern crate dotenv;
 extern crate dotenv;
-#[macro_use]
 extern crate async_trait;
 extern crate async_trait;
+extern crate tokio_pg_mapper_derive;
+extern crate tokio_pg_mapper;
 
 
-mod models;
-mod database_sqlx;
-mod common;
+mod common_handlers;
+mod models_common;
 
 
 use dotenv::dotenv;
 use dotenv::dotenv;
 use std::net::{Ipv4Addr, SocketAddr};
 use std::net::{Ipv4Addr, SocketAddr};
@@ -14,7 +14,7 @@ use axum::http::{header, HeaderValue};
 use tower_http::set_header::SetResponseHeaderLayer;
 use tower_http::set_header::SetResponseHeaderLayer;
 use hyper::Body;
 use hyper::Body;
 
 
-use crate::common::{json, plaintext};
+use common_handlers::{json, plaintext};
 
 
 #[tokio::main]
 #[tokio::main]
 async fn main() {
 async fn main() {
@@ -36,4 +36,3 @@ async fn router() -> Router {
         .route("/json", get(json))
         .route("/json", get(json))
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")))
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")))
 }
 }
-

+ 83 - 62
frameworks/Rust/axum/src/main_bb8.rs

@@ -3,9 +3,11 @@ extern crate dotenv;
 #[macro_use]
 #[macro_use]
 extern crate async_trait;
 extern crate async_trait;
 
 
-mod models;
+mod common_handlers;
+mod models_common;
+mod models_bb8;
 mod database_bb8;
 mod database_bb8;
-mod common;
+mod utils;
 
 
 use dotenv::dotenv;
 use dotenv::dotenv;
 use std::net::{Ipv4Addr, SocketAddr};
 use std::net::{Ipv4Addr, SocketAddr};
@@ -24,10 +26,13 @@ use tower_http::set_header::SetResponseHeaderLayer;
 use hyper::Body;
 use hyper::Body;
 use rand::rngs::SmallRng;
 use rand::rngs::SmallRng;
 use rand::{SeedableRng};
 use rand::{SeedableRng};
-use yarte::TemplateTrait;
+use tokio_pg_mapper::FromTokioPostgresRow;
+use yarte::Template;
 
 
-use models::{World, Fortune};
-use crate::common::{FortunesTemplate, internal_error, json, Params, parse_params, plaintext, random_number, Utf8Html};
+use models_bb8::{World, Fortune};
+use common_handlers::{json, plaintext};
+use utils::{Params, parse_params, random_number};
+use crate::utils::Utf8Html;
 
 
 async fn db(DatabaseConnection(conn): DatabaseConnection) -> impl IntoResponse {
 async fn db(DatabaseConnection(conn): DatabaseConnection) -> impl IntoResponse {
     let mut rng = SmallRng::from_entropy();
     let mut rng = SmallRng::from_entropy();
@@ -39,18 +44,10 @@ async fn db(DatabaseConnection(conn): DatabaseConnection) -> impl IntoResponse {
     (StatusCode::OK, Json(world))
     (StatusCode::OK, Json(world))
 }
 }
 
 
-async fn prepare_fetch_world_by_id_statement(conn: &Connection) -> Statement {
-    conn.prepare("SELECT id, randomnumber FROM World WHERE id = $1").await.unwrap()
-}
-
 async fn fetch_world_by_id_using_statement(conn: &Connection, number: i32, select: &Statement) -> World {
 async fn fetch_world_by_id_using_statement(conn: &Connection, number: i32, select: &Statement) -> World {
     let row: Row = conn.query_one(select, &[&number]).await.unwrap();
     let row: Row = conn.query_one(select, &[&number]).await.unwrap();
 
 
-    let id: i32 = row.get(0);
-    let random_num: i32 = row.get(1);
-
-    let world: World = World { id, random_number: random_num };
-    world
+    World::from_row(row).unwrap()
 }
 }
 
 
 async fn queries(DatabaseConnection(conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
 async fn queries(DatabaseConnection(conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
@@ -72,52 +69,71 @@ async fn queries(DatabaseConnection(conn): DatabaseConnection, Query(params): Qu
 
 
     (StatusCode::OK, Json(results))
     (StatusCode::OK, Json(results))
 }
 }
-//
-// async fn fortunes(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
-//     let mut fortunes: Vec<Fortune> = sqlx::query_as("SELECT * FROM Fortune").fetch_all(&mut conn).await
-//         .ok().expect("Could not load Fortunes");
-//
-//     fortunes.push(Fortune {
-//         id: 0,
-//         message: "Additional fortune added at request time.".to_string(),
-//     });
-//
-//     fortunes.sort_by(|a, b| a.message.cmp(&b.message));
-//
-//     Utf8Html(
-//         FortunesTemplate {
-//             fortunes: &fortunes,
-//         }
-//         .call()
-//         .expect("error rendering template"),
-//     )
-// }
-//
-// async fn updates(DatabaseConnection(mut conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
-//     let q = parse_params(params);
-//
-//     let mut rng = SmallRng::from_entropy();
-//
-//     let mut results = Vec::with_capacity(q as usize);
-//
-//     for _ in 0..q {
-//         let query_id = random_number(&mut rng);
-//         let mut result :World =  sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
-//             .fetch_one(&mut conn).await.ok().expect("error loading world");
-//
-//         result.random_number = random_number(&mut rng);
-//         results.push(result);
-//     }
-//
-//     for w in &results {
-//         sqlx::query("UPDATE World SET randomnumber = $1 WHERE id = $2")
-//             .bind(w.random_number).bind(w.id)
-//             .execute(&mut conn)
-//             .await.ok().expect("could not update world");
-//     }
-//
-//     (StatusCode::OK, Json(results))
-// }
+
+async fn fortunes(DatabaseConnection(conn): DatabaseConnection) -> impl IntoResponse {
+    let select = prepare_fetch_all_fortunes_statement(&conn).await;
+
+    let rows: Vec<Row> = conn.query(&select, &[]).await.unwrap();
+
+    let mut fortunes: Vec<Fortune> = Vec::with_capacity(rows.capacity());;
+
+    for row in rows {
+        fortunes.push(Fortune::from_row(row).unwrap());
+    }
+
+    fortunes.push(Fortune {
+        id: 0,
+        message: "Additional fortune added at request time.".to_string(),
+    });
+
+    fortunes.sort_by(|a, b| a.message.cmp(&b.message));
+
+    Utf8Html(
+        FortunesTemplate {
+            fortunes: &fortunes,
+        }
+        .call()
+        .expect("error rendering template"),
+    )
+}
+
+async fn updates(DatabaseConnection(conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
+    let q = parse_params(params);
+
+    let mut rng = SmallRng::from_entropy();
+
+    let mut results = Vec::with_capacity(q as usize);
+
+    let select = prepare_fetch_world_by_id_statement(&conn).await;
+
+    for _ in 0..q {
+        let query_id = random_number(&mut rng);
+        let mut result :World = fetch_world_by_id_using_statement(&conn, query_id, &select).await;
+
+        result.randomnumber = random_number(&mut rng);
+        results.push(result);
+    }
+
+    let update = prepare_update_world_by_id_statement(&conn).await;
+
+    for w in &results {
+        conn.execute(&update, &[&w.randomnumber, &w.id]).await.unwrap();
+    }
+
+    (StatusCode::OK, Json(results))
+}
+
+async fn prepare_fetch_all_fortunes_statement(conn: &Connection) -> Statement {
+    conn.prepare("SELECT * FROM Fortune").await.unwrap()
+}
+
+async fn prepare_fetch_world_by_id_statement(conn: &Connection) -> Statement {
+    conn.prepare("SELECT id, randomnumber FROM World WHERE id = $1").await.unwrap()
+}
+
+async fn prepare_update_world_by_id_statement(conn: &Connection) -> Statement {
+    conn.prepare("UPDATE World SET randomnumber = $1 WHERE id = $2").await.unwrap()
+}
 
 
 #[tokio::main]
 #[tokio::main]
 async fn main() {
 async fn main() {
@@ -134,10 +150,10 @@ async fn main() {
     let router = Router::new()
     let router = Router::new()
         .route("/plaintext", get(plaintext))
         .route("/plaintext", get(plaintext))
         .route("/json", get(json))
         .route("/json", get(json))
-        // .route("/fortunes", get(fortunes))
+        .route("/fortunes", get(fortunes))
         .route("/db", get(db))
         .route("/db", get(db))
         .route("/queries", get(queries))
         .route("/queries", get(queries))
-        // .route("/updates", get(updates))
+        .route("/updates", get(updates))
         .layer(AddExtensionLayer::new(pool))
         .layer(AddExtensionLayer::new(pool))
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")));
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")));
 
 
@@ -147,3 +163,8 @@ async fn main() {
         .unwrap();
         .unwrap();
 }
 }
 
 
+#[derive(Template)]
+#[template(path = "fortunes.html.hbs")]
+pub struct FortunesTemplate<'a> {
+    pub fortunes: &'a Vec<Fortune>,
+}

+ 13 - 5
frameworks/Rust/axum/src/main_sqlx.rs

@@ -3,9 +3,11 @@ extern crate dotenv;
 #[macro_use]
 #[macro_use]
 extern crate async_trait;
 extern crate async_trait;
 
 
-mod models;
+mod common_handlers;
+mod models_common;
+mod models_sqlx;
 mod database_sqlx;
 mod database_sqlx;
-mod common;
+mod utils;
 
 
 use dotenv::dotenv;
 use dotenv::dotenv;
 use std::net::{Ipv4Addr, SocketAddr};
 use std::net::{Ipv4Addr, SocketAddr};
@@ -24,11 +26,12 @@ use hyper::Body;
 use rand::rngs::SmallRng;
 use rand::rngs::SmallRng;
 use rand::{SeedableRng};
 use rand::{SeedableRng};
 use sqlx::PgPool;
 use sqlx::PgPool;
-use yarte::TemplateTrait;
+use yarte::Template;
 
 
-use models::{World, Fortune};
+use models_sqlx::{World, Fortune};
 use database_sqlx::create_pool;
 use database_sqlx::create_pool;
-use crate::common::{FortunesTemplate, json, Params, parse_params, plaintext, random_number, Utf8Html};
+use common_handlers::{json, plaintext};
+use utils::{Params, parse_params, random_number, Utf8Html};
 
 
 async fn db(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
 async fn db(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
     let mut rng = SmallRng::from_entropy();
     let mut rng = SmallRng::from_entropy();
@@ -137,3 +140,8 @@ async fn router(pool: PgPool) -> Router {
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")))
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")))
 }
 }
 
 
+#[derive(Template)]
+#[template(path = "fortunes.html.hbs")]
+pub struct FortunesTemplate<'a> {
+    pub fortunes: &'a Vec<Fortune>,
+}

+ 22 - 0
frameworks/Rust/axum/src/models_bb8.rs

@@ -0,0 +1,22 @@
+use serde::{Deserialize, Serialize};
+use tokio_pg_mapper_derive::PostgresMapper;
+
+#[allow(non_snake_case)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, PostgresMapper)]
+#[pg_mapper(table = "Fortune")]
+pub struct Fortune {
+    pub id: i32,
+    pub message: String
+}
+
+#[allow(non_snake_case)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, PostgresMapper)]
+#[pg_mapper(table = "World")]
+pub struct World {
+    pub id: i32,
+    #[serde(rename = "randomNumber")]
+    pub randomnumber: i32
+}
+
+
+

+ 6 - 0
frameworks/Rust/axum/src/models_common.rs

@@ -0,0 +1,6 @@
+use serde::{Serialize};
+
+#[derive(Serialize)]
+pub struct Message {
+    pub message: &'static str,
+}

+ 0 - 7
frameworks/Rust/axum/src/models.rs → frameworks/Rust/axum/src/models_sqlx.rs

@@ -1,19 +1,12 @@
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 use sqlx::FromRow;
 use sqlx::FromRow;
 
 
-#[derive(Serialize)]
-pub struct Message {
-    pub message: &'static str,
-}
-
-#[allow(non_snake_case)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
 pub struct Fortune {
 pub struct Fortune {
     pub id: i32,
     pub id: i32,
     pub message: String
     pub message: String
 }
 }
 
 
-#[allow(non_snake_case)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
 pub struct World {
 pub struct World {
     pub id: i32,
     pub id: i32,

+ 0 - 22
frameworks/Rust/axum/src/common.rs → frameworks/Rust/axum/src/utils.rs

@@ -1,38 +1,16 @@
 use std::convert::Infallible;
 use std::convert::Infallible;
 use axum::body::{Bytes, Full};
 use axum::body::{Bytes, Full};
 use axum::http::{header, HeaderValue, Response, StatusCode};
 use axum::http::{header, HeaderValue, Response, StatusCode};
-use axum::Json;
 use axum::response::IntoResponse;
 use axum::response::IntoResponse;
 use rand::Rng;
 use rand::Rng;
 use rand::rngs::SmallRng;
 use rand::rngs::SmallRng;
 use serde::{Deserialize};
 use serde::{Deserialize};
-use yarte::Template;
-
-use crate::models::{Fortune, Message};
 
 
 #[derive(Debug, Deserialize)]
 #[derive(Debug, Deserialize)]
 pub struct Params {
 pub struct Params {
     queries: Option<String>,
     queries: Option<String>,
 }
 }
 
 
-pub async fn plaintext() -> &'static str {
-    "Hello, World!"
-}
-
-pub async fn json() -> impl IntoResponse {
-    let message = Message {
-        message: "Hello, World!",
-    };
-
-    (StatusCode::OK, Json(message))
-}
-
-#[derive(Template)]
-#[template(path = "fortunes.html.hbs")]
-pub struct FortunesTemplate<'a> {
-    pub fortunes: &'a Vec<Fortune>,
-}
-
 pub fn random_number(rng: &mut SmallRng) -> i32 {
 pub fn random_number(rng: &mut SmallRng) -> i32 {
     (rng.gen::<u32>() % 10_000 + 1) as i32
     (rng.gen::<u32>() % 10_000 + 1) as i32
 }
 }