Skip to content

Commit eb7a06d

Browse files
authored
Merge pull request #3095 from itowlson/sqlite-row-count-row-id
SQLite affected row count and last_insert_rowid
2 parents bccd3b7 + b4df1bc commit eb7a06d

File tree

12 files changed

+346
-77
lines changed

12 files changed

+346
-77
lines changed

crates/factor-sqlite/src/host.rs

Lines changed: 170 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::sync::Arc;
33

44
use spin_factors::wasmtime::component::Resource;
55
use spin_factors::{anyhow, SelfInstanceBuilder};
6+
use spin_world::spin::sqlite::sqlite as v3;
67
use spin_world::v1::sqlite as v1;
78
use spin_world::v2::sqlite as v2;
89
use tracing::field::Empty;
@@ -34,40 +35,24 @@ impl InstanceState {
3435
}
3536

3637
/// Get a connection for a given database label.
37-
fn get_connection(
38+
fn get_connection<T: 'static>(
3839
&self,
39-
connection: Resource<v2::Connection>,
40-
) -> Result<&dyn Connection, v2::Error> {
40+
connection: Resource<T>,
41+
) -> Result<&dyn Connection, v3::Error> {
4142
self.connections
4243
.get(connection.rep())
4344
.map(|conn| conn.as_ref())
44-
.ok_or(v2::Error::InvalidConnection)
45+
.ok_or(v3::Error::InvalidConnection)
4546
}
4647

47-
/// Get the set of allowed databases.
48-
pub fn allowed_databases(&self) -> &HashSet<String> {
49-
&self.allowed_databases
50-
}
51-
}
52-
53-
impl SelfInstanceBuilder for InstanceState {}
54-
55-
impl v2::Host for InstanceState {
56-
fn convert_error(&mut self, error: v2::Error) -> anyhow::Result<v2::Error> {
57-
Ok(error)
58-
}
59-
}
60-
61-
impl v2::HostConnection for InstanceState {
62-
#[instrument(name = "spin_sqlite.open", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", sqlite.backend = Empty))]
63-
async fn open(&mut self, database: String) -> Result<Resource<v2::Connection>, v2::Error> {
48+
async fn open_impl<T: 'static>(&mut self, database: String) -> Result<Resource<T>, v3::Error> {
6449
if !self.allowed_databases.contains(&database) {
65-
return Err(v2::Error::AccessDenied);
50+
return Err(v3::Error::AccessDenied);
6651
}
6752
let conn = self
6853
.connection_creators
6954
.get(&database)
70-
.ok_or(v2::Error::NoSuchDatabase)?
55+
.ok_or(v3::Error::NoSuchDatabase)?
7156
.create_connection(&database)
7257
.await?;
7358
tracing::Span::current().record(
@@ -76,26 +61,117 @@ impl v2::HostConnection for InstanceState {
7661
);
7762
self.connections
7863
.push(conn)
79-
.map_err(|()| v2::Error::Io("too many connections opened".to_string()))
64+
.map_err(|()| v3::Error::Io("too many connections opened".to_string()))
8065
.map(Resource::new_own)
8166
}
8267

68+
async fn execute_impl<T: 'static>(
69+
&mut self,
70+
connection: Resource<T>,
71+
query: String,
72+
parameters: Vec<v3::Value>,
73+
) -> Result<v3::QueryResult, v3::Error> {
74+
let conn = self.get_connection(connection)?;
75+
tracing::Span::current().record(
76+
"sqlite.backend",
77+
conn.summary().as_deref().unwrap_or("unknown"),
78+
);
79+
conn.query(&query, parameters).await
80+
}
81+
82+
/// Get the set of allowed databases.
83+
pub fn allowed_databases(&self) -> &HashSet<String> {
84+
&self.allowed_databases
85+
}
86+
}
87+
88+
impl SelfInstanceBuilder for InstanceState {}
89+
90+
impl v3::Host for InstanceState {
91+
fn convert_error(&mut self, error: v3::Error) -> anyhow::Result<v3::Error> {
92+
Ok(error)
93+
}
94+
}
95+
96+
impl v3::HostConnection for InstanceState {
97+
#[instrument(name = "spin_sqlite.open", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", sqlite.backend = Empty))]
98+
async fn open(&mut self, database: String) -> Result<Resource<v3::Connection>, v3::Error> {
99+
self.open_impl(database).await
100+
}
101+
83102
#[instrument(name = "spin_sqlite.execute", skip(self, connection, parameters), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", otel.name = query, sqlite.backend = Empty))]
84103
async fn execute(
85104
&mut self,
86-
connection: Resource<v2::Connection>,
105+
connection: Resource<v3::Connection>,
87106
query: String,
88-
parameters: Vec<v2::Value>,
89-
) -> Result<v2::QueryResult, v2::Error> {
107+
parameters: Vec<v3::Value>,
108+
) -> Result<v3::QueryResult, v3::Error> {
109+
self.execute_impl(connection, query, parameters).await
110+
}
111+
112+
async fn changes(
113+
&mut self,
114+
connection: Resource<v3::Connection>,
115+
) -> spin_factors::wasmtime::Result<u64> {
90116
let conn = match self.get_connection(connection) {
91117
Ok(c) => c,
92-
Err(err) => return Err(err),
118+
Err(err) => return Err(err.into()),
93119
};
94120
tracing::Span::current().record(
95121
"sqlite.backend",
96122
conn.summary().as_deref().unwrap_or("unknown"),
97123
);
98-
conn.query(&query, parameters).await
124+
conn.changes().await.map_err(|e| e.into())
125+
}
126+
127+
async fn last_insert_rowid(
128+
&mut self,
129+
connection: Resource<v3::Connection>,
130+
) -> spin_factors::wasmtime::Result<i64> {
131+
let conn = match self.get_connection(connection) {
132+
Ok(c) => c,
133+
Err(err) => return Err(err.into()),
134+
};
135+
tracing::Span::current().record(
136+
"sqlite.backend",
137+
conn.summary().as_deref().unwrap_or("unknown"),
138+
);
139+
conn.last_insert_rowid().await.map_err(|e| e.into())
140+
}
141+
142+
async fn drop(&mut self, connection: Resource<v3::Connection>) -> anyhow::Result<()> {
143+
let _ = self.connections.remove(connection.rep());
144+
Ok(())
145+
}
146+
}
147+
148+
impl v2::Host for InstanceState {
149+
fn convert_error(&mut self, error: v2::Error) -> anyhow::Result<v2::Error> {
150+
Ok(error)
151+
}
152+
}
153+
154+
impl v2::HostConnection for InstanceState {
155+
#[instrument(name = "spin_sqlite.open", skip(self), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", sqlite.backend = Empty))]
156+
async fn open(&mut self, database: String) -> Result<Resource<v2::Connection>, v2::Error> {
157+
self.open_impl(database).await.map_err(to_v2_error)
158+
}
159+
160+
#[instrument(name = "spin_sqlite.execute", skip(self, connection, parameters), err(level = Level::INFO), fields(otel.kind = "client", db.system = "sqlite", otel.name = query, sqlite.backend = Empty))]
161+
async fn execute(
162+
&mut self,
163+
connection: Resource<v2::Connection>,
164+
query: String,
165+
parameters: Vec<v2::Value>,
166+
) -> Result<v2::QueryResult, v2::Error> {
167+
self.execute_impl(
168+
connection,
169+
query,
170+
parameters.into_iter().map(from_v2_value).collect(),
171+
)
172+
.await
173+
.map(to_v2_query_result)
174+
.map_err(to_v2_error)
99175
}
100176

101177
async fn drop(&mut self, connection: Resource<v2::Connection>) -> anyhow::Result<()> {
@@ -106,7 +182,7 @@ impl v2::HostConnection for InstanceState {
106182

107183
impl v1::Host for InstanceState {
108184
async fn open(&mut self, database: String) -> Result<u32, v1::Error> {
109-
let result = <Self as v2::HostConnection>::open(self, database).await;
185+
let result = <Self as v3::HostConnection>::open(self, database).await;
110186
result.map_err(to_legacy_error).map(|s| s.rep())
111187
}
112188

@@ -117,7 +193,7 @@ impl v1::Host for InstanceState {
117193
parameters: Vec<spin_world::v1::sqlite::Value>,
118194
) -> Result<spin_world::v1::sqlite::QueryResult, v1::Error> {
119195
let this = Resource::new_borrow(connection);
120-
let result = <Self as v2::HostConnection>::execute(
196+
let result = <Self as v3::HostConnection>::execute(
121197
self,
122198
this,
123199
query,
@@ -136,45 +212,88 @@ impl v1::Host for InstanceState {
136212
}
137213
}
138214

139-
fn to_legacy_error(error: v2::Error) -> v1::Error {
215+
fn to_v2_error(error: v3::Error) -> v2::Error {
140216
match error {
141-
v2::Error::NoSuchDatabase => v1::Error::NoSuchDatabase,
142-
v2::Error::AccessDenied => v1::Error::AccessDenied,
143-
v2::Error::InvalidConnection => v1::Error::InvalidConnection,
144-
v2::Error::DatabaseFull => v1::Error::DatabaseFull,
145-
v2::Error::Io(s) => v1::Error::Io(s),
217+
v3::Error::NoSuchDatabase => v2::Error::NoSuchDatabase,
218+
v3::Error::AccessDenied => v2::Error::AccessDenied,
219+
v3::Error::InvalidConnection => v2::Error::InvalidConnection,
220+
v3::Error::DatabaseFull => v2::Error::DatabaseFull,
221+
v3::Error::Io(s) => v2::Error::Io(s),
222+
}
223+
}
224+
225+
fn to_legacy_error(error: v3::Error) -> v1::Error {
226+
match error {
227+
v3::Error::NoSuchDatabase => v1::Error::NoSuchDatabase,
228+
v3::Error::AccessDenied => v1::Error::AccessDenied,
229+
v3::Error::InvalidConnection => v1::Error::InvalidConnection,
230+
v3::Error::DatabaseFull => v1::Error::DatabaseFull,
231+
v3::Error::Io(s) => v1::Error::Io(s),
232+
}
233+
}
234+
235+
fn to_v2_query_result(result: v3::QueryResult) -> v2::QueryResult {
236+
v2::QueryResult {
237+
columns: result.columns,
238+
rows: result.rows.into_iter().map(to_v2_row_result).collect(),
146239
}
147240
}
148241

149-
fn to_legacy_query_result(result: v2::QueryResult) -> v1::QueryResult {
242+
fn to_legacy_query_result(result: v3::QueryResult) -> v1::QueryResult {
150243
v1::QueryResult {
151244
columns: result.columns,
152245
rows: result.rows.into_iter().map(to_legacy_row_result).collect(),
153246
}
154247
}
155248

156-
fn to_legacy_row_result(result: v2::RowResult) -> v1::RowResult {
249+
fn to_v2_row_result(result: v3::RowResult) -> v2::RowResult {
250+
v2::RowResult {
251+
values: result.values.into_iter().map(to_v2_value).collect(),
252+
}
253+
}
254+
255+
fn to_legacy_row_result(result: v3::RowResult) -> v1::RowResult {
157256
v1::RowResult {
158257
values: result.values.into_iter().map(to_legacy_value).collect(),
159258
}
160259
}
161260

162-
fn to_legacy_value(value: v2::Value) -> v1::Value {
261+
fn to_v2_value(value: v3::Value) -> v2::Value {
262+
match value {
263+
v3::Value::Integer(i) => v2::Value::Integer(i),
264+
v3::Value::Real(r) => v2::Value::Real(r),
265+
v3::Value::Text(t) => v2::Value::Text(t),
266+
v3::Value::Blob(b) => v2::Value::Blob(b),
267+
v3::Value::Null => v2::Value::Null,
268+
}
269+
}
270+
271+
fn to_legacy_value(value: v3::Value) -> v1::Value {
272+
match value {
273+
v3::Value::Integer(i) => v1::Value::Integer(i),
274+
v3::Value::Real(r) => v1::Value::Real(r),
275+
v3::Value::Text(t) => v1::Value::Text(t),
276+
v3::Value::Blob(b) => v1::Value::Blob(b),
277+
v3::Value::Null => v1::Value::Null,
278+
}
279+
}
280+
281+
fn from_v2_value(value: v2::Value) -> v3::Value {
163282
match value {
164-
v2::Value::Integer(i) => v1::Value::Integer(i),
165-
v2::Value::Real(r) => v1::Value::Real(r),
166-
v2::Value::Text(t) => v1::Value::Text(t),
167-
v2::Value::Blob(b) => v1::Value::Blob(b),
168-
v2::Value::Null => v1::Value::Null,
283+
v2::Value::Integer(i) => v3::Value::Integer(i),
284+
v2::Value::Real(r) => v3::Value::Real(r),
285+
v2::Value::Text(t) => v3::Value::Text(t),
286+
v2::Value::Blob(b) => v3::Value::Blob(b),
287+
v2::Value::Null => v3::Value::Null,
169288
}
170289
}
171290

172-
fn from_legacy_value(value: v1::Value) -> v2::Value {
291+
fn from_legacy_value(value: v1::Value) -> v3::Value {
173292
match value {
174-
v1::Value::Integer(i) => v2::Value::Integer(i),
175-
v1::Value::Real(r) => v2::Value::Real(r),
176-
v1::Value::Text(t) => v2::Value::Text(t),
177-
v1::Value::Blob(b) => v2::Value::Blob(b),
178-
v1::Value::Null => v2::Value::Null,
293+
v1::Value::Integer(i) => v3::Value::Integer(i),
294+
v1::Value::Real(r) => v3::Value::Real(r),
295+
v1::Value::Text(t) => v3::Value::Text(t),
296+
v1::Value::Blob(b) => v3::Value::Blob(b),
297+
v1::Value::Null => v3::Value::Null,
179298
}
180299
}

crates/factor-sqlite/src/lib.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use host::InstanceState;
99
use async_trait::async_trait;
1010
use spin_factors::{anyhow, Factor};
1111
use spin_locked_app::MetadataKey;
12+
use spin_world::spin::sqlite::sqlite as v3;
1213
use spin_world::v1::sqlite as v1;
1314
use spin_world::v2::sqlite as v2;
1415

@@ -37,6 +38,7 @@ impl Factor for SqliteFactor {
3738
) -> anyhow::Result<()> {
3839
ctx.link_bindings(v1::add_to_linker)?;
3940
ctx.link_bindings(v2::add_to_linker)?;
41+
ctx.link_bindings(v3::add_to_linker)?;
4042
Ok(())
4143
}
4244

@@ -152,7 +154,7 @@ impl AppState {
152154
pub async fn get_connection(
153155
&self,
154156
label: &str,
155-
) -> Option<Result<Box<dyn Connection>, v2::Error>> {
157+
) -> Option<Result<Box<dyn Connection>, v3::Error>> {
156158
let connection = self
157159
.connection_creators
158160
.get(label)?
@@ -178,7 +180,7 @@ pub trait ConnectionCreator: Send + Sync {
178180
async fn create_connection(
179181
&self,
180182
label: &str,
181-
) -> Result<Box<dyn Connection + 'static>, v2::Error>;
183+
) -> Result<Box<dyn Connection + 'static>, v3::Error>;
182184
}
183185

184186
#[async_trait]
@@ -189,9 +191,9 @@ where
189191
async fn create_connection(
190192
&self,
191193
label: &str,
192-
) -> Result<Box<dyn Connection + 'static>, v2::Error> {
194+
) -> Result<Box<dyn Connection + 'static>, v3::Error> {
193195
let _ = label;
194-
(self)().map_err(|_| v2::Error::InvalidConnection)
196+
(self)().map_err(|_| v3::Error::InvalidConnection)
195197
}
196198
}
197199

@@ -201,11 +203,15 @@ pub trait Connection: Send + Sync {
201203
async fn query(
202204
&self,
203205
query: &str,
204-
parameters: Vec<v2::Value>,
205-
) -> Result<v2::QueryResult, v2::Error>;
206+
parameters: Vec<v3::Value>,
207+
) -> Result<v3::QueryResult, v3::Error>;
206208

207209
async fn execute_batch(&self, statements: &str) -> anyhow::Result<()>;
208210

211+
async fn changes(&self) -> Result<u64, v3::Error>;
212+
213+
async fn last_insert_rowid(&self) -> Result<i64, v3::Error>;
214+
209215
/// A human-readable summary of the connection's configuration
210216
///
211217
/// Example: "libSQL at libsql://example.com"

0 commit comments

Comments
 (0)