Skip to main content
CQL provides four collection types for storing multiple values: lists, sets, maps, and vectors. These collections can contain any CQL type, including other collections (if frozen).

Lists

A list is an ordered collection that can contain duplicate elements.

Basic Usage

use scylla::Session;

// Insert a list
let tags = vec!["rust", "database", "nosql"];

session.query_unpaged(
    "INSERT INTO articles (id, tags) VALUES (?, ?)",
    (1, tags),
).await?;

// Read back as Vec
let result = session
    .query_unpaged("SELECT tags FROM articles WHERE id = 1", &[])
    .await?;

if let Some(row) = result.rows()?.first() {
    let tags: Vec<String> = row.columns[0]
        .as_ref()
        .and_then(|v| v.as_list())
        .unwrap()
        .iter()
        .map(|v| v.as_text().unwrap().clone())
        .collect();
    println!("Tags: {:?}", tags);
}

List Operations

// Append to list
session.query_unpaged(
    "UPDATE articles SET tags = tags + ? WHERE id = ?",
    (vec!["scylladb"], 1),
).await?;

// Prepend to list
session.query_unpaged(
    "UPDATE articles SET tags = ? + tags WHERE id = ?",
    (vec!["tutorial"], 1),
).await?;

// Remove from list
session.query_unpaged(
    "UPDATE articles SET tags = tags - ? WHERE id = ?",
    (vec!["nosql"], 1),
).await?;

// Replace element by index
session.query_unpaged(
    "UPDATE articles SET tags[0] = ? WHERE id = ?",
    ("rust-lang", 1),
).await?;

// Delete element by index
session.query_unpaged(
    "DELETE tags[0] FROM articles WHERE id = ?",
    (1,),
).await?;

List of Various Types

// List of integers
let numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
session.query_unpaged(
    "INSERT INTO data (id, numbers) VALUES (?, ?)",
    (1, numbers),
).await?;

// List of UUIDs
use uuid::Uuid;
let ids: Vec<Uuid> = vec![Uuid::new_v4(), Uuid::new_v4()];
session.query_unpaged(
    "INSERT INTO data (id, uuids) VALUES (?, ?)",
    (2, ids),
).await?;

// List of timestamps
use chrono::{DateTime, Utc};
let timestamps: Vec<DateTime<Utc>> = vec![Utc::now(), Utc::now()];
session.query_unpaged(
    "INSERT INTO data (id, timestamps) VALUES (?, ?)",
    (3, timestamps),
).await?;

Sets

A set is an unordered collection of unique elements.

Basic Usage

use std::collections::HashSet;

// Insert a set using HashSet
let mut tags = HashSet::new();
tags.insert("rust".to_string());
tags.insert("database".to_string());
tags.insert("nosql".to_string());

session.query_unpaged(
    "INSERT INTO articles (id, tags) VALUES (?, ?)",
    (1, tags),
).await?;

// Or use Vec (duplicates will be removed)
let tags = vec!["rust", "database", "nosql", "rust"]; // duplicate "rust"
session.query_unpaged(
    "INSERT INTO articles (id, tags) VALUES (?, ?)",
    (2, tags),
).await?;

Reading Sets

use std::collections::{HashSet, BTreeSet};

// Read as HashSet
let result = session
    .query_unpaged("SELECT tags FROM articles WHERE id = 1", &[])
    .await?;

if let Some(row) = result.rows()?.first() {
    let tags: HashSet<String> = row.columns[0]
        .as_ref()
        .and_then(|v| v.as_set())
        .unwrap()
        .iter()
        .map(|v| v.as_text().unwrap().clone())
        .collect();
    println!("Tags: {:?}", tags);
}

// Or read as BTreeSet (sorted)
let tags: BTreeSet<String> = /* deserialize */;

// Or read as Vec
let tags: Vec<String> = /* deserialize */;

Set Operations

// Add elements to set
session.query_unpaged(
    "UPDATE articles SET tags = tags + ? WHERE id = ?",
    (vec!["scylladb", "distributed"], 1),
).await?;

// Remove elements from set
session.query_unpaged(
    "UPDATE articles SET tags = tags - ? WHERE id = ?",
    (vec!["nosql"], 1),
).await?;

Maps

A map is an unordered collection of key-value pairs.

Basic Usage

use std::collections::HashMap;

// Insert a map
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "Alice".to_string());
metadata.insert("category".to_string(), "Technology".to_string());
metadata.insert("views".to_string(), "1000".to_string());

session.query_unpaged(
    "INSERT INTO articles (id, metadata) VALUES (?, ?)",
    (1, metadata),
).await?;

Reading Maps

use std::collections::{HashMap, BTreeMap};

// Read as HashMap
let result = session
    .query_unpaged("SELECT metadata FROM articles WHERE id = 1", &[])
    .await?;

if let Some(row) = result.rows()?.first() {
    let metadata: HashMap<String, String> = row.columns[0]
        .as_ref()
        .and_then(|v| v.as_map())
        .unwrap()
        .iter()
        .map(|(k, v)| (
            k.as_text().unwrap().clone(),
            v.as_text().unwrap().clone(),
        ))
        .collect();
    println!("Metadata: {:?}", metadata);
}

// Or read as BTreeMap (sorted by key)
let metadata: BTreeMap<String, String> = /* deserialize */;

Map Operations

// Add/update entries
let updates = vec![("editor", "Bob"), ("status", "published")];
session.query_unpaged(
    "UPDATE articles SET metadata = metadata + ? WHERE id = ?",
    (updates, 1),
).await?;

// Remove entries by key
session.query_unpaged(
    "DELETE metadata['status'] FROM articles WHERE id = ?",
    (1,),
).await?;

// Update specific key
session.query_unpaged(
    "UPDATE articles SET metadata['views'] = ? WHERE id = ?",
    ("2000", 1),
).await?;

Maps with Different Value Types

use std::collections::HashMap;

// Map with integer values
let scores: HashMap<String, i32> = [
    ("alice".to_string(), 100),
    ("bob".to_string(), 95),
].iter().cloned().collect();

session.query_unpaged(
    "INSERT INTO leaderboard (id, scores) VALUES (?, ?)",
    (1, scores),
).await?;

// Map with UUID keys
use uuid::Uuid;
let user_data: HashMap<Uuid, String> = [
    (Uuid::new_v4(), "Alice".to_string()),
    (Uuid::new_v4(), "Bob".to_string()),
].iter().cloned().collect();

session.query_unpaged(
    "INSERT INTO data (id, user_data) VALUES (?, ?)",
    (1, user_data),
).await?;

Vectors

A vector is a fixed-size list of elements of the same type. Unlike lists, vectors have a predefined dimension.

Basic Usage

// Vector of floats (e.g., for ML embeddings)
let embedding: Vec<f32> = vec![0.1, 0.2, 0.3, 0.4, 0.5];

// Create table with vector type
session.query_unpaged(
    "CREATE TABLE IF NOT EXISTS vectors (
        id int PRIMARY KEY,
        embedding vector<float, 5>
    )",
    &[],
).await?;

// Insert vector
session.query_unpaged(
    "INSERT INTO vectors (id, embedding) VALUES (?, ?)",
    (1, embedding),
).await?;

Vector Dimensions

The vector dimension is part of the type and must match:
// This works - 5 elements for vector<float, 5>
let vec5: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0, 5.0];
session.query_unpaged(
    "INSERT INTO vectors (id, embedding) VALUES (?, ?)",
    (1, vec5),
).await?;

// This will fail - wrong number of elements
let vec3: Vec<f32> = vec![1.0, 2.0, 3.0];
// Error: dimension mismatch

Vector Operations

Vectors are commonly used for similarity search:
// Vector similarity search (if supported by your database)
let query_vector: Vec<f32> = vec![0.1, 0.2, 0.3, 0.4, 0.5];

let result = session
    .query_unpaged(
        "SELECT id FROM vectors ORDER BY embedding ANN OF ? LIMIT 10",
        (query_vector,),
    )
    .await?;

Nested Collections

Collections can contain other collections if the inner collection is frozen.

List of Lists

// CQL: list<frozen<list<text>>>
let matrix: Vec<Vec<String>> = vec![
    vec!["a".to_string(), "b".to_string()],
    vec!["c".to_string(), "d".to_string()],
];

session.query_unpaged(
    "INSERT INTO data (id, matrix) VALUES (?, ?)",
    (1, matrix),
).await?;

Map of Lists

use std::collections::HashMap;

// CQL: map<text, frozen<list<int>>>
let data: HashMap<String, Vec<i32>> = [
    ("scores".to_string(), vec![1, 2, 3]),
    ("ages".to_string(), vec![25, 30, 35]),
].iter().cloned().collect();

session.query_unpaged(
    "INSERT INTO data (id, data) VALUES (?, ?)",
    (1, data),
).await?;

Empty Collections

// Empty list
let empty_list: Vec<String> = Vec::new();
session.query_unpaged(
    "INSERT INTO data (id, tags) VALUES (?, ?)",
    (1, empty_list),
).await?;

// Empty set
let empty_set: HashSet<String> = HashSet::new();
session.query_unpaged(
    "INSERT INTO data (id, tags) VALUES (?, ?)",
    (2, empty_set),
).await?;

// Empty map
let empty_map: HashMap<String, String> = HashMap::new();
session.query_unpaged(
    "INSERT INTO data (id, metadata) VALUES (?, ?)",
    (3, empty_map),
).await?;

Nullable Collections

use std::collections::HashMap;

// Nullable list
let optional_tags: Option<Vec<String>> = None;
session.query_unpaged(
    "INSERT INTO data (id, tags) VALUES (?, ?)",
    (1, optional_tags),
).await?;

// Some collection
let some_tags: Option<Vec<String>> = Some(vec!["tag1".to_string()]);
session.query_unpaged(
    "INSERT INTO data (id, tags) VALUES (?, ?)",
    (2, some_tags),
).await?;

Performance Considerations

Collection Size

Collections have size limits:
  • Maximum recommended size: ~64KB per collection
  • Very large collections can impact performance
  • Consider using multiple rows instead of large collections
// Good: Reasonable collection size
let tags: Vec<String> = vec![/* 10-100 items */];

// Bad: Very large collection
let huge_list: Vec<i32> = vec![/* 10,000+ items */];

Frozen Collections

Frozen collections are more efficient but cannot be partially updated:
// CQL schema
session.query_unpaged(
    "CREATE TABLE data (
        id int PRIMARY KEY,
        tags frozen<list<text>>,      -- Frozen: more efficient, full replacement only
        metadata map<text, text>       -- Not frozen: can update individual keys
    )",
    &[],
).await?;

Common Patterns

Using Sets for Membership Testing

use std::collections::HashSet;

// Store user permissions as a set
let permissions: HashSet<String> = [
    "read".to_string(),
    "write".to_string(),
].iter().cloned().collect();

session.query_unpaged(
    "INSERT INTO users (id, permissions) VALUES (?, ?)",
    (1, permissions),
).await?;

// Query users with specific permission
let result = session
    .query_unpaged(
        "SELECT id FROM users WHERE permissions CONTAINS ?",
        ("admin",),
    )
    .await?;

Using Maps for Attributes

use std::collections::HashMap;

// Store flexible attributes as a map
let attributes: HashMap<String, String> = [
    ("color".to_string(), "blue".to_string()),
    ("size".to_string(), "large".to_string()),
    ("material".to_string(), "cotton".to_string()),
].iter().cloned().collect();

session.query_unpaged(
    "INSERT INTO products (id, attributes) VALUES (?, ?)",
    (1, attributes),
).await?;

Time-Series Data with Lists

use chrono::{DateTime, Utc};

// Store recent events as a list (with TTL)
let events: Vec<DateTime<Utc>> = vec![Utc::now()];

session.query_unpaged(
    "UPDATE sensor_data SET recent_events = ? + recent_events WHERE sensor_id = ?",
    (events, 1),
).await?;

Best Practices

  1. Keep collections small: Large collections can cause performance issues
  2. Use sets for uniqueness: When order doesn’t matter and you need unique elements
  3. Use frozen for nested collections: Required for collections within collections
  4. Avoid reading entire large collections: Use secondary indexes or query by specific elements
  5. Consider denormalization: Sometimes multiple rows are better than large collections

See Also

Build docs developers (and LLMs) love