Browse Source

New test for rocket and diesel (#7990)

* ADD
Adding the new test to the platform

* ADD
rust fmt config and toolchain to stable

* UPDATE
Update project specs and new dependencies

* ADD
Server logic

* ADD
Dockerfile for the rocket-diesel test

* FMT
Cargo fmt

* UPDATE
API Documentation

* FIX
Resolve error during compilation.
  - Dependency version proc-macro2

---------

Co-authored-by: kennycallado <[email protected]>
kennycallado 2 years ago
parent
commit
e02a403a50

+ 33 - 19
frameworks/Rust/rocket/Cargo.toml

@@ -1,32 +1,46 @@
 [package]
 [package]
 name = "rocket_techempower"
 name = "rocket_techempower"
-version = "0.3.0"
-authors = ["Marcelo Barbosa <[email protected]>", "Brendan Hansknecht <[email protected]>", "Dragos Varovici <[email protected]>"]
+version = "0.4.0"
+authors = [
+  "Marcelo Barbosa <[email protected]>",
+  "Brendan Hansknecht <[email protected]>",
+  "Dragos Varovici <[email protected]>",
+  "Raúl Callado <[email protected]>"
+]
 edition = "2021"
 edition = "2021"
 
 
+[profile.release]
+lto = true
+opt-level = 3
+
 [[bin]]
 [[bin]]
 name = "rocket"
 name = "rocket"
 path = "src/main.rs"
 path = "src/main.rs"
 
 
+[[bin]]
+name = "rocket-diesel"
+path = "rocket-diesel/main.rs"
+
 [dependencies]
 [dependencies]
-num_cpus = "1.13.1"
-rand = "0.8.5"
-yarte = "0.15.6"
+diesel = { version = "1.4", features = ["postgres"] }
+# rand = { version = "0.8", features = ["small_rng"] }
+# rocket = { version = "0.5.0-rc.2", features = [ "json" ] }
+rocket_sync_db_pools = { version = "0.1.0-rc.2", default-features = false, features = ["diesel_postgres_pool"] }
+rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] }
+serde = { version = "1.0.152", features = ["derive"] }
+
+dotenv = "0.15.0"
 lazy_static = "1.4.0"
 lazy_static = "1.4.0"
-async-stream = "0.3.3"
-async-trait = "0.1.53"
-futures = "0.3.21"
-futures-util = "0.3.21"
-rocket = { version = "0.5.0-rc.2", features = [
-    "json",
-] }
-rocket_db_pools = { version = "0.1.0-rc.2", features = [
-    "sqlx_postgres",
-] }
-sqlx = { version = "0.5.13", features = [ "postgres", "macros" ] }
 figment = "0.10.6"
 figment = "0.10.6"
-dotenv = "0.15.0"
+rand = { version = "0.8", features = ["small_rng"] }
+rocket = { version = "0.5.0-rc.2", features = [ "json" ] }
+rocket_db_pools = { version = "0.1.0-rc.2", features = [ "sqlx_postgres" ] }
+sqlx = { version = "0.5.13", features = [ "postgres", "macros" ] }
+yarte = "0.15.6"
 
 
-serde = "1.0.137"
+# serde = "1.0.137"
 serde_json = "1.0.81"
 serde_json = "1.0.81"
-serde_derive = "1.0.137"
+serde_derive = "1.0.137"
+
+# Temp issue https://github.com/SergioBenitez/Rocket/issues/2491
+proc-macro2 = "= 1.0.51"

+ 1 - 1
frameworks/Rust/rocket/README.md

@@ -6,7 +6,7 @@
 Rocket is a web framework for Rust that makes it simple to write fast web applications without sacrificing flexibility or type safety. All with minimal code.
 Rocket is a web framework for Rust that makes it simple to write fast web applications without sacrificing flexibility or type safety. All with minimal code.
 
 
 * [User Guide](https://rocket.rs/guide/)
 * [User Guide](https://rocket.rs/guide/)
-* [API Documentation](https://api.rocket.rs/rocket/)
+* [API Documentation](https://api.rocket.rs/v0.5-rc/rocket/)
 * Cargo package: [rocket](https://crates.io/crates/rocket)
 * Cargo package: [rocket](https://crates.io/crates/rocket)
 
 
 ## Database
 ## Database

+ 25 - 0
frameworks/Rust/rocket/benchmark_config.json

@@ -25,6 +25,31 @@
         "notes": "",
         "notes": "",
         "versus": "None"
         "versus": "None"
       }
       }
+    },
+    {
+      "diesel": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?q=",
+        "update_url": "/updates?q=",
+        "port": 8000,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "Postgres",
+        "framework": "Rocket",
+        "language": "Rust",
+        "flavor": "None",
+        "orm": "Full",
+        "platform": "Rust",
+        "webserver": "Hyper",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Rocket (Diesel)",
+        "notes": "",
+        "versus": "None"
+      }
     }
     }
   ]
   ]
 }
 }

+ 17 - 0
frameworks/Rust/rocket/rocket-diesel.dockerfile

@@ -0,0 +1,17 @@
+FROM rust:1.63
+
+ENV ROCKET_BENCHMARK_DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
+
+WORKDIR /rocket-diesel
+COPY ./rocket-diesel ./rocket-diesel
+COPY ./templates ./templates
+COPY ./Cargo.toml ./Cargo.toml
+
+ENV RUSTFLAGS="-C target-cpu=native"
+RUN cargo build --release --bin rocket-diesel
+
+RUN cp ./target/release/rocket-diesel ./target/release/rocket-techempower
+
+EXPOSE 8000
+CMD ./target/release/rocket-techempower
+

+ 4 - 0
frameworks/Rust/rocket/rocket-diesel/database.rs

@@ -0,0 +1,4 @@
+use rocket_sync_db_pools::database;
+
+#[database("hello_world")]
+pub struct Db(diesel::PgConnection);

+ 181 - 0
frameworks/Rust/rocket/rocket-diesel/main.rs

@@ -0,0 +1,181 @@
+use std::net::{IpAddr, Ipv4Addr};
+
+use rand::{rngs::SmallRng, Rng, SeedableRng};
+
+use rocket::http::Status;
+use rocket::response::Redirect;
+use rocket::{figment::Figment, log::LogLevel, serde::json::Json, Config};
+use rocket_dyn_templates::{context, Template};
+
+use diesel::prelude::*;
+
+use crate::database::Db;
+
+use crate::models::Fortune;
+use crate::models::Message;
+use crate::models::World;
+
+use crate::schema::fortune;
+use crate::schema::world;
+
+#[macro_use]
+extern crate diesel;
+#[macro_use]
+extern crate rocket;
+extern crate rocket_sync_db_pools;
+
+mod database;
+mod models;
+mod schema;
+
+fn random() -> i32 {
+    SmallRng::from_entropy().gen_range(1..10_000)
+}
+
+#[get("/plaintext")]
+fn plaintext() -> &'static str {
+    "Hello, World!"
+}
+
+#[get("/json")]
+fn json() -> Json<Message> {
+    let message = Message {
+        message: "Hello, World!",
+    };
+
+    Json(message)
+}
+
+#[get("/db")]
+async fn db(db: Db) -> Result<Json<World>, Status> {
+    let world = db
+        .run(move |conn| world::table.find(random()).first::<World>(conn))
+        .await;
+
+    match world {
+        Ok(world) => Ok(Json(world)),
+        Err(_) => Err(Status::NotFound),
+    }
+}
+
+#[get("/queries")]
+fn queries_empty() -> Redirect {
+    Redirect::to(uri!(queries(1)))
+}
+
+#[get("/queries?<q>")]
+async fn queries(db: Db, q: u16) -> Result<Json<Vec<World>>, Status> {
+    let q = q.clamp(1, 500);
+
+    let mut results = Vec::with_capacity(q.into());
+
+    for _ in 0..q {
+        let world = db
+            .run(move |conn| world::table.find(random()).first::<World>(conn))
+            .await;
+
+        match world {
+            Ok(world) => results.push(world),
+            Err(_) => return Err(Status::NotFound),
+        }
+    }
+
+    Ok(Json(results))
+}
+
+#[get("/updates")]
+fn updates_empty() -> Redirect {
+    Redirect::to(uri!(updates(1)))
+}
+
+#[get("/updates?<q>")]
+async fn updates(db: Db, q: u16) -> Result<Json<Vec<World>>, Status> {
+    let q = q.clamp(1, 500);
+
+    let mut results = Vec::with_capacity(q.into());
+
+    for _ in 0..q {
+        let world = db
+            .run(move |conn| world::table.find(random()).first::<World>(conn))
+            .await;
+
+        match world {
+            Ok(world) => results.push(world),
+            Err(_) => return Err(Status::NotFound),
+        }
+    }
+
+    for mut world in results.clone() {
+        world.randomnumber = random();
+
+        let result = db
+            .run(move |conn| {
+                diesel::update(world::table.find(world.id))
+                    .set(world::randomnumber.eq(world.randomnumber))
+                    .execute(conn)
+            })
+            .await;
+
+        if let Err(_) = result {
+            return Err(Status::InternalServerError);
+        }
+    }
+
+    Ok(Json(results))
+}
+
+#[get("/fortunes")]
+async fn fortunes(db: Db) -> Template {
+    let mut fortunes = db
+        .run(|conn| fortune::table.load::<Fortune>(conn))
+        .await
+        .expect("Error loading fortunes from database.");
+
+    fortunes.push(Fortune {
+        id: 0,
+        message: "Additional fortune added at request time.".to_string(),
+    });
+
+    fortunes.sort_by(|a, b| a.message.cmp(&b.message));
+
+    Template::render("fortunes", context! { fortunes: fortunes })
+}
+
+#[launch]
+fn rocket() -> _ {
+    let config = Config {
+        address: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
+        port: 8000,
+        keep_alive: 0,
+        log_level: LogLevel::Off,
+        ..Default::default()
+    };
+
+    let figment = Figment::from(config).merge((
+        "databases.hello_world",
+        rocket_sync_db_pools::Config {
+            pool_size: 100,
+            timeout: 3,
+            url: rocket::Config::figment()
+                .extract_inner("benchmark_database_url")
+                .expect("Expected env: ROCKET_BENCHMARK_DATABASE_URL"),
+        },
+    ));
+
+    rocket::custom(figment)
+        .attach(Db::fairing())
+        .attach(Template::fairing())
+        .mount(
+            "/",
+            routes![
+                plaintext,
+                json,
+                fortunes,
+                db,
+                queries_empty,
+                queries,
+                updates_empty,
+                updates,
+            ],
+        )
+}

+ 23 - 0
frameworks/Rust/rocket/rocket-diesel/models.rs

@@ -0,0 +1,23 @@
+use serde::{Deserialize, Serialize};
+
+#[allow(non_snake_case)]
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(crate = "rocket::serde")]
+pub struct Message {
+    pub message: &'static str,
+}
+
+#[allow(non_snake_case)]
+#[derive(Debug, Clone, Serialize, Queryable)]
+pub struct World {
+    pub id: i32,
+    pub randomnumber: i32,
+}
+
+#[allow(non_snake_case)]
+#[derive(Debug, Deserialize, Serialize, Queryable)]
+#[serde(crate = "rocket::serde")]
+pub struct Fortune {
+    pub id: i32,
+    pub message: String,
+}

+ 31 - 0
frameworks/Rust/rocket/rocket-diesel/schema.rs

@@ -0,0 +1,31 @@
+// @generated automatically by Diesel CLI.
+
+diesel::table! {
+    Fortune (id) {
+        id -> Int4,
+        message -> Varchar,
+    }
+}
+
+diesel::table! {
+    World (id) {
+        id -> Int4,
+        randomnumber -> Int4,
+    }
+}
+
+diesel::table! {
+    fortune (id) {
+        id -> Int4,
+        message -> Varchar,
+    }
+}
+
+diesel::table! {
+    world (id) {
+        id -> Int4,
+        randomnumber -> Int4,
+    }
+}
+
+diesel::allow_tables_to_appear_in_same_query!(Fortune, World, fortune, world,);

+ 2 - 0
frameworks/Rust/rocket/rust-toolchain.toml

@@ -0,0 +1,2 @@
+[toolchain]
+channel = "stable"

+ 2 - 0
frameworks/Rust/rocket/rustfmt.toml

@@ -0,0 +1,2 @@
+max_width = 96
+reorder_imports = true

+ 1 - 1
frameworks/Rust/rocket/src/database.rs

@@ -2,4 +2,4 @@ use rocket_db_pools::{sqlx, Database};
 
 
 #[derive(Database)]
 #[derive(Database)]
 #[database("hello_world")]
 #[database("hello_world")]
-pub struct HelloWorld(sqlx::PgPool);
+pub struct HelloWorld(sqlx::PgPool);

+ 62 - 35
frameworks/Rust/rocket/src/main.rs

@@ -2,28 +2,28 @@
 extern crate lazy_static;
 extern crate lazy_static;
 #[macro_use]
 #[macro_use]
 extern crate rocket;
 extern crate rocket;
-extern crate serde_derive;
 extern crate dotenv;
 extern crate dotenv;
+extern crate serde_derive;
 
 
+mod database;
 mod models;
 mod models;
 mod random;
 mod random;
-mod database;
 
 
 use dotenv::dotenv;
 use dotenv::dotenv;
-use std::net::{IpAddr, Ipv4Addr};
+use figment::Figment;
+use rocket::config::{Config, LogLevel};
+use rocket::response::content::RawHtml;
+use rocket::serde::json::Json;
+use rocket::{Build, Rocket};
+use rocket_db_pools::{sqlx, Connection, Database};
+use sqlx::Acquire;
 use std::env;
 use std::env;
+use std::net::{IpAddr, Ipv4Addr};
 use std::thread::available_parallelism;
 use std::thread::available_parallelism;
-use rocket::{Rocket, Build};
-use rocket::serde::json::Json;
-use rocket::response::content::RawHtml;
-use rocket::config::{Config, LogLevel};
 use yarte::Template;
 use yarte::Template;
-use rocket_db_pools::{sqlx, Database, Connection};
-use sqlx::Acquire;
-use figment::Figment;
 
 
-use models::{World, Fortune, Message};
 use database::HelloWorld;
 use database::HelloWorld;
+use models::{Fortune, Message, World};
 use random::random_number;
 use random::random_number;
 
 
 #[get("/plaintext")]
 #[get("/plaintext")]
@@ -43,15 +43,19 @@ async fn json() -> Json<models::Message> {
 async fn db(mut db: Connection<HelloWorld>) -> Json<World> {
 async fn db(mut db: Connection<HelloWorld>) -> Json<World> {
     let number = random_number();
     let number = random_number();
 
 
-    let result : World = sqlx::query_as("SELECT id, randomnumber FROM World WHERE id = $1").bind(number)
-        .fetch_one(&mut *db).await.ok().expect("error loading world");
+    let result: World = sqlx::query_as("SELECT id, randomnumber FROM World WHERE id = $1")
+        .bind(number)
+        .fetch_one(&mut *db)
+        .await
+        .ok()
+        .expect("error loading world");
 
 
     Json(result)
     Json(result)
 }
 }
 
 
 #[get("/queries")]
 #[get("/queries")]
 async fn queries_empty(db: Connection<HelloWorld>) -> Json<Vec<World>> {
 async fn queries_empty(db: Connection<HelloWorld>) -> Json<Vec<World>> {
-    queries(db,1).await
+    queries(db, 1).await
 }
 }
 
 
 #[get("/queries?<q>")]
 #[get("/queries?<q>")]
@@ -69,8 +73,12 @@ async fn queries(mut db: Connection<HelloWorld>, q: u16) -> Json<Vec<World>> {
     for _ in 0..q {
     for _ in 0..q {
         let query_id = random_number();
         let query_id = random_number();
 
 
-        let result :World = sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
-            .fetch_one(&mut *db).await.ok().expect("error loading world");
+        let result: World = sqlx::query_as("SELECT * FROM World WHERE id = $1")
+            .bind(query_id)
+            .fetch_one(&mut *db)
+            .await
+            .ok()
+            .expect("error loading world");
 
 
         results.push(result);
         results.push(result);
     }
     }
@@ -86,8 +94,11 @@ pub struct FortunesTemplate<'a> {
 
 
 #[get("/fortunes")]
 #[get("/fortunes")]
 async fn fortunes(mut db: Connection<HelloWorld>) -> RawHtml<String> {
 async fn fortunes(mut db: Connection<HelloWorld>) -> RawHtml<String> {
-    let mut fortunes: Vec<Fortune> = sqlx::query_as("SELECT * FROM Fortune").fetch_all(&mut *db).await
-        .ok().expect("Could not load Fortunes");
+    let mut fortunes: Vec<Fortune> = sqlx::query_as("SELECT * FROM Fortune")
+        .fetch_all(&mut *db)
+        .await
+        .ok()
+        .expect("Could not load Fortunes");
 
 
     fortunes.push(Fortune {
     fortunes.push(Fortune {
         id: 0,
         id: 0,
@@ -107,7 +118,7 @@ async fn fortunes(mut db: Connection<HelloWorld>) -> RawHtml<String> {
 
 
 #[get("/updates")]
 #[get("/updates")]
 async fn updates_empty(db: Connection<HelloWorld>) -> Json<Vec<World>> {
 async fn updates_empty(db: Connection<HelloWorld>) -> Json<Vec<World>> {
-    updates(db,1).await
+    updates(db, 1).await
 }
 }
 
 
 #[get("/updates?<q>")]
 #[get("/updates?<q>")]
@@ -124,21 +135,32 @@ async fn updates(mut db: Connection<HelloWorld>, q: u16) -> Json<Vec<World>> {
 
 
     for _ in 0..q {
     for _ in 0..q {
         let query_id = random_number();
         let query_id = random_number();
-        let mut result :World = sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
-            .fetch_one(&mut *db).await.ok().expect("World was not found");
+        let mut result: World = sqlx::query_as("SELECT * FROM World WHERE id = $1")
+            .bind(query_id)
+            .fetch_one(&mut *db)
+            .await
+            .ok()
+            .expect("World was not found");
 
 
         result.random_number = random_number();
         result.random_number = random_number();
         results.push(result);
         results.push(result);
     }
     }
 
 
     let mut pool = db.into_inner();
     let mut pool = db.into_inner();
-    let mut tx = pool.begin().await.ok().expect("could not start transaction");
+    let mut tx = pool
+        .begin()
+        .await
+        .ok()
+        .expect("could not start transaction");
 
 
     for w in &results {
     for w in &results {
         sqlx::query("UPDATE World SET randomnumber = $1 WHERE id = $2")
         sqlx::query("UPDATE World SET randomnumber = $1 WHERE id = $2")
-            .bind(w.random_number).bind(w.id)
+            .bind(w.random_number)
+            .bind(w.id)
             .execute(&mut tx)
             .execute(&mut tx)
-            .await.ok().expect("Could not update World");
+            .await
+            .ok()
+            .expect("Could not update World");
     }
     }
 
 
     tx.commit().await.ok().expect("could not update worlds");
     tx.commit().await.ok().expect("could not update worlds");
@@ -157,21 +179,27 @@ pub fn launch() -> Rocket<Build> {
         port: 8000,
         port: 8000,
         keep_alive: 0,
         keep_alive: 0,
         log_level: LogLevel::Off,
         log_level: LogLevel::Off,
-        workers: available_parallelism().expect("could not get parallelism").get() * 16,
+        workers: available_parallelism()
+            .expect("could not get parallelism")
+            .get()
+            * 16,
         ..Default::default()
         ..Default::default()
     };
     };
 
 
-    let database_url = env::var("ROCKET_BENCHMARK_DATABASE_URL").ok()
+    let database_url = env::var("ROCKET_BENCHMARK_DATABASE_URL")
+        .ok()
         .expect("ROCKET_BENCHMARK_DATABASE_URL environment variable was not set");
         .expect("ROCKET_BENCHMARK_DATABASE_URL environment variable was not set");
 
 
-    let figment = Figment::from(config)
-        .merge(("databases.hello_world", rocket_db_pools::Config {
-                url: database_url,
-                min_connections: None,
-                max_connections: 100,
-                connect_timeout: 3,
-                idle_timeout: None,
-            }));
+    let figment = Figment::from(config).merge((
+        "databases.hello_world",
+        rocket_db_pools::Config {
+            url: database_url,
+            min_connections: None,
+            max_connections: 100,
+            connect_timeout: 3,
+            idle_timeout: None,
+        },
+    ));
 
 
     rocket::custom(figment)
     rocket::custom(figment)
         .mount(
         .mount(
@@ -189,4 +217,3 @@ pub fn launch() -> Rocket<Build> {
         )
         )
         .attach(HelloWorld::init())
         .attach(HelloWorld::init())
 }
 }
-

+ 2 - 5
frameworks/Rust/rocket/src/models.rs

@@ -11,7 +11,7 @@ pub struct Message {
 #[serde(crate = "rocket::serde")]
 #[serde(crate = "rocket::serde")]
 pub struct Fortune {
 pub struct Fortune {
     pub id: i32,
     pub id: i32,
-    pub message: String
+    pub message: String,
 }
 }
 
 
 #[allow(non_snake_case)]
 #[allow(non_snake_case)]
@@ -21,8 +21,5 @@ pub struct World {
     pub id: i32,
     pub id: i32,
     #[sqlx(rename = "randomnumber")]
     #[sqlx(rename = "randomnumber")]
     #[serde(rename = "randomNumber")]
     #[serde(rename = "randomNumber")]
-    pub random_number: i32
+    pub random_number: i32,
 }
 }
-
-
-

+ 0 - 1
frameworks/Rust/rocket/src/random.rs

@@ -44,4 +44,3 @@ impl RandomArray {
         self.data[self.pointer - 1]
         self.data[self.pointer - 1]
     }
     }
 }
 }
-