rquickjs_extra_sqlite/
open.rs1use 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}