1use crate::cache::CacheKey;
8use crate::schema::Schema;
9use crate::value::Value;
10use serde::{Deserialize, Serialize};
11use std::fmt;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
25#[non_exhaustive]
26pub enum VirtualValue {
27 Materialized {
29 value: Value,
31 schema: Schema,
33 },
34
35 Cached {
37 key: CacheKey,
39 schema: Schema,
41 },
42
43 Deferred {
46 producer_node_id: String,
48 cache_key: CacheKey,
50 schema: Schema,
52 },
53
54 Stream {
56 source_id: String,
58 schema: Schema,
60 },
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ValueStatus {
66 InMemory,
68 OnDisk,
70 NotComputed,
72 Streaming,
74}
75
76impl VirtualValue {
77 pub fn materialized(value: Value) -> Self {
79 let schema = Self::infer_schema(&value);
80 Self::Materialized { value, schema }
81 }
82
83 pub fn materialized_with_schema(value: Value, schema: Schema) -> Self {
85 Self::Materialized { value, schema }
86 }
87
88 pub fn cached(key: CacheKey, schema: Schema) -> Self {
90 Self::Cached { key, schema }
91 }
92
93 pub fn deferred(
95 producer_node_id: impl Into<String>,
96 cache_key: CacheKey,
97 schema: Schema,
98 ) -> Self {
99 Self::Deferred {
100 producer_node_id: producer_node_id.into(),
101 cache_key,
102 schema,
103 }
104 }
105
106 pub fn schema(&self) -> &Schema {
108 match self {
109 Self::Materialized { schema, .. }
110 | Self::Cached { schema, .. }
111 | Self::Deferred { schema, .. }
112 | Self::Stream { schema, .. } => schema,
113 }
114 }
115
116 pub fn status(&self) -> ValueStatus {
118 match self {
119 Self::Materialized { .. } => ValueStatus::InMemory,
120 Self::Cached { .. } => ValueStatus::OnDisk,
121 Self::Deferred { .. } => ValueStatus::NotComputed,
122 Self::Stream { .. } => ValueStatus::Streaming,
123 }
124 }
125
126 pub fn as_value(&self) -> Option<&Value> {
128 match self {
129 Self::Materialized { value, .. } => Some(value),
130 _ => None,
131 }
132 }
133
134 pub fn cache_key(&self) -> Option<&CacheKey> {
136 match self {
137 Self::Cached { key, .. } | Self::Deferred { cache_key: key, .. } => Some(key),
138 _ => None,
139 }
140 }
141
142 pub fn try_load(
144 &self,
145 cache: &dyn crate::cache::CacheStore,
146 ) -> crate::error::Result<Option<Value>> {
147 match self {
148 Self::Materialized { value, .. } => Ok(Some(value.clone())),
149 Self::Cached { key, .. } => cache.get(key),
150 Self::Deferred { cache_key, .. } => cache.get(cache_key),
151 _ => Ok(None),
152 }
153 }
154
155 pub fn resolve(&self, cache: &dyn crate::cache::CacheStore) -> crate::error::Result<Self> {
158 match self {
159 Self::Materialized { .. } => Ok(self.clone()),
160 Self::Cached { key, schema } => {
161 if let Some(value) = cache.get(key)? {
162 Ok(Self::Materialized {
163 value,
164 schema: schema.clone(),
165 })
166 } else {
167 Ok(self.clone()) }
169 }
170 Self::Deferred {
171 cache_key, schema, ..
172 } => {
173 if let Some(value) = cache.get(cache_key)? {
174 Ok(Self::Materialized {
175 value,
176 schema: schema.clone(),
177 })
178 } else {
179 Ok(self.clone()) }
181 }
182 _ => Ok(self.clone()),
183 }
184 }
185
186 fn infer_schema(value: &Value) -> Schema {
188 match value {
189 Value::Tensor { values: _, shape } => Schema {
190 dtype: crate::schema::DataType::Float64,
191 shape: Some(
192 shape
193 .iter()
194 .map(|&d| crate::schema::Dimension::Fixed(d))
195 .collect(),
196 ),
197 },
198 Value::Text(_) => Schema::text(),
199 Value::Json(_) => Schema::json(),
200 Value::Bytes(_) | Value::Object(_) => Schema::bytes(),
201 Value::Empty => Schema::dynamic(crate::schema::DataType::Float64),
202 }
203 }
204}
205
206impl fmt::Display for VirtualValue {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 match self {
209 Self::Materialized { schema, value } => {
210 write!(f, "Materialized({schema}, size={})", value.size())
211 }
212 Self::Cached { key, schema } => {
213 write!(f, "Cached({schema}, key={key})")
214 }
215 Self::Deferred {
216 producer_node_id,
217 schema,
218 ..
219 } => {
220 write!(f, "Deferred({schema}, producer={producer_node_id})")
221 }
222 Self::Stream { source_id, schema } => {
223 write!(f, "Stream({schema}, source={source_id})")
224 }
225 }
226 }
227}
228
229impl From<Value> for VirtualValue {
230 fn from(value: Value) -> Self {
231 Self::materialized(value)
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::cache::CacheKey;
239 use crate::schema::{DataType, Schema};
240
241 #[test]
242 fn materialized_from_value() {
243 let val = Value::tensor(vec![1.0, 2.0, 3.0], vec![3]);
244 let vv = VirtualValue::materialized(val.clone());
245
246 assert_eq!(vv.status(), ValueStatus::InMemory);
247 assert_eq!(vv.as_value(), Some(&val));
248 assert_eq!(vv.schema().dtype, DataType::Float64);
249 assert_eq!(vv.schema().rank(), Some(1));
250 }
251
252 #[test]
253 fn cached_reference() {
254 let key = CacheKey::hash_data(b"test");
255 let schema = Schema::vector(DataType::Float64, 100);
256 let vv = VirtualValue::cached(key.clone(), schema.clone());
257
258 assert_eq!(vv.status(), ValueStatus::OnDisk);
259 assert_eq!(vv.cache_key(), Some(&key));
260 assert_eq!(vv.schema(), &schema);
261 assert!(vv.as_value().is_none());
262 }
263
264 #[test]
265 fn deferred_reference() {
266 let key = CacheKey::hash_data(b"deferred");
267 let schema = Schema::batched(DataType::Float64, &[128]);
268 let vv = VirtualValue::deferred("my_node", key.clone(), schema.clone());
269
270 assert_eq!(vv.status(), ValueStatus::NotComputed);
271 assert_eq!(vv.cache_key(), Some(&key));
272 assert_eq!(vv.schema(), &schema);
273 }
274
275 #[test]
276 fn schema_inferred_from_tensor() {
277 let val = Value::tensor(vec![0.0; 12], vec![3, 4]);
278 let vv = VirtualValue::materialized(val);
279 assert_eq!(vv.schema().dtype, DataType::Float64);
280 assert_eq!(vv.schema().rank(), Some(2));
281 }
282
283 #[test]
284 fn schema_inferred_from_json() {
285 let val = Value::json(serde_json::json!({"a": 1}));
286 let vv = VirtualValue::materialized(val);
287 assert_eq!(vv.schema().dtype, DataType::Json);
288 }
289
290 #[test]
291 fn display_formatting() {
292 let vv = VirtualValue::materialized(Value::tensor(vec![1.0], vec![1]));
293 assert!(vv.to_string().contains("Materialized"));
294
295 let vv = VirtualValue::cached(CacheKey::hash_data(b"k"), Schema::json());
296 assert!(vv.to_string().contains("Cached"));
297
298 let vv = VirtualValue::deferred("node_1", CacheKey::hash_data(b"k"), Schema::json());
299 assert!(vv.to_string().contains("Deferred"));
300 }
301
302 #[test]
303 fn from_value_conversion() {
304 let val = Value::tensor(vec![1.0, 2.0], vec![2]);
305 let vv: VirtualValue = val.clone().into();
306 assert_eq!(vv.status(), ValueStatus::InMemory);
307 assert_eq!(vv.as_value(), Some(&val));
308 }
309
310 #[test]
311 fn resolve_materialized_stays_materialized() {
312 use crate::cache::CacheStore;
313
314 struct EmptyCache;
316 impl CacheStore for EmptyCache {
317 fn get(&self, _: &CacheKey) -> crate::error::Result<Option<Value>> {
318 Ok(None)
319 }
320 fn put(&self, _: &CacheKey, _: &Value) -> crate::error::Result<()> {
321 Ok(())
322 }
323 fn exists(&self, _: &CacheKey) -> crate::error::Result<bool> {
324 Ok(false)
325 }
326 fn remove(&self, _: &CacheKey) -> crate::error::Result<()> {
327 Ok(())
328 }
329 fn metadata(
330 &self,
331 _: &CacheKey,
332 ) -> crate::error::Result<Option<crate::cache::EntryMeta>> {
333 Ok(None)
334 }
335 }
336
337 let val = Value::tensor(vec![1.0], vec![1]);
338 let vv = VirtualValue::materialized(val);
339 let resolved = vv.resolve(&EmptyCache).unwrap();
340 assert_eq!(resolved.status(), ValueStatus::InMemory);
341 }
342
343 #[test]
344 fn resolve_deferred_checks_cache() {
345 use crate::cache::{CacheStore, EntryMeta};
346 use std::collections::HashMap;
347 use std::sync::Mutex;
348
349 struct TestCache {
350 store: Mutex<HashMap<CacheKey, Value>>,
351 }
352 impl TestCache {
353 fn with(key: CacheKey, value: Value) -> Self {
354 let mut store = HashMap::new();
355 store.insert(key, value);
356 Self {
357 store: Mutex::new(store),
358 }
359 }
360 }
361 impl CacheStore for TestCache {
362 fn get(&self, key: &CacheKey) -> crate::error::Result<Option<Value>> {
363 Ok(self.store.lock().unwrap().get(key).cloned())
364 }
365 fn put(&self, _: &CacheKey, _: &Value) -> crate::error::Result<()> {
366 Ok(())
367 }
368 fn exists(&self, key: &CacheKey) -> crate::error::Result<bool> {
369 Ok(self.store.lock().unwrap().contains_key(key))
370 }
371 fn remove(&self, _: &CacheKey) -> crate::error::Result<()> {
372 Ok(())
373 }
374 fn metadata(&self, _: &CacheKey) -> crate::error::Result<Option<EntryMeta>> {
375 Ok(None)
376 }
377 }
378
379 let key = CacheKey::hash_data(b"cached_value");
380 let expected = Value::tensor(vec![42.0], vec![1]);
381 let cache = TestCache::with(key.clone(), expected.clone());
382
383 let vv = VirtualValue::deferred("producer", key, Schema::vector(DataType::Float64, 1));
385 assert_eq!(vv.status(), ValueStatus::NotComputed);
386
387 let resolved = vv.resolve(&cache).unwrap();
388 assert_eq!(resolved.status(), ValueStatus::InMemory);
389 assert_eq!(resolved.as_value(), Some(&expected));
390 }
391
392 #[test]
393 fn serde_roundtrip() {
394 let values = vec![
395 VirtualValue::materialized(Value::tensor(vec![1.0], vec![1])),
396 VirtualValue::cached(CacheKey::hash_data(b"k"), Schema::json()),
397 VirtualValue::deferred(
398 "n",
399 CacheKey::hash_data(b"d"),
400 Schema::vector(DataType::Float64, 10),
401 ),
402 ];
403 for vv in values {
404 let json = serde_json::to_string(&vv).unwrap();
405 let deserialized: VirtualValue = serde_json::from_str(&json).unwrap();
406 assert_eq!(vv.status(), deserialized.status());
407 assert_eq!(vv.schema(), deserialized.schema());
408 }
409 }
410}