1use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10use arrow::array::{Array as ArrowArray, Float64Array, UInt32Array};
11use arrow::datatypes::{DataType, Field, Schema};
12use arrow::record_batch::RecordBatch;
13use log::{debug, info};
14use smartcore::linalg::basic::arrays::Array;
15use smartcore::linalg::basic::matrix::DenseMatrix;
16use sprs::CsMat;
17
18use crate::metadata::FileInfo;
19use crate::metadata::GeneMetadata;
20use crate::traits::backend::StorageBackend;
21use crate::traits::lance::LanceStorage;
22use crate::traits::metadata::Metadata;
23use crate::{StorageError, StorageResult};
24
25#[derive(Debug, Clone)]
31pub struct LanceStorageGraph {
32 pub(crate) _base: String,
33 pub(crate) _name: String,
34}
35
36impl LanceStorageGraph {
37 pub fn new(_base: String, _name: String) -> Self {
46 info!("Creating LanceStorage at base={}, name={}", _base, _name);
47 Self { _base, _name }
48 }
49
50 pub async fn spawn(base_path: String) -> Result<(Self, GeneMetadata), StorageError> {
52 let (exists, md_path) = Self::exists(&base_path);
54
55 if !exists || md_path.is_none() {
57 return Err(StorageError::Invalid(format!(
58 "Metadata does not exist in base path: {}",
59 base_path
60 )));
61 }
62
63 let metadata = GeneMetadata::read(md_path.unwrap()).await?;
65
66 let storage = Self::new(base_path.clone(), metadata.name_id.clone());
68 Ok((storage, metadata))
69 }
70}
71
72impl LanceStorage for LanceStorageGraph {}
73
74impl StorageBackend for LanceStorageGraph {
75 fn get_base(&self) -> String {
76 self._base.clone()
77 }
78
79 fn get_name(&self) -> String {
80 self._name.clone()
81 }
82
83 fn base_path(&self) -> PathBuf {
84 PathBuf::from(&self._base)
85 }
86
87 fn metadata_path(&self) -> PathBuf {
88 self.base_path()
89 .join(format!("{}_metadata.json", self._name))
90 }
91
92 fn basepath_to_uri(&self) -> String {
94 Self::path_to_uri(PathBuf::from(self._base.clone()).as_path())
95 }
96
97 async fn save_dense(
107 &self,
108 key: &str,
109 matrix: &DenseMatrix<f64>,
110 md_path: &Path,
111 ) -> StorageResult<()> {
112 self.validate_initialized(md_path)?;
113 let path = self.file_path(key);
114 let (n_rows, n_cols) = matrix.shape();
115
116 info!(
117 "Saving dense {} matrix: {} x {} at {:?}",
118 key, n_rows, n_cols, path
119 );
120
121 let batch = self.to_dense_record_batch(matrix)?;
123
124 if batch.num_rows() != n_rows {
126 return Err(StorageError::Invalid(format!(
127 "RecordBatch has {} rows but matrix has {} rows",
128 batch.num_rows(),
129 n_rows
130 )));
131 }
132
133 {
134 let uri = Self::path_to_uri(&path);
136 self.write_lance_batch_async(uri, batch).await?;
137 let mut md = self.load_metadata().await?;
138 md = md.add_file(
139 key,
140 FileInfo::new(
141 format!("{}_{}.lance", self.get_name(), key),
142 "dense",
143 matrix.shape(),
144 None,
145 None,
146 ),
147 );
148 self.save_metadata(&md).await?;
149 info!("Dense {} matrix saved successfully", key);
150 }
151 Ok(())
152 }
153
154 async fn load_dense(&self, key: &str) -> StorageResult<DenseMatrix<f64>> {
164 let path = self.file_path(key);
165 info!("Loading dense {} matrix from {:?}", key, path);
166
167 let uri = Self::path_to_uri(&path);
169 let batch = self.read_lance_all_batches_async(uri).await?;
170
171 let matrix = self.from_dense_record_batch(&batch)?;
173
174 let (n_rows, n_cols) = matrix.shape();
175 info!("Loaded dense {} matrix: {} x {}", key, n_rows, n_cols);
176
177 Ok(matrix)
178 }
179
180 async fn load_dense_from_file(&self, path: &Path) -> StorageResult<DenseMatrix<f64>> {
184 info!("Loading dense matrix from file (async): {:?}", path);
185
186 if !path.exists() {
187 return Err(StorageError::Invalid(format!(
188 "Dense file does not exist: {:?}",
189 path
190 )));
191 }
192
193 let extension = path
194 .extension()
195 .and_then(|e| e.to_str())
196 .ok_or_else(|| StorageError::Invalid(format!("Invalid file path: {:?}", path)))?;
197
198 match extension {
199 "lance" => {
200 let parent = path
203 .parent()
204 .ok_or_else(|| {
205 StorageError::Invalid(format!("Path has no parent: {:?}", path))
206 })?
207 .to_str()
208 .ok_or_else(|| {
209 StorageError::Invalid(format!("Non-UTF8 parent path for {:?}", path))
210 })?
211 .to_string();
212
213 let tmp_storage = Self::new(parent, String::from("tmp_storage"));
214
215 let uri = Self::path_to_uri(path);
217 let batch = tmp_storage.read_lance_all_batches_async(uri).await?;
218 let matrix = tmp_storage.from_dense_record_batch(&batch)?;
219 info!(
220 "Loaded dense matrix from Lance: {} x {}",
221 matrix.shape().0,
222 matrix.shape().1
223 );
224 Ok(matrix)
225 }
226 "parquet" => {
227 use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
228 use std::fs::File;
229
230 let file = File::open(path)
232 .map_err(|e| StorageError::Io(format!("Failed to open parquet file: {}", e)))?;
233
234 let builder = ParquetRecordBatchReaderBuilder::try_new(file).map_err(|e| {
235 StorageError::Parquet(format!("Failed to create parquet reader: {}", e))
236 })?;
237 let mut reader = builder.build().map_err(|e| {
238 StorageError::Parquet(format!("Failed to build parquet reader: {}", e))
239 })?;
240
241 let mut batches = Vec::new();
242 #[allow(clippy::while_let_on_iterator)]
243 while let Some(batch) = reader.next() {
244 let batch = batch.map_err(|e| {
245 StorageError::Parquet(format!("Failed to read parquet batch: {}", e))
246 })?;
247 batches.push(batch);
248 }
249
250 if batches.is_empty() {
251 return Err(StorageError::Invalid(format!(
252 "Empty parquet dataset at {:?}",
253 path
254 )));
255 }
256
257 let schema = batches[0].schema();
258 let combined = arrow::compute::concat_batches(&schema, &batches).map_err(|e| {
259 StorageError::Parquet(format!("Failed to concatenate parquet batches: {}", e))
260 })?;
261
262 let fields = schema.fields();
264 let is_vector = fields.len() == 1
265 && matches!(
266 fields[0].data_type(),
267 DataType::FixedSizeList(inner, _)
268 if matches!(inner.data_type(), DataType::Float64)
269 );
270
271 let is_wide_col = !is_vector
272 && !fields.is_empty()
273 && fields
274 .iter()
275 .all(|f| matches!(f.data_type(), DataType::Float64))
276 && fields.iter().any(|f| f.name().starts_with("col_"));
277
278 let matrix = if is_vector {
280 let parent = path
283 .parent()
284 .ok_or_else(|| {
285 StorageError::Invalid(format!("Path has no parent: {:?}", path))
286 })?
287 .to_str()
288 .ok_or_else(|| {
289 StorageError::Invalid(format!("Non-UTF8 parent path for {:?}", path))
290 })?
291 .to_string();
292
293 let tmp_storage = Self::new(parent, String::from("tmp_storage"));
294 tmp_storage.from_dense_record_batch(&combined)?
295 } else if is_wide_col {
296 let n_rows = combined.num_rows();
298 let n_cols = combined.num_columns();
299 if n_rows == 0 || n_cols == 0 {
300 return Err(StorageError::Invalid(format!(
301 "Cannot load empty wide-column parquet at {:?}",
302 path
303 )));
304 }
305
306 let mut data = Vec::with_capacity(n_rows * n_cols);
307 for col_idx in 0..n_cols {
308 let col = combined.column(col_idx);
309 let arr = col.as_any().downcast_ref::<Float64Array>().ok_or_else(|| {
310 StorageError::Invalid(format!(
311 "Wide-column parquet expects Float64, got {:?} in column {}",
312 col.data_type(),
313 col_idx
314 ))
315 })?;
316 for row_idx in 0..n_rows {
318 data.push(arr.value(row_idx));
319 }
320 }
321
322 DenseMatrix::new(n_rows, n_cols, data, true)
323 .map_err(|e| StorageError::Invalid(e.to_string()))?
324 } else {
325 return Err(StorageError::Invalid(format!(
326 "Unsupported Parquet schema at {:?}: expected FixedSizeList<Float64> \
327 or wide Float64 columns named col_*",
328 path
329 )));
330 };
331
332 info!(
333 "Loaded dense matrix from Parquet: {} x {}",
334 matrix.shape().0,
335 matrix.shape().1
336 );
337
338 Ok(matrix)
339 }
340 _ => Err(StorageError::Invalid(format!(
341 "Unsupported file format: {}. Only .lance and .parquet are supported",
342 extension
343 ))),
344 }
345 }
346
347 fn file_path(&self, key: &str) -> PathBuf {
348 self.base_path()
349 .join(format!("{}_{}.lance", self._name, key))
350 }
351
352 async fn save_sparse(
357 &self,
358 key: &str,
359 matrix: &CsMat<f64>,
360 md_path: &Path,
361 ) -> StorageResult<()> {
362 self.validate_initialized(md_path)?;
363 let path = self.file_path(key);
364 info!(
365 "Saving sparse {} matrix: {} x {}, nnz={} at {:?}",
366 key,
367 matrix.rows(),
368 matrix.cols(),
369 matrix.nnz(),
370 path
371 );
372
373 let filetype = FileInfo::which_filetype(key);
374 {
375 let mut metadata = self.load_metadata().await?;
376 metadata = metadata.add_file(
377 key,
378 FileInfo::new(
379 format!("{}_{}.lance", self.get_name(), key),
380 filetype.as_str(),
381 (matrix.rows(), matrix.cols()),
382 Some(matrix.nnz()),
383 None,
384 ),
385 );
386 self.save_metadata(&metadata).await?;
387
388 let batch = self.to_sparse_record_batch(matrix)?;
389 let uri = Self::path_to_uri(&path);
390 self.write_lance_batch_async(uri, batch).await?;
391 }
392 info!("Sparse matrix {} saved successfully", filetype);
393 Ok(())
394 }
395
396 async fn load_sparse(&self, key: &str) -> StorageResult<CsMat<f64>> {
397 info!("Loading sparse {} matrix", key);
398
399 let metadata = self.load_metadata().await?;
400 let filetype = FileInfo::which_filetype(key);
401 let file_info = metadata
402 .files
403 .get(key)
404 .ok_or_else(|| StorageError::Invalid(format!("{key} not found in metadata")))?;
405
406 let expected_rows = file_info.rows;
407 let expected_cols = file_info.cols;
408 debug!(
409 "Expected dimensions from storage metadata: {} x {}",
410 expected_rows, expected_cols
411 );
412
413 let path = self.file_path(key);
414 let uri = Self::path_to_uri(&path);
415 let batch = self.read_lance_first_batch_async(uri).await?;
416 let matrix = self.from_sparse_record_batch(batch, expected_rows, expected_cols)?;
417 info!(
418 "Sparse {} matrix loaded: {} x {}, nnz={}",
419 filetype,
420 matrix.rows(),
421 matrix.cols(),
422 matrix.nnz()
423 );
424 Ok(matrix)
425 }
426
427 async fn save_lambdas(&self, lambdas: &[f64], md_path: &Path) -> StorageResult<()> {
428 self.validate_initialized(md_path)?;
429 let key = "lambdas";
430 let path = self.file_path("lambdas");
431 info!("Saving {} lambda values", lambdas.len());
432
433 let schema = Schema::new(vec![Field::new("lambda", DataType::Float64, false)]);
434 let batch = RecordBatch::try_new(
435 Arc::new(schema),
436 vec![Arc::new(Float64Array::from(lambdas.to_vec())) as _],
437 )
438 .map_err(|e| StorageError::Lance(e.to_string()))?;
439
440 {
441 let mut metadata = self.load_metadata().await?;
442 metadata = metadata.add_file(
443 key,
444 FileInfo::new(
445 format!("{}_{}.lance", self.get_name(), key),
446 "vector",
447 (lambdas.len(), 1),
448 None,
449 None,
450 ),
451 );
452 self.save_metadata(&metadata).await?;
453
454 let uri = Self::path_to_uri(&path);
455 self.write_lance_batch_async(uri, batch).await?;
456 }
457 info!("Lambda values saved successfully");
458 Ok(())
459 }
460
461 async fn load_lambdas(&self) -> StorageResult<Vec<f64>> {
462 let path = self.file_path("lambdas");
463 info!("Loading lambda values from {:?}", path);
464
465 let uri = Self::path_to_uri(&path);
466 let batch = self.read_lance_first_batch_async(uri).await?;
467 let arr = batch
468 .column(0)
469 .as_any()
470 .downcast_ref::<Float64Array>()
471 .ok_or_else(|| StorageError::Invalid("lambda column type mismatch".into()))?;
472
473 let lambdas: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
474 info!("Loaded {} lambda values", lambdas.len());
475 Ok(lambdas)
476 }
477
478 async fn save_vector(&self, key: &str, vector: &[f64], md_path: &Path) -> StorageResult<()> {
479 self.validate_initialized(md_path)?;
480 let path = self.file_path(key);
481 info!("Saving {} values for vector {}", vector.len(), key);
482
483 let schema = Schema::new(vec![Field::new("element", DataType::Float64, false)]);
484 let float64_array = Float64Array::from_iter_values::<Vec<f64>>(vector.into());
485 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(float64_array) as _])
486 .map_err(|e| StorageError::Lance(e.to_string()))?;
487
488 {
489 let mut metadata = self.load_metadata().await?;
490 metadata = metadata.add_file(
491 key,
492 FileInfo::new(
493 format!("{}_{}.lance", self.get_name(), key),
494 "vector",
495 (vector.len(), 1),
496 None,
497 None,
498 ),
499 );
500 self.save_metadata(&metadata).await?;
501
502 let uri = Self::path_to_uri(&path);
503 self.write_lance_batch_async(uri, batch).await?;
504 }
505 info!("Index {} saved successfully", key);
506 Ok(())
507 }
508
509 async fn save_index(&self, key: &str, vector: &[usize], md_path: &Path) -> StorageResult<()> {
510 self.validate_initialized(md_path)?;
511 let path = self.file_path(key);
512 info!("Saving {} values for index {}", vector.len(), key);
513
514 let schema = Schema::new(vec![Field::new("id", DataType::UInt32, false)]);
515 let uint32_array = UInt32Array::from_iter_values(vector.iter().map(|&x| x as u32));
516 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(uint32_array) as _])
517 .map_err(|e| StorageError::Lance(e.to_string()))?;
518
519 {
520 let mut metadata = self.load_metadata().await?;
521 metadata = metadata.add_file(
522 key,
523 FileInfo::new(
524 format!("{}_{}.lance", self.get_name(), key),
525 "vector",
526 (vector.len(), 1),
527 None,
528 None,
529 ),
530 );
531 self.save_metadata(&metadata).await?;
532
533 let uri = Self::path_to_uri(&path);
534 self.write_lance_batch_async(uri, batch).await?;
535 }
536 info!("Index {} saved successfully", key);
537 Ok(())
538 }
539
540 async fn load_vector(&self, filename: &str) -> StorageResult<Vec<f64>> {
541 let path = self.file_path(filename);
542 info!("Loading vector {} from {:?}", filename, path);
543
544 let uri = Self::path_to_uri(&path);
545 let batch = self.read_lance_first_batch_async(uri).await?;
546 let arr = batch
547 .column(0)
548 .as_any()
549 .downcast_ref::<Float64Array>()
550 .ok_or_else(|| StorageError::Invalid("column type mismatch".into()))?;
551
552 let vector: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
553 info!("Loaded {} vector values for {}", vector.len(), filename);
554 Ok(vector)
555 }
556
557 async fn load_index(&self, filename: &str) -> StorageResult<Vec<usize>> {
558 let path = self.file_path(filename);
559 info!("Loading vector {} from {:?}", filename, path);
560
561 let uri = Self::path_to_uri(&path);
562 let batch = self.read_lance_first_batch_async(uri).await?;
563 let arr = batch
564 .column(0)
565 .as_any()
566 .downcast_ref::<UInt32Array>()
567 .ok_or_else(|| StorageError::Invalid("column type mismatch".into()))?;
568
569 let vector: Vec<usize> = (0..arr.len()).map(|i| arr.value(i) as usize).collect();
570 info!("Loaded {} vector values for {}", vector.len(), filename);
571 Ok(vector)
572 }
573
574 async fn save_dense_to_file(data: &DenseMatrix<f64>, path: &Path) -> StorageResult<()> {
578 use tokio::fs as tokio_fs;
579
580 info!("Saving dense matrix to file (async): {:?}", path);
581
582 if let Some(parent) = path.parent() {
584 tokio_fs::try_exists(parent).await.map_err(|e| {
585 StorageError::Io(format!("Failed to create dir {:?}: {}", parent, e))
586 })?;
587 }
588
589 let tmp_storage = Self::new(
591 String::from(path.parent().unwrap().to_str().unwrap()),
592 String::from("tmp_storage"),
593 );
594
595 let extension = path
596 .extension()
597 .and_then(|e| e.to_str())
598 .ok_or_else(|| StorageError::Invalid(format!("Invalid file path: {:?}", path)))?;
599
600 let (n_rows, n_cols) = data.shape();
601 info!("Saving matrix: {} rows x {} cols", n_rows, n_cols);
602
603 match extension {
604 "lance" => {
605 let batch = tmp_storage.to_dense_record_batch(data)?;
606 debug!(
607 "Created RecordBatch with {} rows for Lance",
608 batch.num_rows()
609 );
610
611 if batch.num_rows() != n_rows {
613 return Err(StorageError::Invalid(format!(
614 "RecordBatch has {} rows but matrix has {} rows",
615 batch.num_rows(),
616 n_rows
617 )));
618 }
619
620 let uri = Self::path_to_uri(path);
621 tmp_storage.write_lance_batch_async(uri, batch).await?;
622 info!("Saved dense matrix to Lance: {} x {}", n_rows, n_cols);
623 Ok(())
624 }
625 "parquet" => {
626 use parquet::arrow::ArrowWriter;
627 use parquet::file::properties::WriterProperties;
628 use std::fs::File;
629
630 let batch = tmp_storage.to_dense_record_batch(data)?;
632 debug!(
633 "Created RecordBatch with {} rows for Parquet",
634 batch.num_rows()
635 );
636
637 if batch.num_rows() != n_rows {
638 return Err(StorageError::Invalid(format!(
639 "RecordBatch has {} rows but matrix has {} rows",
640 batch.num_rows(),
641 n_rows
642 )));
643 }
644
645 let file = File::create(path).map_err(|e| {
646 StorageError::Io(format!("Failed to create parquet file: {}", e))
647 })?;
648
649 let props = WriterProperties::builder()
650 .set_compression(parquet::basic::Compression::SNAPPY)
651 .build();
652
653 let mut writer =
654 ArrowWriter::try_new(file, batch.schema(), Some(props)).map_err(|e| {
655 StorageError::Parquet(format!("Failed to create parquet writer: {}", e))
656 })?;
657
658 writer
659 .write(&batch)
660 .map_err(|e| StorageError::Parquet(format!("Failed to write batch: {}", e)))?;
661
662 writer
663 .close()
664 .map_err(|e| StorageError::Parquet(format!("Failed to close writer: {}", e)))?;
665
666 info!("Saved dense matrix to Parquet: {} x {}", n_rows, n_cols);
667 Ok(())
668 }
669 _ => Err(StorageError::Invalid(format!(
670 "Unsupported file format: {}. Only .lance and .parquet are supported",
671 extension
672 ))),
673 }
674 }
675
676 async fn save_centroid_map(&self, map: &[usize], md_path: &Path) -> StorageResult<()> {
678 self.validate_initialized(md_path)?;
679 let key = "centroid_map";
680 let path = self.file_path(key);
681 info!("Saving {} centroid map entries", map.len());
682
683 let schema = Schema::new(vec![Field::new("centroid_id", DataType::UInt32, false)]);
684 let uint32_array = UInt32Array::from_iter_values(map.iter().map(|&x| x as u32));
685 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(uint32_array) as _])
686 .map_err(|e| StorageError::Lance(e.to_string()))?;
687
688 {
689 let mut metadata = self.load_metadata().await?;
690 metadata = metadata.add_file(
691 key,
692 FileInfo::new(
693 format!("{}_{}.lance", self.get_name(), key),
694 "vector",
695 (map.len(), 1),
696 None,
697 None,
698 ),
699 );
700 self.save_metadata(&metadata).await?;
701
702 let uri = Self::path_to_uri(&path);
703 self.write_lance_batch_async(uri, batch).await?;
704 }
705 info!("Centroid map saved successfully");
706 Ok(())
707 }
708
709 async fn load_centroid_map(&self) -> StorageResult<Vec<usize>> {
711 let path = self.file_path("centroid_map");
712 info!("Loading centroid map from {:?}", path);
713
714 let uri = Self::path_to_uri(&path);
715 let batch = self.read_lance_first_batch_async(uri).await?;
716 let arr = batch
717 .column(0)
718 .as_any()
719 .downcast_ref::<UInt32Array>()
720 .ok_or_else(|| StorageError::Invalid("centroid_id column type mismatch".into()))?;
721
722 let map: Vec<usize> = (0..arr.len()).map(|i| arr.value(i) as usize).collect();
723 info!("Loaded {} centroid map entries", map.len());
724 Ok(map)
725 }
726
727 async fn save_subcentroid_lambdas(&self, lambdas: &[f64], md_path: &Path) -> StorageResult<()> {
729 self.validate_initialized(md_path)?;
730 let key = "subcentroid_lambdas";
731 let path = self.file_path(key);
732 info!("Saving {} subcentroid lambda values", lambdas.len());
733
734 let schema = Schema::new(vec![Field::new(
735 "subcentroid_lambda",
736 DataType::Float64,
737 false,
738 )]);
739 let batch = RecordBatch::try_new(
740 Arc::new(schema),
741 vec![Arc::new(Float64Array::from(lambdas.to_vec())) as _],
742 )
743 .map_err(|e| StorageError::Lance(e.to_string()))?;
744 {
745 let mut metadata = self.load_metadata().await?;
746 metadata = metadata.add_file(
747 key,
748 FileInfo::new(
749 format!("{}_{}.lance", self.get_name(), key),
750 "vector",
751 (lambdas.len(), 1),
752 None,
753 None,
754 ),
755 );
756 self.save_metadata(&metadata).await?;
757
758 let uri = Self::path_to_uri(&path);
759 self.write_lance_batch_async(uri, batch).await?;
760 }
761 info!("Subcentroid lambda values saved successfully");
762 Ok(())
763 }
764
765 async fn load_subcentroid_lambdas(&self) -> StorageResult<Vec<f64>> {
767 let path = self.file_path("subcentroid_lambdas");
768 info!("Loading subcentroid lambda values from {:?}", path);
769
770 let uri = Self::path_to_uri(&path);
771 let batch = self.read_lance_first_batch_async(uri).await?;
772 let arr = batch
773 .column(0)
774 .as_any()
775 .downcast_ref::<Float64Array>()
776 .ok_or_else(|| {
777 StorageError::Invalid("subcentroid_lambda column type mismatch".into())
778 })?;
779
780 let lambdas: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
781 info!("Loaded {} subcentroid lambda values", lambdas.len());
782 Ok(lambdas)
783 }
784
785 async fn save_subcentroids(
787 &self,
788 subcentroids: &DenseMatrix<f64>,
789 md_path: &Path,
790 ) -> StorageResult<()> {
791 self.validate_initialized(md_path)?;
792 let key = "sub_centroids";
793 let path = self.file_path(key);
794 let (n_rows, n_cols) = subcentroids.shape();
795 info!(
796 "Saving subcentroids matrix {} x {} at {:?}",
797 n_rows, n_cols, path
798 );
799
800 let batch = self.to_dense_record_batch(subcentroids)?;
801 {
802 let mut metadata = self.load_metadata().await?;
803 metadata = metadata.add_file(
804 key,
805 FileInfo::new(
806 format!("{}_{}.lance", self.get_name(), key),
807 "vector",
808 subcentroids.shape(),
809 None,
810 None,
811 ),
812 );
813 self.save_metadata(&metadata).await?;
814
815 let uri = Self::path_to_uri(&path);
816 self.write_lance_batch_async(uri, batch).await?;
817 }
818 debug!("Subcentroids matrix saved successfully");
819 Ok(())
820 }
821
822 async fn load_subcentroids(&self) -> StorageResult<Vec<Vec<f64>>> {
824 let path = self.file_path("sub_centroids");
825 info!("Loading sub_centroids from {:?}", path);
826
827 let uri = Self::path_to_uri(&path);
828 let batch = self.read_lance_all_batches_async(uri).await?;
829 let matrix = self.from_dense_record_batch(&batch)?;
830
831 let (n_rows, n_cols) = matrix.shape();
833 let mut result = Vec::with_capacity(n_rows);
834
835 for row_idx in 0..n_rows {
836 let row: Vec<f64> = (0..n_cols)
837 .map(|col_idx| *matrix.get((row_idx, col_idx)))
838 .collect();
839 result.push(row);
840 }
841
842 info!(
843 "Loaded sub_centroids: {} x {} as Vec<Vec<f64>>",
844 n_rows, n_cols
845 );
846 Ok(result)
847 }
848
849 async fn save_item_norms(&self, item_norms: &[f64], md_path: &Path) -> StorageResult<()> {
851 self.validate_initialized(md_path)?;
852 let key = "item_norms";
853 let path = self.file_path(key);
854 info!("Saving {} item norm values", item_norms.len());
855
856 let schema = Schema::new(vec![Field::new("norm", DataType::Float64, false)]);
857 let batch = RecordBatch::try_new(
858 Arc::new(schema),
859 vec![Arc::new(Float64Array::from(item_norms.to_vec())) as _],
860 )
861 .map_err(|e| StorageError::Lance(e.to_string()))?;
862
863 {
864 let mut metadata = self.load_metadata().await?;
865 metadata = metadata.add_file(
866 key,
867 FileInfo::new(
868 format!("{}_{}.lance", self.get_name(), key),
869 "vector",
870 (item_norms.len(), 1),
871 None,
872 None,
873 ),
874 );
875 self.save_metadata(&metadata).await?;
876
877 let uri = Self::path_to_uri(&path);
878 self.write_lance_batch_async(uri, batch).await?;
879 }
880 info!("Item norms saved successfully");
881 Ok(())
882 }
883
884 async fn load_item_norms(&self) -> StorageResult<Vec<f64>> {
886 let path = self.file_path("item_norms");
887 info!("Loading item norms from {:?}", path);
888
889 let uri = Self::path_to_uri(&path);
890 let batch = self.read_lance_first_batch_async(uri).await?;
891 let arr = batch
892 .column(0)
893 .as_any()
894 .downcast_ref::<Float64Array>()
895 .ok_or_else(|| StorageError::Invalid("norm column type mismatch".into()))?;
896
897 let norms: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
898 info!("Loaded {} item norm values", norms.len());
899 Ok(norms)
900 }
901
902 async fn save_cluster_assignments(
903 &self,
904 assignments: &[Option<usize>],
905 md_path: &Path,
906 ) -> StorageResult<()> {
907 self.validate_initialized(md_path)?;
908 let key = "cluster_assignments";
909 let path = self.file_path(key);
910 info!("Saving {} cluster assignments", assignments.len());
911
912 let values: Vec<i64> = assignments
914 .iter()
915 .map(|opt| opt.map(|v| v as i64).unwrap_or(-1))
916 .collect();
917
918 use arrow::array::Int64Array;
919 let schema = Schema::new(vec![Field::new("cluster_id", DataType::Int64, false)]);
920 let batch = RecordBatch::try_new(
921 Arc::new(schema),
922 vec![Arc::new(Int64Array::from(values)) as _],
923 )
924 .map_err(|e| StorageError::Lance(e.to_string()))?;
925
926 {
927 let mut metadata = self.load_metadata().await?;
928 metadata = metadata.add_file(
929 key,
930 FileInfo::new(
931 format!("{}_{}.lance", self.get_name(), key),
932 "vector",
933 (assignments.len(), 1),
934 None,
935 None,
936 ),
937 );
938 self.save_metadata(&metadata).await?;
939
940 let uri = Self::path_to_uri(&path);
941 self.write_lance_batch_async(uri, batch).await?;
942 }
943 info!("Cluster assignments saved successfully");
944 Ok(())
945 }
946
947 async fn load_cluster_assignments(&self) -> StorageResult<Vec<Option<usize>>> {
948 use arrow::array::Int64Array;
949 let path = self.file_path("cluster_assignments");
950 info!("Loading cluster assignments from {:?}", path);
951
952 let uri = Self::path_to_uri(&path);
953 let batch = self.read_lance_first_batch_async(uri).await?;
954 let arr = batch
955 .column(0)
956 .as_any()
957 .downcast_ref::<Int64Array>()
958 .ok_or_else(|| StorageError::Invalid("cluster_id column type mismatch".into()))?;
959
960 let assignments: Vec<Option<usize>> = (0..arr.len())
961 .map(|i| {
962 let v = arr.value(i);
963 if v < 0 { None } else { Some(v as usize) }
964 })
965 .collect();
966 info!("Loaded {} cluster assignments", assignments.len());
967 Ok(assignments)
968 }
969}