Skip to content

Commit 49694dd

Browse files
authored
Merge pull request #2833 from calebschoepp/unify-sqlite
2 parents 4fbf872 + f5c911b commit 49694dd

File tree

8 files changed

+100
-97
lines changed

8 files changed

+100
-97
lines changed

Cargo.lock

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/factor-sqlite/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ spin-world = { path = "../world" }
1717
table = { path = "../table" }
1818
tokio = "1"
1919
toml = "0.8"
20+
tracing = { workspace = true }
2021

2122
[dev-dependencies]
2223
spin-factors-test = { path = "../factors-test" }

crates/factor-sqlite/src/host.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use spin_factors::wasmtime::component::Resource;
77
use spin_factors::{anyhow, SelfInstanceBuilder};
88
use spin_world::v1::sqlite as v1;
99
use spin_world::v2::sqlite as v2;
10+
use tracing::field::Empty;
11+
use tracing::{instrument, Level};
1012

1113
use crate::{Connection, ConnectionCreator};
1214

@@ -62,6 +64,7 @@ impl v2::Host for InstanceState {
6264

6365
#[async_trait]
6466
impl v2::HostConnection for InstanceState {
67+
#[instrument(name = "spin_sqlite.open", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", sqlite.backend = Empty))]
6568
async fn open(&mut self, database: String) -> Result<Resource<v2::Connection>, v2::Error> {
6669
if !self.allowed_databases.contains(&database) {
6770
return Err(v2::Error::AccessDenied);
@@ -70,12 +73,17 @@ impl v2::HostConnection for InstanceState {
7073
.ok_or(v2::Error::NoSuchDatabase)?
7174
.create_connection(&database)
7275
.await?;
76+
tracing::Span::current().record(
77+
"sqlite.backend",
78+
conn.summary().as_deref().unwrap_or("unknown"),
79+
);
7380
self.connections
7481
.push(conn)
7582
.map_err(|()| v2::Error::Io("too many connections opened".to_string()))
7683
.map(Resource::new_own)
7784
}
7885

86+
#[instrument(name = "spin_sqlite.execute", skip(self, connection), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", otel.name = query, sqlite.backend = Empty))]
7987
async fn execute(
8088
&mut self,
8189
connection: Resource<v2::Connection>,
@@ -86,6 +94,10 @@ impl v2::HostConnection for InstanceState {
8694
Ok(c) => c,
8795
Err(err) => return Err(err),
8896
};
97+
tracing::Span::current().record(
98+
"sqlite.backend",
99+
conn.summary().as_deref().unwrap_or("unknown"),
100+
);
89101
conn.query(&query, parameters).await
90102
}
91103

crates/sqlite-inproc/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ rusqlite = { version = "0.29.0", features = ["bundled"] }
1313
spin-factor-sqlite = { path = "../factor-sqlite" }
1414
spin-world = { path = "../world" }
1515
tokio = "1"
16-
tracing = { workspace = true }
1716

1817
[lints]
1918
workspace = true

crates/sqlite-inproc/src/lib.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@ use async_trait::async_trait;
88
use once_cell::sync::OnceCell;
99
use spin_factor_sqlite::Connection;
1010
use spin_world::v2::sqlite;
11-
use tracing::{instrument, Level};
1211

12+
/// The location of an in-process sqlite database.
1313
#[derive(Debug, Clone)]
1414
pub enum InProcDatabaseLocation {
15+
/// An in-memory sqlite database.
1516
InMemory,
17+
/// The path to the sqlite database.
1618
Path(PathBuf),
1719
}
1820

1921
impl InProcDatabaseLocation {
2022
/// Convert an optional path to a database location.
2123
///
22-
/// Ensures that the parent directory of the database exists.
24+
/// Ensures that the parent directory of the database exists. If path is None, then an in memory
25+
/// database will be used.
2326
pub fn from_path(path: Option<PathBuf>) -> anyhow::Result<Self> {
2427
match path {
2528
Some(path) => {
@@ -68,9 +71,9 @@ impl InProcConnection {
6871
}
6972
}
7073

71-
impl InProcConnection {
72-
#[instrument(name = "spin_sqlite_inproc.query", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", otel.name = query))]
73-
pub async fn query(
74+
#[async_trait]
75+
impl Connection for InProcConnection {
76+
async fn query(
7477
&self,
7578
query: &str,
7679
parameters: Vec<sqlite::Value>,
@@ -84,8 +87,7 @@ impl InProcConnection {
8487
.map_err(|e| sqlite::Error::Io(e.to_string()))?
8588
}
8689

87-
#[instrument(name = "spin_sqlite_inproc.execute_batch", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", db.statements = statements))]
88-
pub async fn execute_batch(&self, statements: &str) -> anyhow::Result<()> {
90+
async fn execute_batch(&self, statements: &str) -> anyhow::Result<()> {
8991
let connection = self.db_connection()?;
9092
let statements = statements.to_owned();
9193
tokio::task::spawn_blocking(move || {
@@ -97,21 +99,6 @@ impl InProcConnection {
9799
.context("failed to spawn blocking task")?;
98100
Ok(())
99101
}
100-
}
101-
102-
#[async_trait]
103-
impl Connection for InProcConnection {
104-
async fn query(
105-
&self,
106-
query: &str,
107-
parameters: Vec<sqlite::Value>,
108-
) -> Result<sqlite::QueryResult, sqlite::Error> {
109-
self.query(query, parameters).await
110-
}
111-
112-
async fn execute_batch(&self, statements: &str) -> anyhow::Result<()> {
113-
self.execute_batch(statements).await
114-
}
115102

116103
fn summary(&self) -> Option<String> {
117104
Some(match &self.location {
@@ -121,6 +108,7 @@ impl Connection for InProcConnection {
121108
}
122109
}
123110

111+
// This function lives outside the query function to make it more readable.
124112
fn execute_query(
125113
connection: &Mutex<rusqlite::Connection>,
126114
query: &str,

crates/sqlite-libsql/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ async-trait = "0.1.68"
1111
# libsqlite3-sys as used by spin-sqlite-inproc.
1212
libsql = { version = "0.3.2", features = ["remote"], default-features = false }
1313
rusqlite = { version = "0.29.0", features = ["bundled"] }
14+
spin-factor-sqlite = { path = "../factor-sqlite" }
1415
spin-world = { path = "../world" }
1516
sqlparser = "0.34"
1617
tokio = { version = "1", features = ["full"] }
17-
tracing = { workspace = true }
1818

1919
[lints]
2020
workspace = true

crates/sqlite-libsql/src/lib.rs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,77 @@
1+
use anyhow::Context;
2+
use async_trait::async_trait;
3+
use spin_factor_sqlite::Connection;
4+
use spin_world::v2::sqlite as v2;
15
use spin_world::v2::sqlite::{self, RowResult};
2-
use tracing::{instrument, Level};
6+
use tokio::sync::OnceCell;
7+
8+
/// A lazy wrapper around a [`LibSqlConnection`] that implements the [`Connection`] trait.
9+
pub struct LazyLibSqlConnection {
10+
url: String,
11+
token: String,
12+
// Since the libSQL client can only be created asynchronously, we wait until
13+
// we're in the `Connection` implementation to create. Since we only want to do
14+
// this once, we use a `OnceCell` to store it.
15+
inner: OnceCell<LibSqlConnection>,
16+
}
17+
18+
impl LazyLibSqlConnection {
19+
pub fn new(url: String, token: String) -> Self {
20+
Self {
21+
url,
22+
token,
23+
inner: OnceCell::new(),
24+
}
25+
}
26+
27+
pub async fn get_or_create_connection(&self) -> Result<&LibSqlConnection, v2::Error> {
28+
self.inner
29+
.get_or_try_init(|| async {
30+
LibSqlConnection::create(self.url.clone(), self.token.clone())
31+
.await
32+
.context("failed to create SQLite client")
33+
})
34+
.await
35+
.map_err(|_| v2::Error::InvalidConnection)
36+
}
37+
}
38+
39+
#[async_trait]
40+
impl Connection for LazyLibSqlConnection {
41+
async fn query(
42+
&self,
43+
query: &str,
44+
parameters: Vec<v2::Value>,
45+
) -> Result<v2::QueryResult, v2::Error> {
46+
let client = self.get_or_create_connection().await?;
47+
client.query(query, parameters).await
48+
}
49+
50+
async fn execute_batch(&self, statements: &str) -> anyhow::Result<()> {
51+
let client = self.get_or_create_connection().await?;
52+
client.execute_batch(statements).await
53+
}
54+
55+
fn summary(&self) -> Option<String> {
56+
Some(format!("libSQL at {}", self.url))
57+
}
58+
}
359

60+
/// An open connection to a libSQL server.
461
#[derive(Clone)]
5-
pub struct LibsqlClient {
62+
pub struct LibSqlConnection {
663
inner: libsql::Connection,
764
}
865

9-
impl LibsqlClient {
10-
#[instrument(name = "spin_sqlite_libsql.create_connection", skip(token), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite"))]
66+
impl LibSqlConnection {
1167
pub async fn create(url: String, token: String) -> anyhow::Result<Self> {
1268
let db = libsql::Builder::new_remote(url, token).build().await?;
1369
let inner = db.connect()?;
1470
Ok(Self { inner })
1571
}
1672
}
1773

18-
impl LibsqlClient {
19-
#[instrument(name = "spin_sqlite_libsql.query", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", otel.name = query))]
74+
impl LibSqlConnection {
2075
pub async fn query(
2176
&self,
2277
query: &str,
@@ -36,7 +91,6 @@ impl LibsqlClient {
3691
})
3792
}
3893

39-
#[instrument(name = "spin_sqlite_libsql.execute_batch", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", db.statements = statements))]
4094
pub async fn execute_batch(&self, statements: &str) -> anyhow::Result<()> {
4195
self.inner.execute_batch(statements).await?;
4296

0 commit comments

Comments
 (0)