rquickjs_extra_sqlite/
database.rs

1use rquickjs::{class::Trace, Ctx, JsLifetime, Result};
2use rquickjs_extra_utils::result::ResultExt;
3use sqlx::{Executor, SqlitePool};
4
5use super::Statement;
6
7#[derive(Clone, Trace, JsLifetime)]
8#[rquickjs::class]
9pub struct Database {
10    #[qjs(skip_trace)]
11    pool: SqlitePool,
12}
13
14impl Database {
15    pub fn new(pool: SqlitePool) -> Self {
16        Self { pool }
17    }
18}
19
20#[rquickjs::methods(rename_all = "camelCase")]
21impl Database {
22    async fn exec(&self, ctx: Ctx<'_>, sql: String) -> Result<()> {
23        sqlx::raw_sql(&sql)
24            .execute(&self.pool)
25            .await
26            .or_throw(&ctx)?;
27        Ok(())
28    }
29
30    async fn prepare(&self, ctx: Ctx<'_>, sql: String) -> Result<Statement> {
31        let stmt = sqlx::Statement::to_owned(&self.pool.prepare(&sql).await.or_throw(&ctx)?);
32        Ok(Statement::new(stmt, self.pool.clone()))
33    }
34
35    async fn close(&mut self) -> Result<()> {
36        self.pool.close().await;
37        Ok(())
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use rquickjs::CatchResultExt;
44    use rquickjs_extra_test::{call_test, test_async_with, ModuleEvaluator};
45
46    use crate::SqliteModule;
47
48    #[tokio::test]
49    async fn test_database_exec() {
50        test_async_with(|ctx| {
51            Box::pin(async move {
52                ModuleEvaluator::eval_rust::<SqliteModule>(ctx.clone(), "sqlite")
53                    .await
54                    .unwrap();
55
56                let module = ModuleEvaluator::eval_js(
57                    ctx.clone(),
58                    "test",
59                    r#"
60                        import { open } from "sqlite";
61
62                        export async function test() {
63                            const db = await open({ inMemory: true });
64                            await db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT);");
65                            await db.exec("INSERT INTO test (name) VALUES ('test');");
66                            return "ok";
67                        }
68                    "#,
69                )
70                .await
71                .catch(&ctx)
72                .unwrap();
73
74                let result = call_test::<String, _>(&ctx, &module, ()).await;
75                assert_eq!(result, "ok");
76            })
77        })
78        .await;
79    }
80
81    #[tokio::test]
82    async fn test_database_close() {
83        test_async_with(|ctx| {
84            Box::pin(async move {
85                ModuleEvaluator::eval_rust::<SqliteModule>(ctx.clone(), "sqlite")
86                    .await
87                    .unwrap();
88
89                let module = ModuleEvaluator::eval_js(
90                    ctx.clone(),
91                    "test",
92                    r#"
93                        import { open } from "sqlite";
94
95                        export async function test() {
96                            const db = await open({ inMemory: true });
97                            await db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT);");
98                            await db.close();
99                            return "ok";
100                        }
101                    "#,
102                )
103                .await
104                .catch(&ctx)
105                .unwrap();
106
107                let result = call_test::<String, _>(&ctx, &module, ()).await;
108                assert_eq!(result, "ok");
109            })
110        })
111        .await;
112    }
113}