1use std::path::{Path, PathBuf};
8
9use arrow::array::{Array as ArrowArray, Float64Array, UInt32Array};
10use arrow::datatypes::{DataType, Float64Type, Int64Type, UInt32Type};
11use arrow::record_batch::RecordBatch;
12use log::{debug, info};
13use smartcore::linalg::basic::arrays::Array;
14use smartcore::linalg::basic::matrix::DenseMatrix;
15use sprs::CsMat;
16
17use crate::metadata::FileInfo;
18use crate::metadata::GeneMetadata;
19use crate::traits::backend::StorageBackend;
20use crate::traits::lance::LanceStorage;
21use crate::traits::metadata::Metadata;
22use crate::{StorageError, StorageResult};
23
24fn checked_u32_values(values: &[usize], what: &str) -> StorageResult<Vec<u32>> {
29 values
30 .iter()
31 .map(|&v| {
32 u32::try_from(v).map_err(|_| {
33 StorageError::Overflow(format!(
34 "{} value {} exceeds u32::MAX and would be silently truncated",
35 what, v
36 ))
37 })
38 })
39 .collect()
40}
41
42#[derive(Debug, Clone)]
95pub struct LanceStorageGraph {
96 pub(crate) base: String,
97 pub(crate) name: String,
98}
99
100impl LanceStorageGraph {
101 pub fn new(base: String, name: String) -> Self {
110 info!("Creating LanceStorage at base={}, name={}", base, name);
111 Self { base, name }
112 }
113
114 pub async fn spawn(base_path: String) -> Result<(Self, GeneMetadata), StorageError> {
116 let (exists, md_path) = Self::exists(&base_path);
118
119 if !exists || md_path.is_none() {
121 return Err(StorageError::Invalid(format!(
122 "Metadata does not exist in base path: {}",
123 base_path
124 )));
125 }
126
127 let metadata = GeneMetadata::read(md_path.unwrap()).await?;
129
130 let storage = Self::new(base_path.clone(), metadata.name_id.clone());
132 Ok((storage, metadata))
133 }
134}
135
136impl LanceStorage for LanceStorageGraph {}
137
138impl StorageBackend for LanceStorageGraph {
139 fn get_base(&self) -> String {
140 self.base.clone()
141 }
142
143 fn get_name(&self) -> String {
144 self.name.clone()
145 }
146
147 fn base_path(&self) -> PathBuf {
148 PathBuf::from(&self.base)
149 }
150
151 fn metadata_path(&self) -> PathBuf {
152 self.base_path()
153 .join(format!("{}_metadata.json", self.name))
154 }
155
156 fn basepath_to_uri(&self) -> StorageResult<String> {
158 Self::path_to_uri(PathBuf::from(self.base.clone()).as_path())
159 }
160
161 async fn save_dense(
171 &self,
172 key: &str,
173 matrix: &DenseMatrix<f64>,
174 md_path: &Path,
175 ) -> StorageResult<()> {
176 self.validate_initialized(md_path)?;
177 let path = self.file_path(key);
178 let (n_rows, n_cols) = matrix.shape();
179
180 info!(
181 "Saving dense {} matrix: {} x {} at {:?}",
182 key, n_rows, n_cols, path
183 );
184
185 let batch = self.to_dense_record_batch(matrix)?;
187
188 if batch.num_rows() != n_rows {
190 return Err(StorageError::Invalid(format!(
191 "RecordBatch has {} rows but matrix has {} rows",
192 batch.num_rows(),
193 n_rows
194 )));
195 }
196
197 {
198 let uri = Self::path_to_uri(&path)?;
200 self.write_lance_batch_async(uri, batch).await?;
201 let mut md = self.load_metadata().await?;
202 md = md.add_file(
203 key,
204 FileInfo::new(
205 format!("{}_{}.lance", self.get_name(), key),
206 "dense",
207 matrix.shape(),
208 None,
209 None,
210 )?,
211 );
212 self.save_metadata(&md).await?;
213 info!("Dense {} matrix saved successfully", key);
214 }
215 Ok(())
216 }
217
218 async fn load_dense(&self, key: &str) -> StorageResult<DenseMatrix<f64>> {
228 let path = self.file_path(key);
229 info!("Loading dense {} matrix from {:?}", key, path);
230
231 let uri = Self::path_to_uri(&path)?;
233 let batch = self.read_lance_all_batches_async(uri).await?;
234
235 let matrix = self.from_dense_record_batch(&batch)?;
237
238 let (n_rows, n_cols) = matrix.shape();
239 info!("Loaded dense {} matrix: {} x {}", key, n_rows, n_cols);
240
241 Ok(matrix)
242 }
243
244 async fn load_dense_from_file(&self, path: &Path) -> StorageResult<DenseMatrix<f64>> {
248 info!("Loading dense matrix from file (async): {:?}", path);
249
250 if !path.exists() {
251 return Err(StorageError::Invalid(format!(
252 "Dense file does not exist: {:?}",
253 path
254 )));
255 }
256
257 let extension = path
258 .extension()
259 .and_then(|e| e.to_str())
260 .ok_or_else(|| StorageError::Invalid(format!("Invalid file path: {:?}", path)))?;
261
262 match extension {
263 "lance" => {
264 let parent = path
267 .parent()
268 .ok_or_else(|| {
269 StorageError::Invalid(format!("Path has no parent: {:?}", path))
270 })?
271 .to_str()
272 .ok_or_else(|| {
273 StorageError::Invalid(format!("Non-UTF8 parent path for {:?}", path))
274 })?
275 .to_string();
276
277 let tmp_storage = Self::new(parent, String::from("tmp_storage"));
278
279 let uri = Self::path_to_uri(path)?;
281 let batch = tmp_storage.read_lance_all_batches_async(uri).await?;
282 let matrix = tmp_storage.from_dense_record_batch(&batch)?;
283 info!(
284 "Loaded dense matrix from Lance: {} x {}",
285 matrix.shape().0,
286 matrix.shape().1
287 );
288 Ok(matrix)
289 }
290 "parquet" => {
291 use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
292
293 let owned_path = path.to_path_buf();
297 let combined =
298 tokio::task::spawn_blocking(move || -> StorageResult<RecordBatch> {
299 let file = std::fs::File::open(&owned_path).map_err(|e| {
300 StorageError::Io(format!("Failed to open parquet file: {}", e))
301 })?;
302
303 let builder =
304 ParquetRecordBatchReaderBuilder::try_new(file).map_err(|e| {
305 StorageError::Parquet(format!(
306 "Failed to create parquet reader: {}",
307 e
308 ))
309 })?;
310 let reader = builder.build().map_err(|e| {
311 StorageError::Parquet(format!("Failed to build parquet reader: {}", e))
312 })?;
313
314 let batches: Vec<RecordBatch> =
315 reader.collect::<Result<Vec<_>, _>>().map_err(|e| {
316 StorageError::Parquet(format!(
317 "Failed to read parquet batch: {}",
318 e
319 ))
320 })?;
321
322 if batches.is_empty() {
323 return Err(StorageError::Invalid(format!(
324 "Empty parquet dataset at {:?}",
325 owned_path
326 )));
327 }
328
329 let schema = batches[0].schema();
330 arrow::compute::concat_batches(&schema, &batches).map_err(|e| {
331 StorageError::Parquet(format!(
332 "Failed to concatenate parquet batches: {}",
333 e
334 ))
335 })
336 })
337 .await
338 .map_err(|e| {
339 StorageError::Io(format!("Parquet reader task failed: {}", e))
340 })??;
341
342 let schema = combined.schema();
344 let fields = schema.fields();
345 let is_vector = fields.len() == 1
346 && matches!(
347 fields[0].data_type(),
348 DataType::FixedSizeList(inner, _)
349 if matches!(inner.data_type(), DataType::Float64)
350 );
351
352 let is_wide_col = !is_vector
353 && !fields.is_empty()
354 && fields
355 .iter()
356 .all(|f| matches!(f.data_type(), DataType::Float64))
357 && fields.iter().any(|f| f.name().starts_with("col_"));
358
359 let matrix = if is_vector {
361 let parent = path
364 .parent()
365 .ok_or_else(|| {
366 StorageError::Invalid(format!("Path has no parent: {:?}", path))
367 })?
368 .to_str()
369 .ok_or_else(|| {
370 StorageError::Invalid(format!("Non-UTF8 parent path for {:?}", path))
371 })?
372 .to_string();
373
374 let tmp_storage = Self::new(parent, String::from("tmp_storage"));
375 tmp_storage.from_dense_record_batch(&combined)?
376 } else if is_wide_col {
377 let n_rows = combined.num_rows();
379 let n_cols = combined.num_columns();
380 if n_rows == 0 || n_cols == 0 {
381 return Err(StorageError::Invalid(format!(
382 "Cannot load empty wide-column parquet at {:?}",
383 path
384 )));
385 }
386
387 let mut data = Vec::with_capacity(n_rows * n_cols);
388 for col_idx in 0..n_cols {
389 let col = combined.column(col_idx);
390 let arr = col.as_any().downcast_ref::<Float64Array>().ok_or_else(|| {
391 StorageError::Invalid(format!(
392 "Wide-column parquet expects Float64, got {:?} in column {}",
393 col.data_type(),
394 col_idx
395 ))
396 })?;
397 for row_idx in 0..n_rows {
399 data.push(arr.value(row_idx));
400 }
401 }
402
403 DenseMatrix::new(n_rows, n_cols, data, true)
404 .map_err(|e| StorageError::Invalid(e.to_string()))?
405 } else {
406 return Err(StorageError::Invalid(format!(
407 "Unsupported Parquet schema at {:?}: expected FixedSizeList<Float64> \
408 or wide Float64 columns named col_*",
409 path
410 )));
411 };
412
413 info!(
414 "Loaded dense matrix from Parquet: {} x {}",
415 matrix.shape().0,
416 matrix.shape().1
417 );
418
419 Ok(matrix)
420 }
421 _ => Err(StorageError::Invalid(format!(
422 "Unsupported file format: {}. Only .lance and .parquet are supported",
423 extension
424 ))),
425 }
426 }
427
428 fn file_path(&self, key: &str) -> PathBuf {
429 self.base_path()
430 .join(format!("{}_{}.lance", self.name, key))
431 }
432
433 async fn save_sparse(
438 &self,
439 key: &str,
440 matrix: &CsMat<f64>,
441 md_path: &Path,
442 ) -> StorageResult<()> {
443 self.validate_initialized(md_path)?;
444 let path = self.file_path(key);
445 info!(
446 "Saving sparse {} matrix: {} x {}, nnz={} at {:?}",
447 key,
448 matrix.rows(),
449 matrix.cols(),
450 matrix.nnz(),
451 path
452 );
453
454 let filetype = FileInfo::which_filetype(key)?;
455 {
456 let mut metadata = self.load_metadata().await?;
457 metadata = metadata.add_file(
458 key,
459 FileInfo::new(
460 format!("{}_{}.lance", self.get_name(), key),
461 filetype.as_str(),
462 (matrix.rows(), matrix.cols()),
463 Some(matrix.nnz()),
464 None,
465 )?,
466 );
467 self.save_metadata(&metadata).await?;
468
469 let batch = self.to_sparse_record_batch(matrix)?;
470 let uri = Self::path_to_uri(&path)?;
471 self.write_lance_batch_async(uri, batch).await?;
472 }
473 info!("Sparse matrix {} saved successfully", filetype);
474 Ok(())
475 }
476
477 async fn load_sparse(&self, key: &str) -> StorageResult<CsMat<f64>> {
478 info!("Loading sparse {} matrix", key);
479
480 let metadata = self.load_metadata().await?;
481 let filetype = FileInfo::which_filetype(key)?;
482 let file_info = metadata
483 .files
484 .get(key)
485 .ok_or_else(|| StorageError::Invalid(format!("{key} not found in metadata")))?;
486
487 let expected_rows = file_info.rows;
488 let expected_cols = file_info.cols;
489 debug!(
490 "Expected dimensions from storage metadata: {} x {}",
491 expected_rows, expected_cols
492 );
493
494 let path = self.file_path(key);
495 let uri = Self::path_to_uri(&path)?;
496 let batch = self.read_lance_all_batches_async(uri).await?;
497 let matrix = self.from_sparse_record_batch(batch, expected_rows, expected_cols)?;
498 info!(
499 "Sparse {} matrix loaded: {} x {}, nnz={}",
500 filetype,
501 matrix.rows(),
502 matrix.cols(),
503 matrix.nnz()
504 );
505 Ok(matrix)
506 }
507
508 async fn save_lambdas(&self, lambdas: &[f64], md_path: &Path) -> StorageResult<()> {
509 info!("Saving {} lambda values", lambdas.len());
510 self.save_primitive_column::<Float64Type>("lambdas", "lambda", lambdas.to_vec(), md_path)
511 .await
512 }
513
514 async fn load_lambdas(&self) -> StorageResult<Vec<f64>> {
515 let path = self.file_path("lambdas");
516 info!("Loading lambda values from {:?}", path);
517
518 let uri = Self::path_to_uri(&path)?;
519 let batch = self.read_lance_all_batches_async(uri).await?;
520 let arr = batch
521 .column(0)
522 .as_any()
523 .downcast_ref::<Float64Array>()
524 .ok_or_else(|| StorageError::Invalid("lambda column type mismatch".into()))?;
525
526 let lambdas: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
527 info!("Loaded {} lambda values", lambdas.len());
528 Ok(lambdas)
529 }
530
531 async fn save_vector(&self, key: &str, vector: &[f64], md_path: &Path) -> StorageResult<()> {
532 info!("Saving {} values for vector {}", vector.len(), key);
533 self.save_primitive_column::<Float64Type>(key, "element", vector.to_vec(), md_path)
534 .await
535 }
536
537 async fn save_index(&self, key: &str, vector: &[usize], md_path: &Path) -> StorageResult<()> {
538 info!("Saving {} values for index {}", vector.len(), key);
539 let values = checked_u32_values(vector, "index")?;
542 self.save_primitive_column::<UInt32Type>(key, "id", values, md_path)
543 .await
544 }
545
546 async fn load_vector(&self, filename: &str) -> StorageResult<Vec<f64>> {
547 let path = self.file_path(filename);
548 info!("Loading vector {} from {:?}", filename, path);
549
550 let uri = Self::path_to_uri(&path)?;
551 let batch = self.read_lance_all_batches_async(uri).await?;
552 let arr = batch
553 .column(0)
554 .as_any()
555 .downcast_ref::<Float64Array>()
556 .ok_or_else(|| StorageError::Invalid("column type mismatch".into()))?;
557
558 let vector: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
559 info!("Loaded {} vector values for {}", vector.len(), filename);
560 Ok(vector)
561 }
562
563 async fn load_index(&self, filename: &str) -> StorageResult<Vec<usize>> {
564 let path = self.file_path(filename);
565 info!("Loading vector {} from {:?}", filename, path);
566
567 let uri = Self::path_to_uri(&path)?;
568 let batch = self.read_lance_all_batches_async(uri).await?;
569 let arr = batch
570 .column(0)
571 .as_any()
572 .downcast_ref::<UInt32Array>()
573 .ok_or_else(|| StorageError::Invalid("column type mismatch".into()))?;
574
575 let vector: Vec<usize> = (0..arr.len()).map(|i| arr.value(i) as usize).collect();
576 info!("Loaded {} vector values for {}", vector.len(), filename);
577 Ok(vector)
578 }
579
580 async fn save_dense_to_file(data: &DenseMatrix<f64>, path: &Path) -> StorageResult<()> {
584 info!("Saving dense matrix to file (async): {:?}", path);
585
586 let parent = path
589 .parent()
590 .filter(|p| !p.as_os_str().is_empty())
591 .ok_or_else(|| {
592 StorageError::Invalid(format!("path has no parent directory: {:?}", path))
593 })?;
594 tokio::fs::create_dir_all(parent)
595 .await
596 .map_err(|e| StorageError::Io(format!("create dir {:?}: {}", parent, e)))?;
597 let parent_str = parent
598 .to_str()
599 .ok_or_else(|| StorageError::Invalid(format!("non-UTF8 parent path for {:?}", path)))?
600 .to_string();
601
602 let tmp_storage = Self::new(parent_str, String::from("tmp_storage"));
604
605 let extension = path
606 .extension()
607 .and_then(|e| e.to_str())
608 .ok_or_else(|| StorageError::Invalid(format!("Invalid file path: {:?}", path)))?;
609
610 let (n_rows, n_cols) = data.shape();
611 info!("Saving matrix: {} rows x {} cols", n_rows, n_cols);
612
613 match extension {
614 "lance" => {
615 let batch = tmp_storage.to_dense_record_batch(data)?;
616 debug!(
617 "Created RecordBatch with {} rows for Lance",
618 batch.num_rows()
619 );
620
621 if batch.num_rows() != n_rows {
623 return Err(StorageError::Invalid(format!(
624 "RecordBatch has {} rows but matrix has {} rows",
625 batch.num_rows(),
626 n_rows
627 )));
628 }
629
630 let uri = Self::path_to_uri(path)?;
631 tmp_storage.write_lance_batch_async(uri, batch).await?;
632 info!("Saved dense matrix to Lance: {} x {}", n_rows, n_cols);
633 Ok(())
634 }
635 "parquet" => {
636 use parquet::arrow::ArrowWriter;
637 use parquet::file::properties::WriterProperties;
638 use std::fs::File;
639
640 let batch = tmp_storage.to_dense_record_batch(data)?;
641 debug!(
642 "Created RecordBatch with {} rows for Parquet",
643 batch.num_rows()
644 );
645
646 if batch.num_rows() != n_rows {
647 return Err(StorageError::Invalid(format!(
648 "RecordBatch has {} rows but matrix has {} rows",
649 batch.num_rows(),
650 n_rows
651 )));
652 }
653
654 let owned_path = path.to_path_buf();
658 tokio::task::spawn_blocking(move || -> StorageResult<()> {
659 let file = File::create(&owned_path).map_err(|e| {
660 StorageError::Io(format!("Failed to create parquet file: {}", e))
661 })?;
662
663 let props = WriterProperties::builder()
664 .set_compression(parquet::basic::Compression::SNAPPY)
665 .build();
666
667 let mut writer = ArrowWriter::try_new(file, batch.schema(), Some(props))
668 .map_err(|e| {
669 StorageError::Parquet(format!("Failed to create parquet writer: {}", e))
670 })?;
671
672 writer.write(&batch).map_err(|e| {
673 StorageError::Parquet(format!("Failed to write batch: {}", e))
674 })?;
675
676 writer.close().map_err(|e| {
677 StorageError::Parquet(format!("Failed to close writer: {}", e))
678 })?;
679
680 Ok(())
681 })
682 .await
683 .map_err(|e| StorageError::Io(format!("parquet writer task failed: {}", e)))??;
684
685 info!("Saved dense matrix to Parquet: {} x {}", n_rows, n_cols);
686 Ok(())
687 }
688 _ => Err(StorageError::Invalid(format!(
689 "Unsupported file format: {}. Only .lance and .parquet are supported",
690 extension
691 ))),
692 }
693 }
694
695 async fn save_centroid_map(&self, map: &[usize], md_path: &Path) -> StorageResult<()> {
697 info!("Saving {} centroid map entries", map.len());
698 let values = checked_u32_values(map, "centroid map")?;
701 self.save_primitive_column::<UInt32Type>("centroid_map", "centroid_id", values, md_path)
702 .await
703 }
704
705 async fn load_centroid_map(&self) -> StorageResult<Vec<usize>> {
707 let path = self.file_path("centroid_map");
708 info!("Loading centroid map from {:?}", path);
709
710 let uri = Self::path_to_uri(&path)?;
711 let batch = self.read_lance_all_batches_async(uri).await?;
712 let arr = batch
713 .column(0)
714 .as_any()
715 .downcast_ref::<UInt32Array>()
716 .ok_or_else(|| StorageError::Invalid("centroid_id column type mismatch".into()))?;
717
718 let map: Vec<usize> = (0..arr.len()).map(|i| arr.value(i) as usize).collect();
719 info!("Loaded {} centroid map entries", map.len());
720 Ok(map)
721 }
722
723 async fn save_subcentroid_lambdas(&self, lambdas: &[f64], md_path: &Path) -> StorageResult<()> {
725 info!("Saving {} subcentroid lambda values", lambdas.len());
726 self.save_primitive_column::<Float64Type>(
727 "subcentroid_lambdas",
728 "subcentroid_lambda",
729 lambdas.to_vec(),
730 md_path,
731 )
732 .await
733 }
734
735 async fn load_subcentroid_lambdas(&self) -> StorageResult<Vec<f64>> {
737 let path = self.file_path("subcentroid_lambdas");
738 info!("Loading subcentroid lambda values from {:?}", path);
739
740 let uri = Self::path_to_uri(&path)?;
741 let batch = self.read_lance_all_batches_async(uri).await?;
742 let arr = batch
743 .column(0)
744 .as_any()
745 .downcast_ref::<Float64Array>()
746 .ok_or_else(|| {
747 StorageError::Invalid("subcentroid_lambda column type mismatch".into())
748 })?;
749
750 let lambdas: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
751 info!("Loaded {} subcentroid lambda values", lambdas.len());
752 Ok(lambdas)
753 }
754
755 async fn save_subcentroids(
757 &self,
758 subcentroids: &DenseMatrix<f64>,
759 md_path: &Path,
760 ) -> StorageResult<()> {
761 self.validate_initialized(md_path)?;
762 let key = "sub_centroids";
763 let path = self.file_path(key);
764 let (n_rows, n_cols) = subcentroids.shape();
765 info!(
766 "Saving subcentroids matrix {} x {} at {:?}",
767 n_rows, n_cols, path
768 );
769
770 let batch = self.to_dense_record_batch(subcentroids)?;
771 {
772 let mut metadata = self.load_metadata().await?;
773 metadata = metadata.add_file(
774 key,
775 FileInfo::new(
776 format!("{}_{}.lance", self.get_name(), key),
777 "vector",
778 subcentroids.shape(),
779 None,
780 None,
781 )?,
782 );
783 self.save_metadata(&metadata).await?;
784
785 let uri = Self::path_to_uri(&path)?;
786 self.write_lance_batch_async(uri, batch).await?;
787 }
788 debug!("Subcentroids matrix saved successfully");
789 Ok(())
790 }
791
792 async fn load_subcentroids(&self) -> StorageResult<Vec<Vec<f64>>> {
794 let path = self.file_path("sub_centroids");
795 info!("Loading sub_centroids from {:?}", path);
796
797 let uri = Self::path_to_uri(&path)?;
798 let batch = self.read_lance_all_batches_async(uri).await?;
799 let matrix = self.from_dense_record_batch(&batch)?;
800
801 let (n_rows, n_cols) = matrix.shape();
803 let mut result = Vec::with_capacity(n_rows);
804
805 for row_idx in 0..n_rows {
806 let row: Vec<f64> = (0..n_cols)
807 .map(|col_idx| *matrix.get((row_idx, col_idx)))
808 .collect();
809 result.push(row);
810 }
811
812 info!(
813 "Loaded sub_centroids: {} x {} as Vec<Vec<f64>>",
814 n_rows, n_cols
815 );
816 Ok(result)
817 }
818
819 async fn save_item_norms(&self, item_norms: &[f64], md_path: &Path) -> StorageResult<()> {
821 info!("Saving {} item norm values", item_norms.len());
822 self.save_primitive_column::<Float64Type>(
823 "item_norms",
824 "norm",
825 item_norms.to_vec(),
826 md_path,
827 )
828 .await
829 }
830
831 async fn load_item_norms(&self) -> StorageResult<Vec<f64>> {
833 let path = self.file_path("item_norms");
834 info!("Loading item norms from {:?}", path);
835
836 let uri = Self::path_to_uri(&path)?;
837 let batch = self.read_lance_all_batches_async(uri).await?;
838 let arr = batch
839 .column(0)
840 .as_any()
841 .downcast_ref::<Float64Array>()
842 .ok_or_else(|| StorageError::Invalid("norm column type mismatch".into()))?;
843
844 let norms: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
845 info!("Loaded {} item norm values", norms.len());
846 Ok(norms)
847 }
848
849 async fn save_cluster_assignments(
850 &self,
851 assignments: &[Option<usize>],
852 md_path: &Path,
853 ) -> StorageResult<()> {
854 info!("Saving {} cluster assignments", assignments.len());
855
856 let values: Vec<i64> = assignments
858 .iter()
859 .map(|opt| opt.map(|v| v as i64).unwrap_or(-1))
860 .collect();
861
862 self.save_primitive_column::<Int64Type>(
863 "cluster_assignments",
864 "cluster_id",
865 values,
866 md_path,
867 )
868 .await
869 }
870
871 async fn load_cluster_assignments(&self) -> StorageResult<Vec<Option<usize>>> {
872 use arrow::array::Int64Array;
873 let path = self.file_path("cluster_assignments");
874 info!("Loading cluster assignments from {:?}", path);
875
876 let uri = Self::path_to_uri(&path)?;
877 let batch = self.read_lance_all_batches_async(uri).await?;
878 let arr = batch
879 .column(0)
880 .as_any()
881 .downcast_ref::<Int64Array>()
882 .ok_or_else(|| StorageError::Invalid("cluster_id column type mismatch".into()))?;
883
884 let assignments: Vec<Option<usize>> = (0..arr.len())
885 .map(|i| {
886 let v = arr.value(i);
887 if v < 0 { None } else { Some(v as usize) }
888 })
889 .collect();
890 info!("Loaded {} cluster assignments", assignments.len());
891 Ok(assignments)
892 }
893}