Skip to main content
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.

Result Metadata

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());

Additional Information

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

1

Use Type-Safe Deserialization

Derive DeserializeRow on structs for better type safety and code maintainability.
2

Handle Empty Results

Use maybe_first_row() instead of first_row() when results might be empty.
3

Stream Large Results

For large result sets, use query_iter() or execute_iter() instead of loading all rows into memory.
4

Check Result Type

For non-SELECT queries, use result_not_rows() to verify the result is what you expect.

See Also

Build docs developers (and LLMs) love