use js_sys::Array;
use sqlx_cloudflare_core::js::{database_error, from_js, to_js_array};
use sqlx_cloudflare_core::{QueryResult, Value};
use sqlx_core::error::Error;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use worker::worker_sys::types::{SqlStorage as SqlStorageSys, SqlStorageCursor};
use crate::{DoArgumentValue, DoRow};
pub(crate) fn fetch(
storage: &worker::SqlStorage,
sql: &str,
arguments: &[DoArgumentValue],
limit: Option<usize>,
) -> Result<(Vec<DoRow>, u64), Error> {
let cursor = exec(storage, sql, arguments)?;
let names = cursor
.column_names()
.iter()
.map(|name| {
name.as_string().ok_or_else(|| {
Error::Protocol(
"Durable Object storage returned a column name that is not a string".into(),
)
})
})
.collect::<Result<Vec<_>, _>>()?;
let limit = limit.unwrap_or(usize::MAX);
let mut rows = Vec::new();
let raw = cursor.raw();
loop {
let next = raw.next().map_err(|error| database_error(&error))?;
if next.done() {
break;
}
if rows.len() < limit {
let row: Array = next.value().dyn_into().map_err(|_| {
Error::Protocol("Durable Object storage returned a row that is not an array".into())
})?;
rows.push(
row.iter()
.map(|value| from_js(&value))
.collect::<Result<Vec<Value>, _>>()?,
);
}
}
Ok((
DoRow::from_result(names, rows)?,
whole_number_u64(cursor.rows_written()),
))
}
pub(crate) fn run(
storage: &worker::SqlStorage,
sql: &str,
arguments: &[DoArgumentValue],
) -> Result<QueryResult, Error> {
execute(storage, sql, arguments, Some(0)).map(|(_, result)| result)
}
pub(crate) fn execute(
storage: &worker::SqlStorage,
sql: &str,
arguments: &[DoArgumentValue],
limit: Option<usize>,
) -> Result<(Vec<DoRow>, QueryResult), Error> {
let (rows, rows_written) = fetch(storage, sql, arguments, limit)?;
if rows_written == 0 {
return Ok((rows, QueryResult::default()));
}
let (counts, _) = fetch(storage, "SELECT changes(), last_insert_rowid()", &[], None)?;
let counts = counts.first().ok_or_else(|| {
Error::Protocol("`SELECT changes(), last_insert_rowid()` returned no row".into())
})?;
let changes = counts.values[0].int().map_err(Error::Decode)?;
let rowid = counts.values[1].int().map_err(Error::Decode)?;
let result = QueryResult::new(
u64::try_from(changes).map_err(|error| Error::Decode(error.into()))?,
Some(rowid),
);
Ok((rows, result))
}
fn exec(
storage: &worker::SqlStorage,
sql: &str,
arguments: &[DoArgumentValue],
) -> Result<SqlStorageCursor, Error> {
let storage: &SqlStorageSys = AsRef::<JsValue>::as_ref(storage).unchecked_ref();
let arguments = to_js_array(arguments)?;
storage
.exec(sql, arguments)
.map_err(|error| database_error(&error))
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "a count of rows written is whole, non-negative and far below 2^53"
)]
fn whole_number_u64(value: f64) -> u64 {
value as u64
}