rquickjs_extra_sqlite/
open.rs

1use std::{
2    path::PathBuf,
3    sync::atomic::{AtomicUsize, Ordering},
4    time::Duration,
5};
6
7use either::Either;
8use rquickjs::{Ctx, FromJs, Null, Object, Result, Value};
9use rquickjs_extra_utils::result::ResultExt;
10use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
11
12use super::Database;
13
14static IN_MEMORY_DB_SEQ: AtomicUsize = AtomicUsize::new(0);
15
16pub async fn open(ctx: Ctx<'_>, options: OpenOptions) -> Result<Database> {
17    let mut connect_options = SqliteConnectOptions::new();
18    connect_options = connect_options
19        .foreign_keys(options.foreign_keys)
20        .page_size(options.page_size)
21        .busy_timeout(options.busy_timeout)
22        .thread_name(|id| format!("quickjs-sqlite-worker-{id}"));
23    if let Some(filename) = options.filename {
24        connect_options = connect_options.filename(filename).create_if_missing(true);
25    }
26    if options.in_memory {
27        let seqno = IN_MEMORY_DB_SEQ.fetch_add(1, Ordering::Relaxed);
28        connect_options = connect_options
29            .filename(format!("file:sqlite-in-memory-{seqno}"))
30            .in_memory(true)
31            .shared_cache(true);
32    }
33    if options.wal {
34        connect_options = connect_options.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
35    }
36
37    let mut pool_options = SqlitePoolOptions::new();
38    pool_options = pool_options
39        .idle_timeout(options.idle_timeout)
40        .max_lifetime(options.max_lifetime)
41        .max_connections(options.max_connections)
42        .min_connections(options.min_connections);
43
44    let pool = pool_options
45        .connect_with(connect_options)
46        .await
47        .or_throw_msg(&ctx, "Unable to open database")?;
48    Ok(Database::new(pool))
49}
50
51#[derive(Debug, Clone)]
52pub struct OpenOptions {
53    pub filename: Option<PathBuf>,
54    pub in_memory: bool,
55    pub wal: bool,
56    pub page_size: u32,
57    pub foreign_keys: bool,
58    pub max_connections: u32,
59    pub min_connections: u32,
60    pub idle_timeout: Option<Duration>,
61    pub max_lifetime: Option<Duration>,
62    pub busy_timeout: Duration,
63}
64
65impl Default for OpenOptions {
66    fn default() -> Self {
67        Self {
68            filename: None,
69            in_memory: true,
70            wal: true,
71            page_size: 4096,
72            foreign_keys: true,
73            max_connections: 5,
74            min_connections: 0,
75            idle_timeout: None,
76            max_lifetime: Some(Duration::from_secs(60 * 60)),
77            busy_timeout: Duration::from_millis(5 * 1000),
78        }
79    }
80}
81
82impl<'js> FromJs<'js> for OpenOptions {
83    fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
84        let default = OpenOptions::default();
85        let obj = value.get::<Object<'js>>()?;
86        let filename = obj.get::<_, String>("filename").map(PathBuf::from).ok();
87        let in_memory = obj.get::<_, bool>("inMemory").unwrap_or(default.in_memory);
88        let wal = obj.get::<_, bool>("wal").unwrap_or(default.wal);
89        let page_size = obj.get::<_, u32>("pageSize").unwrap_or(default.page_size);
90        let foreign_keys = obj
91            .get::<_, bool>("foreignKeys")
92            .unwrap_or(default.foreign_keys);
93        let max_connections = obj
94            .get::<_, u32>("maxConnections")
95            .unwrap_or(default.max_connections);
96        let min_connections = obj
97            .get::<_, u32>("minConnections")
98            .unwrap_or(default.min_connections);
99        let idle_timeout = obj
100            .get::<_, Option<u64>>("idleTimeout")?
101            .map(Duration::from_secs);
102        let max_lifetime =
103            obj.get::<_, Either<Option<u64>, Null>>("maxLifetime")
104                .map(|e| match e {
105                    Either::Left(s) => s.map(Duration::from_secs).or(default.max_lifetime),
106                    Either::Right(_) => None,
107                })?;
108        let busy_timeout = obj
109            .get::<_, u64>("busyTimeout")
110            .map(Duration::from_secs)
111            .unwrap_or(default.busy_timeout);
112        Ok(Self {
113            filename,
114            in_memory,
115            wal,
116            page_size,
117            foreign_keys,
118            max_connections,
119            min_connections,
120            idle_timeout,
121            max_lifetime,
122            busy_timeout,
123        })
124    }
125}