tursodatabase/turso · error · ToSqlConversionFailure
only finite floating-point values can be bound
Error message
only finite floating-point values can be bound
What it means
The Rust batch API validates parameters before executing a batch and rejects any bound Value::Real that is NaN-adjacent infinity (f64::INFINITY or NEG_INFINITY). SQLite has no way to store IEEE infinity in its wire/file format, so binding one would silently corrupt or fail later; the library fails fast with ToSqlConversionFailure wrapping an InvalidInput io::Error.
Source
Thrown at bindings/rust/src/batch.rs:60
impl BatchStatement {
/// Create a batch statement from SQL text and parameters, accepting the
/// same parameter forms as [`Connection::execute`](crate::Connection::execute).
pub fn new(sql: impl Into<String>, params: impl IntoParams) -> Result<Self> {
Ok(Self {
sql: sql.into(),
params: params.into_params()?,
})
}
pub(crate) fn validate_params(&self) -> Result<()> {
let has_infinity = match &self.params {
Params::None => false,
Params::Positional(values) => values.iter().any(is_infinite),
Params::Named(values) => values.iter().any(|(_, value)| is_infinite(value)),
};
if has_infinity {
return Err(Error::ToSqlConversionFailure(Box::new(
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"only finite floating-point values can be bound",
),
)));
}
Ok(())
}
pub(crate) fn controls_transaction(&self) -> bool {
matches!(
first_sql_keyword(&self.sql).as_deref(),
Some("BEGIN" | "COMMIT" | "END" | "ROLLBACK" | "SAVEPOINT" | "RELEASE")
)
}
}
fn is_infinite(value: &Value) -> bool {
matches!(value, Value::Real(number) if number.is_infinite())View on GitHub (pinned to c1e5928725)
Solutions
- Check parameter values with f64::is_finite() before constructing the BatchStatement and clamp, store NULL, or return an application error instead.
- Use Option<f64> (Params None/Null) for values that may be infinite, mapping non-finite to NULL.
- If infinity must be persisted, store a sentinel TEXT/REAL value (e.g. 'Infinity') and convert on read.
Example fix
// before
let ratio = numerator / denominator; // may be inf
conn.batch([BatchStatement::new("INSERT INTO t(v) VALUES (?1)", (ratio,))?])?;
// after
let ratio = if denominator != 0.0 { numerator / denominator } else { f64::NAN };
let v = if ratio.is_finite() { Some(ratio) } else { None };
conn.batch([BatchStatement::new("INSERT INTO t(v) VALUES (?1)", (v,))?])?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_bindable(params: &[f64]) -> Result<(), String> {
params.iter().find(|v| !v.is_finite())
.map_or(Ok(()), |v| Err(format!("non-finite param: {v}")))
} Type guard
fn is_bindable_real(v: f64) -> bool { v.is_finite() } Try / catch
match stmt_result {
Err(turso::Error::ToSqlConversionFailure(e)) if e.to_string().contains("finite") => {
eprintln!("non-finite bind value: {e}"); // sanitize params and retry
}
r => r.expect("batch failed"),
} Prevention
- Filter all float params with is_finite() before constructing statements
- Map non-finite values to Option<f64> (NULL) at the data-model boundary
- Guard divisions and accumulation that can overflow to infinity
- Test numeric pipelines with extreme values (1e308*10, 0.0-divisions)
When it happens
Trigger: Calling BatchStatement::new with params containing an infinite f64 (e.g. computed via 1.0/0.0, f64::INFINITY, or overflowed arithmetic) in either positional or named Params, when the batch is later validated by validate_params during batch execution.
Common situations: Dividing by zero or accumulating overflow in Rust code that feeds values into a batch INSERT/UPDATE; deserializing JSON numbers like 1e999 into f64 infinity; log/metric aggregation producing inf and passing it straight to the database.
Related errors
- InvalidData
- UnexpectedEof
- HTTP request missing URL: no URL in request and no baseUrl i
- setAsciiStream length must be non-negative
- Error reading ASCII stream
AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31).
Data as JSON: /api/errors/f94dccd033042aac.
Report an issue: GitHub.