Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ starknet-core = "0.16.0"


dashmap = "6.1.0"
redis = { version = "0.25", features = ["tokio-comp", "connection-manager"] }
sha2 = "0.10"
bincode = "1.3"

# [patch.crates-io]
# cainome = { git = "https://github.com/Larkooo/cainome", branch = "patch-1" }
Expand Down
12 changes: 10 additions & 2 deletions crates/cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ edition.workspace = true
repository.workspace = true
version.workspace = true

[features]
default = []
redis = ["dep:redis"]

[dependencies]
async-trait.workspace = true
bincode.workspace = true
dashmap.workspace = true
dojo-types.workspace = true
dojo-world.workspace = true
redis = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
sqlx.workspace = true
starknet.workspace = true
thiserror.workspace = true
tokio.workspace = true
torii-math.workspace = true
torii-proto.workspace = true
torii-sqlite-types.workspace = true
serde_json.workspace = true
torii-storage.workspace = true
torii-proto.workspace = true
1 change: 1 addition & 0 deletions crates/cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use torii_storage::ReadOnlyStorage;
use crate::error::Error;

pub mod error;
pub mod query_cache;

pub type CacheError = Error;

Expand Down
177 changes: 177 additions & 0 deletions crates/cache/src/query_cache/cached_row.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use serde::{Deserialize, Serialize};
use sqlx::sqlite::SqliteRow;
use sqlx::{Column, Row, TypeInfo};

/// A serializable representation of a SQLite row.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedRow {
/// Column metadata.
pub columns: Vec<CachedColumn>,
/// Row values.
pub values: Vec<CachedValue>,
}

/// Column metadata for cached rows.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedColumn {
/// Column name.
pub name: String,
/// SQLite type name.
pub type_name: String,
}

/// Cached value types matching SQLite's type affinity.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CachedValue {
/// NULL value.
Null,
/// INTEGER value (i64).
Integer(i64),
/// REAL value (f64).
Real(f64),
/// TEXT value.
Text(String),
/// BLOB value.
Blob(Vec<u8>),
}

impl CachedRow {
/// Create a CachedRow from a SQLite row.
pub fn from_sqlite_row(row: &SqliteRow) -> Self {
let columns: Vec<CachedColumn> = row
.columns()
.iter()
.map(|c| CachedColumn {
name: c.name().to_string(),
type_name: c.type_info().name().to_string(),
})
.collect();

let values: Vec<CachedValue> = (0..columns.len())
.map(|i| {
// Try each type in order based on SQLite type affinity
if let Ok(v) = row.try_get::<Option<i64>, _>(i) {
match v {
Some(n) => CachedValue::Integer(n),
None => CachedValue::Null,
}
} else if let Ok(v) = row.try_get::<Option<f64>, _>(i) {
match v {
Some(n) => CachedValue::Real(n),
None => CachedValue::Null,
}
} else if let Ok(v) = row.try_get::<Option<String>, _>(i) {
match v {
Some(s) => CachedValue::Text(s),
None => CachedValue::Null,
}
} else if let Ok(v) = row.try_get::<Option<Vec<u8>>, _>(i) {
match v {
Some(b) => CachedValue::Blob(b),
None => CachedValue::Null,
}
} else {
CachedValue::Null
}
})
.collect();

Self { columns, values }
}

/// Get a value by column name.
pub fn get(&self, column_name: &str) -> Option<&CachedValue> {
let idx = self.columns.iter().position(|c| c.name == column_name)?;
self.values.get(idx)
}

/// Get an integer value by column name.
pub fn get_i64(&self, column_name: &str) -> Option<i64> {
match self.get(column_name)? {
CachedValue::Integer(v) => Some(*v),
_ => None,
}
}

/// Get a string value by column name.
pub fn get_string(&self, column_name: &str) -> Option<&str> {
match self.get(column_name)? {
CachedValue::Text(v) => Some(v),
_ => None,
}
}

/// Get a blob value by column name.
pub fn get_blob(&self, column_name: &str) -> Option<&[u8]> {
match self.get(column_name)? {
CachedValue::Blob(v) => Some(v),
_ => None,
}
}
}

impl CachedValue {
/// Check if the value is null.
pub fn is_null(&self) -> bool {
matches!(self, CachedValue::Null)
}

/// Convert to i64 if possible.
pub fn as_i64(&self) -> Option<i64> {
match self {
CachedValue::Integer(v) => Some(*v),
_ => None,
}
}

/// Convert to f64 if possible.
pub fn as_f64(&self) -> Option<f64> {
match self {
CachedValue::Real(v) => Some(*v),
CachedValue::Integer(v) => Some(*v as f64),
_ => None,
}
}

/// Convert to string if possible.
pub fn as_str(&self) -> Option<&str> {
match self {
CachedValue::Text(v) => Some(v),
_ => None,
}
}

/// Convert to bytes if possible.
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
CachedValue::Blob(v) => Some(v),
_ => None,
}
}
}

/// A cached page of query results with optional cursor for pagination.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedPage {
/// The rows in this page.
pub rows: Vec<CachedRow>,
/// Optional cursor for the next page.
pub next_cursor: Option<String>,
}

impl CachedPage {
/// Create a new cached page.
pub fn new(rows: Vec<CachedRow>, next_cursor: Option<String>) -> Self {
Self { rows, next_cursor }
}

/// Check if this page is empty.
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}

/// Get the number of rows in this page.
pub fn len(&self) -> usize {
self.rows.len()
}
}
Loading
Loading