use crate::{Executor, Result};
use toasty_core::{
Error,
driver::{
Capability,
operation::{RawSql, RawSqlRet, TypedValue},
},
schema::db,
stmt::{self, Value},
};
pub struct Statement {
sql: String,
params: Vec<Param>,
}
pub struct Query {
sql: String,
params: Vec<Param>,
ret: RawSqlRet,
}
#[derive(Debug, Clone)]
enum Param {
Infer(Value),
Typed(TypedValue),
}
pub fn statement(sql: impl Into<String>) -> Statement {
Statement {
sql: sql.into(),
params: vec![],
}
}
pub fn query(sql: impl Into<String>) -> Query {
Query {
sql: sql.into(),
params: vec![],
ret: RawSqlRet::Infer,
}
}
impl Statement {
pub fn bind(mut self, value: impl Into<Value>) -> Self {
self.params.push(Param::Infer(value.into()));
self
}
pub fn bind_typed(mut self, value: impl Into<Value>, ty: db::Type) -> Self {
self.params.push(Param::Typed(TypedValue {
value: value.into(),
ty,
}));
self
}
pub async fn exec(self, executor: &mut dyn Executor) -> Result<u64> {
let raw = into_raw_sql(
self.sql,
self.params,
RawSqlRet::None,
executor.capability(),
)?;
let response = executor.exec_raw_sql(raw).await?;
Ok(response.values.into_count())
}
}
impl Query {
pub fn bind(mut self, value: impl Into<Value>) -> Self {
self.params.push(Param::Infer(value.into()));
self
}
pub fn bind_typed(mut self, value: impl Into<Value>, ty: db::Type) -> Self {
self.params.push(Param::Typed(TypedValue {
value: value.into(),
ty,
}));
self
}
pub fn column_types(mut self, types: impl IntoIterator<Item = stmt::Type>) -> Self {
self.ret = RawSqlRet::Types(types.into_iter().collect());
self
}
pub async fn exec(self, executor: &mut dyn Executor) -> Result<Vec<Value>> {
let raw = into_raw_sql(self.sql, self.params, self.ret, executor.capability())?;
let response = executor.exec_raw_sql(raw).await?;
match response.values.collect_as_value().await? {
Value::List(items) => Ok(items),
value => Err(toasty_core::Error::invalid_result(format!(
"raw SQL query expected a list of rows, got {value:?}"
))),
}
}
}
fn into_raw_sql(
sql: String,
params: Vec<Param>,
ret: RawSqlRet,
capability: &Capability,
) -> Result<RawSql> {
if !capability.sql {
return Err(Error::unsupported_feature(
"raw SQL is only supported by SQL drivers",
));
}
Ok(RawSql {
sql,
params: params
.into_iter()
.map(|param| param.into_typed(capability))
.collect::<Result<_>>()?,
ret,
})
}
impl Param {
fn into_typed(self, capability: &Capability) -> Result<TypedValue> {
match self {
Param::Infer(value) => infer_typed_value(value, capability),
Param::Typed(value) => Ok(value),
}
}
}
fn infer_typed_value(value: Value, capability: &Capability) -> Result<TypedValue> {
let ty = value.infer_ty();
let ty = db::Type::from_app(&ty, None, &capability.storage_types).map_err(|err| {
err.context(Error::invalid_statement(format!(
"cannot infer raw SQL bind type for {value:?}; use bind_typed"
)))
})?;
Ok(TypedValue { value, ty })
}