The ScyllaDB Rust Driver provides powerful and type-safe mechanisms for working with query results through the QueryResult and QueryRowsResult types.
Result Types
QueryResult
QueryResult represents any kind of result frame returned from the database. It can represent both row-returning queries (SELECT) and non-row-returning queries (INSERT, UPDATE, DELETE).
let result: QueryResult = session
.query_unpaged("INSERT INTO ks.table (id, value) VALUES (1, 'text')", &[])
.await?;
// Check if result contains rows
if result.is_rows() {
println!("Query returned rows");
}
QueryRowsResult
QueryRowsResult is created from a QueryResult when you know the query returns rows. It provides methods for deserializing and iterating over rows.
let result = session
.query_unpaged("SELECT id, name FROM ks.users", &[])
.await?;
// Transform into rows result
let rows_result = result.into_rows_result()?;
Converting to QueryRowsResult
Use into_rows_result() to convert a QueryResult to QueryRowsResult:
let result = session
.query_unpaged("SELECT a, b FROM ks.table", &[])
.await?;
let rows_result = result.into_rows_result()?;
// Now you can iterate over rows
let mut rows_iter = rows_result.rows::<(i32, String)>()?;
while let Some((a, b)) = rows_iter.next().transpose()? {
println!("a: {}, b: {}", a, b);
}
Error Handling
If the result is not of Rows type, into_rows_result() returns the original QueryResult:
use scylla::response::query_result::IntoRowsResultError;
let result = session
.query_unpaged("INSERT INTO ks.table (id) VALUES (1)", &[])
.await?;
match result.into_rows_result() {
Ok(rows_result) => {
// Process rows
}
Err(IntoRowsResultError::ResultNotRows(query_result)) => {
// Not a rows result - recover the original QueryResult
println!("Query did not return rows");
}
Err(_) => {
// Other errors (deserialization failures)
}
}
Checking for Non-Rows Results
For INSERT, UPDATE, DELETE operations, verify the result doesn’t contain rows:
let result = session
.query_unpaged("INSERT INTO ks.table (id, value) VALUES (?, ?)", (1, "text"))
.await?;
// Verify this is not a rows result
result.result_not_rows()?;
Iterating Over Rows
Basic Iteration
let rows_result = session
.query_unpaged("SELECT a, b, c FROM ks.table", &[])
.await?
.into_rows_result()?;
let mut rows_iter = rows_result.rows::<(i32, i32, String)>()?;
while let Some((a, b, c)) = rows_iter.next().transpose()? {
println!("a: {}, b: {}, c: {}", a, b, c);
}
Using Custom Structs
Derive DeserializeRow for your types:
use scylla::DeserializeRow;
#[derive(Debug, DeserializeRow)]
struct User {
id: i32,
name: String,
email: Option<String>,
}
let rows_result = session
.query_unpaged("SELECT id, name, email FROM ks.users", &[])
.await?
.into_rows_result()?;
let mut rows_iter = rows_result.rows::<User>()?;
while let Some(user) = rows_iter.next().transpose()? {
println!("User: {:?}", user);
}
Untyped Row Access
Access columns by index without type information:
use scylla::value::Row;
let rows_result = session
.query_unpaged("SELECT a, b, c FROM ks.table", &[])
.await?
.into_rows_result()?;
let mut rows_iter = rows_result.rows::<Row>()?;
while let Some(row) = rows_iter.next().transpose()? {
let a = row.columns[0].as_ref().unwrap().as_int().unwrap();
let b = row.columns[1].as_ref().unwrap().as_int().unwrap();
let c = row.columns[2].as_ref().unwrap().as_text().unwrap();
println!("a: {}, b: {}, c: {}", a, b, c);
}
Accessing Specific Rows
First Row
Get the first row, returning an error if empty:
let rows_result = session
.query_unpaged("SELECT id, name FROM ks.users WHERE id = 1", &[])
.await?
.into_rows_result()?;
let (id, name): (i32, String) = rows_result.first_row()?;
println!("Found user: {} - {}", id, name);
Maybe First Row
Get the first row as an Option:
let rows_result = session
.query_unpaged("SELECT id, name FROM ks.users WHERE id = 1", &[])
.await?
.into_rows_result()?;
match rows_result.maybe_first_row::<(i32, String)>()? {
Some((id, name)) => println!("Found user: {} - {}", id, name),
None => println!("No user found"),
}
Single Row
Get the only row, ensuring there’s exactly one:
let rows_result = session
.query_unpaged("SELECT count(*) FROM ks.users", &[])
.await?
.into_rows_result()?;
let (count,): (i64,) = rows_result.single_row()?;
println!("Total users: {}", count);
single_row() returns an error if there are zero or more than one row in the result.
Row Count
let rows_result = session
.query_unpaged("SELECT * FROM ks.table", &[])
.await?
.into_rows_result()?;
println!("Number of rows: {}", rows_result.rows_num());
Column Specifications
let rows_result = session
.query_unpaged("SELECT a, b, c FROM ks.table", &[])
.await?
.into_rows_result()?;
let col_specs = rows_result.column_specs();
println!("Number of columns: {}", col_specs.len());
for spec in col_specs.iter() {
println!("Column: {}, Type: {:?}", spec.name(), spec.typ());
}
// Access column by index
if let Some(spec) = col_specs.get_by_index(0) {
println!("First column: {}", spec.name());
}
// Access column by name
if let Some((idx, spec)) = col_specs.get_by_name("a") {
println!("Column 'a' is at index {} with type {:?}", idx, spec.typ());
}
Result Size
let rows_result = session
.query_unpaged("SELECT * FROM ks.table", &[])
.await?
.into_rows_result()?;
println!("Serialized size: {} bytes", rows_result.rows_bytes_size());
Request Coordinator
Get information about which node handled the request:
let result = session
.query_unpaged("SELECT * FROM ks.table", &[])
.await?;
let coordinator = result.request_coordinator();
println!("Coordinator address: {}", coordinator.address());
println!("Coordinator shard: {:?}", coordinator.shard_info());
Warnings
Check for any warnings returned by the database:
let result = session
.query_unpaged("SELECT * FROM ks.table", &[])
.await?;
for warning in result.warnings() {
eprintln!("Warning: {}", warning);
}
Tracing ID
Get the tracing ID if tracing was enabled:
use scylla::statement::unprepared::Statement;
let mut statement = Statement::new("SELECT * FROM ks.table");
statement.set_tracing(true);
let result = session.query_unpaged(statement, &[]).await?;
if let Some(tracing_id) = result.tracing_id() {
println!("Tracing ID: {}", tracing_id);
// You can use this ID to query tracing information
let tracing_info = session.get_tracing_info(&tracing_id).await?;
println!("Tracing info: {:?}", tracing_info);
}
Streaming Results
For paged queries, use streaming APIs:
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
let mut rows_stream = session
.query_iter("SELECT a, b, c FROM ks.table", &[])
.await?
.rows_stream::<(i32, i32, String)>()?;
while let Some((a, b, c)) = rows_stream.try_next().await? {
println!("a: {}, b: {}, c: {}", a, b, c);
}
See Paged Queries for more details on streaming.
Complete Example
use scylla::client::session::{Session, SessionBuilder};
use scylla::DeserializeRow;
use futures::stream::TryStreamExt;
#[derive(Debug, DeserializeRow)]
struct Product {
id: i32,
name: String,
price: f64,
in_stock: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let session: Session = SessionBuilder::new()
.known_node("127.0.0.1:9042")
.build()
.await?;
session
.query_unpaged(
"CREATE KEYSPACE IF NOT EXISTS examples_ks \
WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}",
&[],
)
.await?;
session
.query_unpaged(
"CREATE TABLE IF NOT EXISTS examples_ks.products \
(id int, name text, price double, in_stock boolean, PRIMARY KEY (id))",
&[],
)
.await?;
// Insert some data
let insert_result = session
.query_unpaged(
"INSERT INTO examples_ks.products (id, name, price, in_stock) VALUES (?, ?, ?, ?)",
(1, "Widget", 19.99, true),
)
.await?;
// Verify it's not a rows result
insert_result.result_not_rows()?;
// Query and process results
let select_result = session
.query_unpaged("SELECT id, name, price, in_stock FROM examples_ks.products", &[])
.await?;
// Check metadata
println!("Warnings: {:?}", select_result.warnings().collect::<Vec<_>>());
// Convert to rows result
let rows_result = select_result.into_rows_result()?;
println!("Found {} products", rows_result.rows_num());
println!("Column specs:");
for spec in rows_result.column_specs().iter() {
println!(" - {}: {:?}", spec.name(), spec.typ());
}
// Iterate over rows
let mut rows_iter = rows_result.rows::<Product>()?;
while let Some(product) = rows_iter.next().transpose()? {
println!("Product: {:?}", product);
}
// Alternative: get first row
let first_product = session
.query_unpaged("SELECT id, name, price, in_stock FROM examples_ks.products LIMIT 1", &[])
.await?
.into_rows_result()?
.first_row::<Product>()?;
println!("First product: {:?}", first_product);
Ok(())
}
Best Practices
Use Type-Safe Deserialization
Derive DeserializeRow on structs for better type safety and code maintainability.
Handle Empty Results
Use maybe_first_row() instead of first_row() when results might be empty.
Stream Large Results
For large result sets, use query_iter() or execute_iter() instead of loading all rows into memory.
Check Result Type
For non-SELECT queries, use result_not_rows() to verify the result is what you expect.
See Also