Browse Source

[xitca-web] use the same update flow in orm tests (#10402)

* [xitca-web] use the same update flow in orm tests

* code dedup
fakeshadow 5 days ago
parent
commit
08c18d0134

+ 27 - 18
frameworks/Rust/xitca-web/Cargo.lock

@@ -30,6 +30,12 @@ version = "1.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
 
+[[package]]
+name = "array-init"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
+
 [[package]]
 name = "async-stream"
 version = "0.3.6"
@@ -527,9 +533,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
 
 [[package]]
 name = "icu_properties"
-version = "2.1.1"
+version = "2.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
 dependencies = [
  "icu_collections",
  "icu_locale_core",
@@ -541,9 +547,9 @@ dependencies = [
 
 [[package]]
 name = "icu_properties_data"
-version = "2.1.1"
+version = "2.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
 
 [[package]]
 name = "icu_provider"
@@ -835,9 +841,11 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095"
 dependencies = [
+ "array-init",
  "bytes",
  "fallible-iterator",
  "postgres-protocol",
+ "uuid",
 ]
 
 [[package]]
@@ -1269,7 +1277,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
 [[package]]
 name = "std-util"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/toasty?branch=engine#2132c89dfd7a4698cdaaa85888c016b97c01471a"
+source = "git+https://github.com/fakeshadow/toasty?branch=engine#b4a6ff95cb23f34c06c383806a0d00f782b6acbc"
 dependencies = [
  "heck",
  "pluralizer",
@@ -1369,7 +1377,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 [[package]]
 name = "toasty"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/toasty?branch=engine#2132c89dfd7a4698cdaaa85888c016b97c01471a"
+source = "git+https://github.com/fakeshadow/toasty?branch=engine#b4a6ff95cb23f34c06c383806a0d00f782b6acbc"
 dependencies = [
  "anyhow",
  "async-stream",
@@ -1387,7 +1395,7 @@ dependencies = [
 [[package]]
 name = "toasty-codegen"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/toasty?branch=engine#2132c89dfd7a4698cdaaa85888c016b97c01471a"
+source = "git+https://github.com/fakeshadow/toasty?branch=engine#b4a6ff95cb23f34c06c383806a0d00f782b6acbc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1398,7 +1406,7 @@ dependencies = [
 [[package]]
 name = "toasty-core"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/toasty?branch=engine#2132c89dfd7a4698cdaaa85888c016b97c01471a"
+source = "git+https://github.com/fakeshadow/toasty?branch=engine#b4a6ff95cb23f34c06c383806a0d00f782b6acbc"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1412,7 +1420,7 @@ dependencies = [
 [[package]]
 name = "toasty-macros"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/toasty?branch=engine#2132c89dfd7a4698cdaaa85888c016b97c01471a"
+source = "git+https://github.com/fakeshadow/toasty?branch=engine#b4a6ff95cb23f34c06c383806a0d00f782b6acbc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1424,7 +1432,7 @@ dependencies = [
 [[package]]
 name = "toasty-sql"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/toasty?branch=engine#2132c89dfd7a4698cdaaa85888c016b97c01471a"
+source = "git+https://github.com/fakeshadow/toasty?branch=engine#b4a6ff95cb23f34c06c383806a0d00f782b6acbc"
 dependencies = [
  "anyhow",
  "toasty-core",
@@ -1472,7 +1480,7 @@ dependencies = [
 [[package]]
 name = "tokio-uring"
 version = "0.5.1"
-source = "git+http://github.com/fakeshadow/tokio-uring?rev=97d9a98#97d9a988704b5466809633b3ca6ba07acba3f38b"
+source = "git+http://github.com/fakeshadow/tokio-uring?rev=c3d5887#c3d588793dc88cb05b57b982356b5e28bc73c192"
 dependencies = [
  "bytes",
  "io-uring",
@@ -1759,7 +1767,7 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
 [[package]]
 name = "xitca-codegen"
 version = "0.4.0"
-source = "git+http://github.com/HFQR/xitca-web?rev=83b4a60#83b4a607a9a704a4286dd190209e567316589afc"
+source = "git+http://github.com/HFQR/xitca-web?rev=b723c0c#b723c0c5800d82beca768bbfff273b3e6d1abf52"
 dependencies = [
  "quote",
  "syn",
@@ -1768,7 +1776,7 @@ dependencies = [
 [[package]]
 name = "xitca-http"
 version = "0.7.1"
-source = "git+http://github.com/HFQR/xitca-web?rev=83b4a60#83b4a607a9a704a4286dd190209e567316589afc"
+source = "git+http://github.com/HFQR/xitca-web?rev=b723c0c#b723c0c5800d82beca768bbfff273b3e6d1abf52"
 dependencies = [
  "futures-core",
  "http",
@@ -1801,7 +1809,7 @@ dependencies = [
 [[package]]
 name = "xitca-postgres"
 version = "0.3.0"
-source = "git+http://github.com/HFQR/xitca-web?rev=83b4a60#83b4a607a9a704a4286dd190209e567316589afc"
+source = "git+http://github.com/HFQR/xitca-web?rev=b723c0c#b723c0c5800d82beca768bbfff273b3e6d1abf52"
 dependencies = [
  "fallible-iterator",
  "futures-core",
@@ -1830,7 +1838,7 @@ dependencies = [
 [[package]]
 name = "xitca-postgres-toasty"
 version = "0.1.0"
-source = "git+https://github.com/fakeshadow/xitca-postgres-toasty?rev=04bedb8#04bedb8641a13b61f13c85a1fd0b8318dd5454a8"
+source = "git+https://github.com/fakeshadow/xitca-postgres-toasty?rev=02c6604#02c6604beaf9f9792eb2c4e54d23cbddc34675fc"
 dependencies = [
  "anyhow",
  "futures-core",
@@ -1839,6 +1847,7 @@ dependencies = [
  "toasty-core",
  "toasty-sql",
  "tokio",
+ "uuid",
  "xitca-postgres",
 ]
 
@@ -1854,7 +1863,7 @@ dependencies = [
 [[package]]
 name = "xitca-server"
 version = "0.5.0"
-source = "git+http://github.com/HFQR/xitca-web?rev=83b4a60#83b4a607a9a704a4286dd190209e567316589afc"
+source = "git+http://github.com/HFQR/xitca-web?rev=b723c0c#b723c0c5800d82beca768bbfff273b3e6d1abf52"
 dependencies = [
  "socket2",
  "tokio",
@@ -1868,7 +1877,7 @@ dependencies = [
 [[package]]
 name = "xitca-service"
 version = "0.3.0"
-source = "git+http://github.com/HFQR/xitca-web?rev=83b4a60#83b4a607a9a704a4286dd190209e567316589afc"
+source = "git+http://github.com/HFQR/xitca-web?rev=b723c0c#b723c0c5800d82beca768bbfff273b3e6d1abf52"
 
 [[package]]
 name = "xitca-unsafe-collection"
@@ -1913,7 +1922,7 @@ dependencies = [
 [[package]]
 name = "xitca-web"
 version = "0.7.1"
-source = "git+http://github.com/HFQR/xitca-web?rev=83b4a60#83b4a607a9a704a4286dd190209e567316589afc"
+source = "git+http://github.com/HFQR/xitca-web?rev=b723c0c#b723c0c5800d82beca768bbfff273b3e6d1abf52"
 dependencies = [
  "futures-core",
  "pin-project-lite",

+ 11 - 9
frameworks/Rust/xitca-web/Cargo.toml

@@ -100,17 +100,19 @@ panic = "abort"
 
 [patch.crates-io]
 xitca-postgres-diesel = { git = "https://github.com/fakeshadow/xitca-postgres-diesel", rev = "fb5dcba" }
-xitca-postgres-toasty = { git = "https://github.com/fakeshadow/xitca-postgres-toasty", rev = "04bedb8" }
+xitca-postgres-toasty = { git = "https://github.com/fakeshadow/xitca-postgres-toasty", rev = "02c6604" }
 
 # personal fork for efficient toasty engine fine tuned with pipelined xitca-postgres client
 toasty = { git = "https://github.com/fakeshadow/toasty", branch = "engine" }
 toasty-core = { git = "https://github.com/fakeshadow/toasty", branch = "engine" }
 toasty-sql = { git = "https://github.com/fakeshadow/toasty", branch = "engine" }
-tokio-uring = { git = "http://github.com/fakeshadow/tokio-uring", rev = "97d9a98" }
-
-xitca-codegen = { git = "http://github.com/HFQR/xitca-web", rev = "83b4a60" }
-xitca-http = { git = "http://github.com/HFQR/xitca-web", rev = "83b4a60" }
-xitca-postgres = { git = "http://github.com/HFQR/xitca-web", rev = "83b4a60" }
-xitca-server = { git = "http://github.com/HFQR/xitca-web", rev = "83b4a60" }
-xitca-service = { git = "http://github.com/HFQR/xitca-web", rev = "83b4a60" }
-xitca-web = { git = "http://github.com/HFQR/xitca-web", rev = "83b4a60" }
+
+# personal fork of tokio-uring with tokio local runtime enabled
+tokio-uring = { git = "http://github.com/fakeshadow/tokio-uring", rev = "c3d5887" }
+
+xitca-codegen = { git = "http://github.com/HFQR/xitca-web", rev = "b723c0c" }
+xitca-http = { git = "http://github.com/HFQR/xitca-web", rev = "b723c0c" }
+xitca-postgres = { git = "http://github.com/HFQR/xitca-web", rev = "b723c0c" }
+xitca-server = { git = "http://github.com/HFQR/xitca-web", rev = "b723c0c" }
+xitca-service = { git = "http://github.com/HFQR/xitca-web", rev = "b723c0c" }
+xitca-web = { git = "http://github.com/HFQR/xitca-web", rev = "b723c0c" }

+ 95 - 83
frameworks/Rust/xitca-web/src/db.rs

@@ -1,107 +1,119 @@
-#[path = "./db_util.rs"]
-mod db_util;
-
-use xitca_postgres::{Execute, iter::AsyncLendingIterator, pool::Pool};
+use core::cell::RefCell;
+
+use xitca_postgres::{
+    Execute,
+    dev::Query,
+    iter::AsyncLendingIterator,
+    statement::{Statement, StatementNamed},
+    types::Type,
+};
 
-use super::{
+use crate::{
     ser::{Fortune, Fortunes, World},
-    util::{DB_URL, HandleResult, Rand},
+    util::{Error, HandleResult, Rand},
 };
 
-use db_util::{FORTUNE_STMT, UPDATE_STMT, WORLD_STMT, not_found};
+pub const FORTUNE_STMT: StatementNamed = Statement::named("SELECT id,message FROM fortune", &[]);
 
-pub struct Client {
-    pool: Pool,
-    rng: core::cell::RefCell<Rand>,
-}
+pub const WORLD_STMT: StatementNamed = Statement::named("SELECT id,randomnumber FROM world WHERE id=$1", &[Type::INT4]);
 
-pub async fn create() -> HandleResult<Client> {
-    Ok(Client {
-        pool: Pool::builder(DB_URL).capacity(1).build()?,
-        rng: Default::default(),
-    })
-}
+pub const UPDATE_STMT: StatementNamed = Statement::named(
+    "UPDATE world SET randomnumber=w.r FROM (SELECT unnest($1) as i,unnest($2) as r) w WHERE world.id=w.i",
+    &[Type::INT4_ARRAY, Type::INT4_ARRAY],
+);
 
-impl Client {
-    pub async fn get_world(&self) -> HandleResult<World> {
-        let mut conn = self.pool.get().await?;
-        let stmt = WORLD_STMT.execute(&mut conn).await?;
-        let id = self.rng.borrow_mut().gen_id();
-        let mut res = stmt.bind([id]).query(&conn.consume()).await?;
-        let row = res.try_next().await?.ok_or_else(not_found)?;
-        Ok(World::new(row.get(0), row.get(1)))
-    }
-
-    pub async fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {
-        let mut conn = self.pool.get().await?;
-        let stmt = WORLD_STMT.execute(&mut conn).await?;
+#[cold]
+#[inline(never)]
+fn not_found() -> Error {
+    "request World does not exist".into()
+}
 
-        let get = self
-            .rng
-            .borrow_mut()
-            .gen_multi()
-            .take(num as _)
-            .map(|id| stmt.bind([id]).query(&conn))
-            .collect::<Vec<_>>();
+pub(crate) async fn db<C>(conn: C, rng: &RefCell<Rand>, stmt: &Statement) -> HandleResult<World>
+where
+    C: Query,
+{
+    let id = rng.borrow_mut().gen_id();
+    let mut res = stmt.bind([id]).query(&conn).await?;
+    drop(conn);
+    let row = res.try_next().await?.ok_or_else(not_found)?;
+    Ok(World::new(row.get(0), row.get(1)))
+}
 
-        drop(conn);
+pub(crate) async fn queries<C>(conn: C, rng: &RefCell<Rand>, stmt: &Statement, num: u16) -> HandleResult<Vec<World>>
+where
+    C: Query,
+{
+    let get = rng
+        .borrow_mut()
+        .gen_multi()
+        .take(num as _)
+        .map(|id| stmt.bind([id]).query(&conn))
+        .collect::<Vec<_>>();
 
-        let mut worlds = Vec::with_capacity(num as _);
+    drop(conn);
 
-        for get in get {
-            let mut res = get.await?;
-            let row = res.try_next().await?.ok_or_else(not_found)?;
-            worlds.push(World::new(row.get(0), row.get(1)));
-        }
+    let mut worlds = Vec::with_capacity(num as _);
 
-        Ok(worlds)
+    for get in get {
+        let mut res = get.await?;
+        let row = res.try_next().await?.ok_or_else(not_found)?;
+        worlds.push(World::new(row.get(0), row.get(1)));
     }
 
-    pub async fn update(&self, num: u16) -> HandleResult<Vec<World>> {
-        let mut conn = self.pool.get().await?;
-        let world_stmt = WORLD_STMT.execute(&mut conn).await?;
-        let update_stmt = UPDATE_STMT.execute(&mut conn).await?;
+    Ok(worlds)
+}
 
-        let (get, update, worlds) = {
-            let mut rng = self.rng.borrow_mut();
-            let mut ids = rng.gen_multi().take(num as _).collect::<Vec<_>>();
-            ids.sort();
+pub(crate) async fn updates<C>(
+    conn: C,
+    rng: &RefCell<Rand>,
+    world_stmt: &Statement,
+    update_stmt: &Statement,
+    num: u16,
+) -> HandleResult<Vec<World>>
+where
+    C: Query,
+{
+    let mut rng = rng.borrow_mut();
+    let mut ids = rng.gen_multi().take(num as _).collect::<Vec<_>>();
+    ids.sort();
+
+    let (get, rngs, worlds) = ids
+        .iter()
+        .cloned()
+        .zip(rng.gen_multi())
+        .map(|(id, rand)| {
+            let get = world_stmt.bind([id]).query(&conn);
+            (get, rand, World::new(id, rand))
+        })
+        .collect::<(Vec<_>, Vec<_>, Vec<_>)>();
+
+    let update = update_stmt.bind([&ids, &rngs]).query(&conn);
+
+    drop(conn);
+    drop(rng);
+
+    for get in get {
+        let _rand = get.await?.try_next().await?.ok_or_else(not_found)?.get::<i32>(1);
+    }
 
-            let (get, rngs, worlds) = ids
-                .iter()
-                .cloned()
-                .zip(rng.gen_multi())
-                .map(|(id, rand)| {
-                    let get = world_stmt.bind([id]).query(&conn);
-                    (get, rand, World::new(id, rand))
-                })
-                .collect::<(Vec<_>, Vec<_>, Vec<_>)>();
+    update.await?;
 
-            let update = update_stmt.bind([&ids, &rngs]).query(&conn.consume());
+    Ok(worlds)
+}
 
-            (get, update, worlds)
-        };
+pub(crate) async fn fortunes<C>(conn: C, stmt: &Statement) -> HandleResult<Fortunes>
+where
+    C: Query,
+{
+    let mut res = stmt.query(&conn).await?;
 
-        for fut in get {
-            let _rand = fut.await?.try_next().await?.ok_or_else(not_found)?.get::<i32>(1);
-        }
+    drop(conn);
 
-        update.await?;
+    let mut fortunes = Vec::with_capacity(16);
 
-        Ok(worlds)
+    while let Some(row) = res.try_next().await? {
+        fortunes.push(Fortune::new(row.get(0), row.get::<String>(1)));
     }
 
-    pub async fn tell_fortune(&self) -> HandleResult<Fortunes> {
-        let mut fortunes = Vec::with_capacity(16);
-
-        let mut conn = self.pool.get().await?;
-        let stmt = FORTUNE_STMT.execute(&mut conn).await?;
-        let mut res = stmt.query(&conn.consume()).await?;
-
-        while let Some(row) = res.try_next().await? {
-            fortunes.push(Fortune::new(row.get(0), row.get::<String>(1)));
-        }
-
-        Ok(Fortunes::new(fortunes))
-    }
+    Ok(Fortunes::new(fortunes))
 }

+ 33 - 56
frameworks/Rust/xitca-web/src/db_diesel.rs

@@ -1,6 +1,6 @@
 use diesel::prelude::*;
 use diesel_async::{AsyncConnection, RunQueryDsl};
-use futures_util::future::{TryFutureExt, TryJoinAll, try_join};
+use futures_util::future::TryJoinAll;
 use xitca_postgres_diesel::AsyncPgConnection;
 
 use crate::{
@@ -23,72 +23,49 @@ impl Pool {
         })
     }
 
-    pub async fn get_world(&self) -> HandleResult<World> {
-        {
-            use schema::world::dsl::*;
+    pub async fn db(&self) -> HandleResult<World> {
+        use schema::world::dsl::{id, world};
 
-            let w_id = self.rng.borrow_mut().gen_id();
-            world.filter(id.eq(w_id)).first(&mut &self.pool).map_err(Into::into)
-        }
-        .await
-    }
-
-    pub async fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {
-        {
-            use schema::world::dsl::*;
-
-            self.rng
-                .borrow_mut()
-                .gen_multi()
-                .take(num as _)
-                .map(|w_id| world.filter(id.eq(w_id)).first(&mut &self.pool).map_err(Into::into))
-                .collect::<TryJoinAll<_>>()
-        }
-        .await
+        let w_id = self.rng.borrow_mut().gen_id();
+        let w = world.filter(id.eq(w_id)).first(&mut &self.pool).await?;
+        Ok(w)
     }
 
-    pub async fn update(&self, num: u16) -> HandleResult<Vec<World>> {
-        {
-            use schema::world::dsl::*;
+    pub async fn queries(&self, num: u16) -> HandleResult<Vec<World>> {
+        use schema::world::dsl::{id, world};
 
-            let mut rng = self.rng.borrow_mut();
-            let mut params = Vec::with_capacity(num as _);
+        let get = self
+            .rng
+            .borrow_mut()
+            .gen_multi()
+            .take(num as _)
+            .map(|w_id| world.filter(id.eq(w_id)).first(&mut &self.pool))
+            .collect::<TryJoinAll<_>>();
 
-            let get = rng
-                .clone()
-                .gen_multi()
-                .take(num as _)
-                .zip(rng.gen_multi())
-                .map(|(w_id, rng)| {
-                    let get = world.filter(id.eq(w_id)).first::<World>(&mut &self.pool);
+        get.await.map_err(Into::into)
+    }
 
-                    params.push((w_id, rng));
+    pub async fn updates(&self, num: u16) -> HandleResult<Vec<World>> {
+        let mut worlds = self.queries(num).await?;
 
-                    async move {
-                        let mut w = get.await?;
-                        w.randomnumber = rng;
-                        HandleResult::Ok(w)
-                    }
-                })
-                .collect::<TryJoinAll<_>>();
+        let params = worlds
+            .iter_mut()
+            .zip(self.rng.borrow_mut().gen_multi())
+            .map(|(world, rand)| {
+                world.randomnumber = rand;
+                (world.id, rand)
+            })
+            .collect();
 
-            let sql = update_query_from_ids(params);
-            let update = diesel::sql_query(sql).execute(&mut &self.pool).map_err(Into::into);
+        let sql = update_query_from_ids(params);
+        diesel::sql_query(sql).execute(&mut &self.pool).await?;
 
-            try_join(get, update)
-        }
-        .await
-        .map(|(worlds, _)| worlds)
+        Ok(worlds)
     }
 
-    pub async fn tell_fortune(&self) -> HandleResult<Fortunes> {
-        {
-            use schema::fortune::dsl::*;
-
-            fortune.load(&mut &self.pool).map_err(Into::into)
-        }
-        .await
-        .map(Fortunes::new)
+    pub async fn fortunes(&self) -> HandleResult<Fortunes> {
+        let fortunes = schema::fortune::dsl::fortune.load(&mut &self.pool).await?;
+        Ok(Fortunes::new(fortunes))
     }
 }
 

+ 47 - 0
frameworks/Rust/xitca-web/src/db_pool.rs

@@ -0,0 +1,47 @@
+use xitca_postgres::{Execute, pool::Pool};
+
+use super::{
+    ser::{Fortunes, World},
+    util::{DB_URL, HandleResult, Rand},
+};
+
+use crate::db::{self, FORTUNE_STMT, UPDATE_STMT, WORLD_STMT};
+
+pub struct Client {
+    pool: Pool,
+    rng: core::cell::RefCell<Rand>,
+}
+
+pub async fn create() -> HandleResult<Client> {
+    Ok(Client {
+        pool: Pool::builder(DB_URL).capacity(1).build()?,
+        rng: Default::default(),
+    })
+}
+
+impl Client {
+    pub async fn db(&self) -> HandleResult<World> {
+        let mut conn = self.pool.get().await?;
+        let stmt = WORLD_STMT.execute(&mut conn).await?;
+        db::db(conn, &self.rng, &stmt).await
+    }
+
+    pub async fn queries(&self, num: u16) -> HandleResult<Vec<World>> {
+        let mut conn = self.pool.get().await?;
+        let stmt = WORLD_STMT.execute(&mut conn).await?;
+        db::queries(conn, &self.rng, &stmt, num).await
+    }
+
+    pub async fn updates(&self, num: u16) -> HandleResult<Vec<World>> {
+        let mut conn = self.pool.get().await?;
+        let world_stmt = WORLD_STMT.execute(&mut conn).await?;
+        let update_stmt = UPDATE_STMT.execute(&mut conn).await?;
+        db::updates(conn, &self.rng, &world_stmt, &update_stmt, num).await
+    }
+
+    pub async fn fortunes(&self) -> HandleResult<Fortunes> {
+        let mut conn = self.pool.get().await?;
+        let stmt = FORTUNE_STMT.execute(&mut conn).await?;
+        db::fortunes(conn, &stmt).await
+    }
+}

+ 23 - 14
frameworks/Rust/xitca-web/src/db_toasty.rs

@@ -1,4 +1,4 @@
-use futures_util::future::try_join_all;
+use futures_util::future::TryJoinAll;
 use toasty::Db;
 
 use crate::{
@@ -27,30 +27,39 @@ impl Pool {
         })
     }
 
-    pub async fn get_world(&self) -> HandleResult<World> {
+    pub async fn db(&self) -> HandleResult<World> {
         let id = self.rng.borrow_mut().gen_id();
         World::get_by_id(&self.db, id).await.map_err(Into::into)
     }
 
-    pub async fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {
-        try_join_all(core::iter::repeat_with(|| self.get_world()).take(num as _)).await
+    pub async fn queries(&self, num: u16) -> HandleResult<Vec<World>> {
+        let get = self
+            .rng
+            .borrow_mut()
+            .gen_multi()
+            .take(num as _)
+            .map(|id| World::get_by_id(&self.db, id))
+            .collect::<TryJoinAll<_>>();
+
+        get.await.map_err(Into::into)
     }
 
-    pub async fn update(&self, num: u16) -> HandleResult<Vec<World>> {
-        let mut worlds = self.get_worlds(num).await?;
+    pub async fn updates(&self, num: u16) -> HandleResult<Vec<World>> {
+        let mut worlds = self.queries(num).await?;
 
-        try_join_all({
-            let mut rng = self.rng.borrow_mut();
-            worlds
-                .iter_mut()
-                .map(move |world| world.update().randomnumber(rng.gen_id()).exec(&self.db))
-        })
-        .await?;
+        // TODO: revisit when toasty supports batch update or raw sql
+        let update = worlds
+            .iter_mut()
+            .zip(self.rng.borrow_mut().gen_multi())
+            .map(|(world, rand)| world.update().randomnumber(rand).exec(&self.db))
+            .collect::<TryJoinAll<_>>();
+
+        update.await?;
 
         Ok(worlds)
     }
 
-    pub async fn tell_fortune(&self) -> HandleResult<Fortunes> {
+    pub async fn fortunes(&self) -> HandleResult<Fortunes> {
         let fortunes = Fortune::all().all(&self.db).await?.collect().await?;
         Ok(Fortunes::new(fortunes))
     }

+ 15 - 65
frameworks/Rust/xitca-web/src/db_unrealistic.rs

@@ -1,17 +1,16 @@
 //! this module is unrealistic. related issue:
 //! https://github.com/TechEmpower/FrameworkBenchmarks/issues/8790
 
-#[path = "./db_util.rs"]
-mod db_util;
+use core::future::Future;
 
 use xitca_postgres::{Execute, iter::AsyncLendingIterator, statement::Statement};
 
 use super::{
-    ser::{Fortune, Fortunes, World},
+    ser::{Fortunes, World},
     util::{DB_URL, HandleResult, Rand},
 };
 
-use db_util::{FORTUNE_STMT, UPDATE_STMT, WORLD_STMT, not_found};
+use crate::db::{self, FORTUNE_STMT, UPDATE_STMT, WORLD_STMT};
 
 pub struct Client {
     cli: xitca_postgres::Client,
@@ -43,72 +42,23 @@ pub async fn create() -> HandleResult<Client> {
 }
 
 impl Client {
-    pub async fn get_world(&self) -> HandleResult<World> {
-        let id = self.rng.borrow_mut().gen_id();
-        let mut res = self.world.bind([id]).query(&self.cli).await?;
-        let row = res.try_next().await?.ok_or_else(not_found)?;
-        Ok(World::new(row.get(0), row.get(1)))
+    #[inline]
+    pub fn db(&self) -> impl Future<Output = HandleResult<World>> {
+        db::db(&self.cli, &self.rng, &self.world)
     }
 
-    pub async fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {
-        let get = self
-            .rng
-            .borrow_mut()
-            .gen_multi()
-            .take(num as _)
-            .map(|id| self.world.bind([id]).query(&self.cli))
-            .collect::<Vec<_>>();
-
-        let mut worlds = Vec::with_capacity(num as _);
-
-        for query in get {
-            let mut res = query.await?;
-            let row = res.try_next().await?.ok_or_else(not_found)?;
-            worlds.push(World::new(row.get(0), row.get(1)));
-        }
-
-        Ok(worlds)
+    #[inline]
+    pub fn queries(&self, num: u16) -> impl Future<Output = HandleResult<Vec<World>>> {
+        db::queries(&self.cli, &self.rng, &self.world, num)
     }
 
-    pub async fn update(&self, num: u16) -> HandleResult<Vec<World>> {
-        let (get, update, worlds) = {
-            let mut rng = self.rng.borrow_mut();
-            let mut ids = rng.gen_multi().take(num as _).collect::<Vec<_>>();
-            ids.sort();
-
-            let (get, rngs, worlds) = ids
-                .iter()
-                .cloned()
-                .zip(rng.gen_multi())
-                .map(|(id, rand)| {
-                    let get = self.world.bind([id]).query(&self.cli);
-                    (get, rand, World::new(id, rand))
-                })
-                .collect::<(Vec<_>, Vec<_>, Vec<_>)>();
-
-            let update = self.update.bind([&ids, &rngs]).query(&self.cli);
-
-            (get, update, worlds)
-        };
-
-        for fut in get {
-            let _rand = fut.await?.try_next().await?.ok_or_else(not_found)?.get::<i32>(1);
-        }
-
-        update.await?;
-
-        Ok(worlds)
+    #[inline]
+    pub fn updates(&self, num: u16) -> impl Future<Output = HandleResult<Vec<World>>> {
+        db::updates(&self.cli, &self.rng, &self.world, &self.update, num)
     }
 
-    pub async fn tell_fortune(&self) -> HandleResult<Fortunes> {
-        let mut items = Vec::with_capacity(16);
-
-        let mut res = self.fortune.query(&self.cli).await?;
-
-        while let Some(row) = res.try_next().await? {
-            items.push(Fortune::new(row.get(0), row.get::<String>(1)));
-        }
-
-        Ok(Fortunes::new(items))
+    #[inline]
+    pub fn fortunes(&self) -> impl Future<Output = HandleResult<Fortunes>> {
+        db::fortunes(&self.cli, &self.fortune)
     }
 }

+ 0 - 21
frameworks/Rust/xitca-web/src/db_util.rs

@@ -1,21 +0,0 @@
-use xitca_postgres::{
-    statement::{Statement, StatementNamed},
-    types::Type,
-};
-
-use crate::util::Error;
-
-pub const FORTUNE_STMT: StatementNamed = Statement::named("SELECT id,message FROM fortune", &[]);
-
-pub const WORLD_STMT: StatementNamed = Statement::named("SELECT id,randomnumber FROM world WHERE id=$1", &[Type::INT4]);
-
-pub const UPDATE_STMT: StatementNamed = Statement::named(
-    "UPDATE world SET randomnumber=w.r FROM (SELECT unnest($1) as i,unnest($2) as r) w WHERE world.id=w.i",
-    &[Type::INT4_ARRAY, Type::INT4_ARRAY],
-);
-
-#[cold]
-#[inline(never)]
-pub fn not_found() -> Error {
-    "request World does not exist".into()
-}

+ 13 - 8
frameworks/Rust/xitca-web/src/main.rs

@@ -1,4 +1,5 @@
 mod db;
+mod db_pool;
 mod ser;
 mod util;
 
@@ -16,11 +17,10 @@ use xitca_http::{
 };
 use xitca_service::{Service, ServiceExt, fn_service};
 
-use db::Client;
 use ser::{IntoResponse, Message, Request, Response, error_response};
 use util::{QueryParse, SERVER_HEADER_VALUE, State};
 
-type Ctx<'a> = Context<'a, Request<RequestBody>, State<Client>>;
+type Ctx<'a> = Context<'a, Request<RequestBody>, State<db_pool::Client>>;
 
 fn main() -> std::io::Result<()> {
     let service = Router::new()
@@ -39,7 +39,7 @@ fn main() -> std::io::Result<()> {
             "/db",
             get(fn_service(async |ctx: Ctx| {
                 let (req, state) = ctx.into_parts();
-                let world = state.client.get_world().await?;
+                let world = state.client.db().await?;
                 req.json_response(state, &world)
             })),
         )
@@ -47,7 +47,7 @@ fn main() -> std::io::Result<()> {
             "/fortunes",
             get(fn_service(async |ctx: Ctx| {
                 let (req, state) = ctx.into_parts();
-                let fortunes = state.client.tell_fortune().await?.render_once()?;
+                let fortunes = state.client.fortunes().await?.render_once()?;
                 req.html_response(fortunes)
             })),
         )
@@ -56,7 +56,7 @@ fn main() -> std::io::Result<()> {
             get(fn_service(async |ctx: Ctx| {
                 let (req, state) = ctx.into_parts();
                 let num = req.uri().query().parse_query();
-                let worlds = state.client.get_worlds(num).await?;
+                let worlds = state.client.queries(num).await?;
                 req.json_response(state, &worlds)
             })),
         )
@@ -65,11 +65,13 @@ fn main() -> std::io::Result<()> {
             get(fn_service(async |ctx: Ctx| {
                 let (req, state) = ctx.into_parts();
                 let num = req.uri().query().parse_query();
-                let worlds = state.client.update(num).await?;
+                let worlds = state.client.updates(num).await?;
                 req.json_response(state, &worlds)
             })),
         )
-        .enclosed(ContextBuilder::new(|| async { db::create().await.map(State::new) }))
+        .enclosed(ContextBuilder::new(|| async {
+            db_pool::create().await.map(State::new)
+        }))
         .enclosed_fn(async |service, req| {
             let mut res = service.call(req).await.unwrap_or_else(error_handler);
             res.headers_mut().insert(SERVER, SERVER_HEADER_VALUE);
@@ -88,6 +90,9 @@ fn error_handler(e: RouterError<util::Error>) -> Response {
     error_response(match e {
         RouterError::Match(_) => StatusCode::NOT_FOUND,
         RouterError::NotAllowed(_) => StatusCode::METHOD_NOT_ALLOWED,
-        RouterError::Service(_) => StatusCode::INTERNAL_SERVER_ERROR,
+        RouterError::Service(e) => {
+            eprintln!("Internal Error: {e}");
+            StatusCode::INTERNAL_SERVER_ERROR
+        }
     })
 }

+ 7 - 7
frameworks/Rust/xitca-web/src/main_barebone.rs

@@ -5,8 +5,8 @@
 #[global_allocator]
 static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
 
-#[path = "db_unrealistic.rs"]
 mod db;
+mod db_unrealistic;
 mod ser;
 mod util;
 
@@ -51,7 +51,7 @@ fn main() -> io::Result<()> {
                 socket.bind(addr)?;
                 let listener = socket.listen(1024)?;
 
-                let client = db::create().await.unwrap();
+                let client = db_unrealistic::create().await.unwrap();
 
                 // unrealistic http dispatcher. no spec check. no security feature.
                 let service = Dispatcher::new(handler, State::new(client));
@@ -87,7 +87,7 @@ fn main() -> io::Result<()> {
     Ok(())
 }
 
-async fn handler<'h>(req: Request<'h, State<db::Client>>, res: Response<'h>) -> Response<'h, 3> {
+async fn handler<'h>(req: Request<'h, State<db_unrealistic::Client>>, res: Response<'h>) -> Response<'h, 3> {
     // unrealistic due to no http method check
     match req.path {
         // unrealistic due to no dynamic path matching
@@ -112,7 +112,7 @@ async fn handler<'h>(req: Request<'h, State<db::Client>>, res: Response<'h>) ->
 
         // all database related categories are unrealistic. please reference db_unrealistic module for detail.
         "/fortunes" => {
-            let fortunes = req.ctx.client.tell_fortune().await.unwrap().render_once().unwrap();
+            let fortunes = req.ctx.client.fortunes().await.unwrap().render_once().unwrap();
             res.status(StatusCode::OK)
                 .header("content-type", "text/html; charset=utf-8")
                 .header("server", "X")
@@ -121,17 +121,17 @@ async fn handler<'h>(req: Request<'h, State<db::Client>>, res: Response<'h>) ->
         "/db" => {
             // unrealistic due to no error handling. any db/serialization error will cause process crash.
             // the same goes for all following unwraps on database related functions.
-            let world = req.ctx.client.get_world().await.unwrap();
+            let world = req.ctx.client.db().await.unwrap();
             json_response(res, req.ctx, &world)
         }
         p if p.starts_with("/q") => {
             let num = p["/queries?q=".len()..].parse_query();
-            let worlds = req.ctx.client.get_worlds(num).await.unwrap();
+            let worlds = req.ctx.client.queries(num).await.unwrap();
             json_response(res, req.ctx, &worlds)
         }
         p if p.starts_with("/u") => {
             let num = p["/updates?q=".len()..].parse_query();
-            let worlds = req.ctx.client.update(num).await.unwrap();
+            let worlds = req.ctx.client.updates(num).await.unwrap();
             json_response(res, req.ctx, &worlds)
         }
         _ => res.status(StatusCode::NOT_FOUND).header("server", "X").body(&[]),

+ 10 - 13
frameworks/Rust/xitca-web/src/main_orm.rs

@@ -12,8 +12,6 @@ mod orm;
 #[path = "./db_toasty.rs"]
 mod orm;
 
-use ser::{Num, World};
-use util::{HandleResult, SERVER_HEADER_VALUE};
 use xitca_web::{
     App,
     codegen::route,
@@ -22,6 +20,8 @@ use xitca_web::{
 };
 
 use orm::Pool;
+use ser::{Num, World};
+use util::{HandleResult, SERVER_HEADER_VALUE};
 
 fn main() -> std::io::Result<()> {
     App::new()
@@ -30,7 +30,10 @@ fn main() -> std::io::Result<()> {
         .at_typed(fortunes)
         .at_typed(queries)
         .at_typed(updates)
-        .map(header)
+        .map(|mut res: WebResponse| {
+            res.headers_mut().insert(SERVER, SERVER_HEADER_VALUE);
+            res
+        })
         .serve()
         .disable_vectored_write()
         .bind("0.0.0.0:8080")?
@@ -38,28 +41,22 @@ fn main() -> std::io::Result<()> {
         .wait()
 }
 
-fn header(mut res: WebResponse) -> WebResponse {
-    res.headers_mut().insert(SERVER, SERVER_HEADER_VALUE);
-    res
-}
-
 #[route("/db", method = get)]
 async fn db(StateRef(pool): StateRef<'_, Pool>) -> HandleResult<Json<World>> {
-    pool.get_world().await.map(Json)
+    pool.db().await.map(Json)
 }
 
 #[route("/fortunes", method = get)]
 async fn fortunes(StateRef(pool): StateRef<'_, Pool>) -> HandleResult<Html<String>> {
-    let html = pool.tell_fortune().await?.render_once()?;
-    Ok(Html(html))
+    pool.fortunes().await?.render_once().map(Html)
 }
 
 #[route("/queries", method = get)]
 async fn queries(Query(Num(num)): Query<Num>, StateRef(pool): StateRef<'_, Pool>) -> HandleResult<Json<Vec<World>>> {
-    pool.get_worlds(num).await.map(Json)
+    pool.queries(num).await.map(Json)
 }
 
 #[route("/updates", method = get)]
 async fn updates(Query(Num(num)): Query<Num>, StateRef(pool): StateRef<'_, Pool>) -> HandleResult<Json<Vec<World>>> {
-    pool.update(num).await.map(Json)
+    pool.updates(num).await.map(Json)
 }

+ 8 - 8
frameworks/Rust/xitca-web/src/ser.rs

@@ -13,7 +13,7 @@ use xitca_http::{
     },
 };
 
-use crate::util::Error;
+use crate::util::HandleResult;
 
 const HELLO: &str = "Hello, World!";
 const HELLO_BYTES: &[u8] = HELLO.as_bytes();
@@ -76,7 +76,7 @@ pub struct Fortunes {
 // bench to reduce resource usage of bench runner
 #[cfg(feature = "template")]
 impl Fortunes {
-    pub fn render_once(self) -> sailfish::RenderResult {
+    pub fn render_once(self) -> HandleResult<String> {
         use sailfish::runtime::{Buffer, Render};
 
         const PREFIX: &str = "<!DOCTYPE html>\n<html>\n<head><title>Fortunes</title></head>\n<body>\n<table>\n<tr><th>id</th><th>message</th></tr>\n";
@@ -199,16 +199,16 @@ pub type Response = http::Response<Once<Bytes>>;
 
 pub trait IntoResponse: Sized {
     #[cfg(any(feature = "json", feature = "perf-json"))]
-    fn json_response<C>(self, state: &crate::util::State<C>, val: &impl Serialize) -> Result<Response, Error>;
+    fn json_response<C>(self, state: &crate::util::State<C>, val: &impl Serialize) -> HandleResult<Response>;
 
-    fn text_response(self) -> Result<Response, Error>;
+    fn text_response(self) -> HandleResult<Response>;
 
-    fn html_response(self, val: String) -> Result<Response, Error>;
+    fn html_response(self, val: String) -> HandleResult<Response>;
 }
 
 impl<Ext> IntoResponse for Request<Ext> {
     #[cfg(any(feature = "json", feature = "perf-json"))]
-    fn json_response<C>(self, state: &crate::util::State<C>, val: &impl Serialize) -> Result<Response, Error> {
+    fn json_response<C>(self, state: &crate::util::State<C>, val: &impl Serialize) -> HandleResult<Response> {
         let buf = &mut *state.write_buf.borrow_mut();
         #[cfg(all(feature = "json", not(feature = "perf-json")))]
         serde_json::to_writer(xitca_http::bytes::BufMutWriter(buf), val)?;
@@ -222,13 +222,13 @@ impl<Ext> IntoResponse for Request<Ext> {
         Ok(res)
     }
 
-    fn text_response(self) -> Result<Response, Error> {
+    fn text_response(self) -> HandleResult<Response> {
         let mut res = self.into_response(const { Bytes::from_static(HELLO_BYTES) });
         res.headers_mut().insert(CONTENT_TYPE, TEXT_UTF8);
         Ok(res)
     }
 
-    fn html_response(self, val: String) -> Result<Response, Error> {
+    fn html_response(self, val: String) -> HandleResult<Response> {
         let mut res = self.into_response(Bytes::from(val));
         res.headers_mut().insert(CONTENT_TYPE, TEXT_HTML_UTF8);
         Ok(res)

+ 1 - 1
frameworks/Rust/xitca-web/xitca-web-barebone.dockerfile

@@ -1,4 +1,4 @@
-FROM rust:1.91.1
+FROM rust:1.92
 
 ADD ./ /xitca-web
 WORKDIR /xitca-web

+ 1 - 1
frameworks/Rust/xitca-web/xitca-web-diesel.dockerfile

@@ -1,4 +1,4 @@
-FROM rust:1.91.1
+FROM rust:1.92
 
 ADD ./ /xitca-web
 WORKDIR /xitca-web

+ 1 - 1
frameworks/Rust/xitca-web/xitca-web-toasty.dockerfile

@@ -1,4 +1,4 @@
-FROM rust:1.91.1
+FROM rust:1.92
 
 ADD ./ /xitca-web
 WORKDIR /xitca-web

+ 1 - 1
frameworks/Rust/xitca-web/xitca-web.dockerfile

@@ -1,4 +1,4 @@
-FROM rust:1.91.1
+FROM rust:1.92
 
 ADD ./ /xitca-web
 WORKDIR /xitca-web