genegraph_storage/traits/
lance.rs1use std::path::Path;
2use std::sync::Arc;
3
4use arrow::array::{ArrayRef, PrimitiveArray};
5use arrow::datatypes::{ArrowPrimitiveType, Field, Schema};
6use arrow::record_batch::RecordBatch;
7use log::{debug, info};
8
9use crate::metadata::FileInfo;
10use crate::traits::backend::StorageBackend;
11use crate::traits::metadata::Metadata;
12use crate::{StorageError, StorageResult};
13
14#[cfg(feature = "official-lance")]
15use arrow::record_batch::RecordBatchIterator;
16#[cfg(feature = "official-lance")]
17use futures::StreamExt;
18#[cfg(feature = "official-lance")]
19use lance::Dataset;
20#[cfg(feature = "official-lance")]
21use lance::dataset::{WriteMode, WriteParams};
22
23#[cfg(not(feature = "official-lance"))]
25fn uri_to_path(uri: &str) -> StorageResult<std::path::PathBuf> {
26 let url = url::Url::parse(uri)
27 .map_err(|e| StorageError::Invalid(format!("bad dataset URI `{uri}`: {e}")))?;
28 url.to_file_path().map_err(|_| {
29 StorageError::Invalid(format!("dataset URI is not a local file path: `{uri}`"))
30 })
31}
32
33pub trait LanceStorage {
34 #[cfg(feature = "official-lance")]
40 async fn write_lance_batch_async(&self, uri: String, batch: RecordBatch) -> StorageResult<()> {
41 info!("Writing Lance dataset to {}", uri);
42
43 let schema = batch.schema();
44 let batches = vec![batch];
45 let reader = RecordBatchIterator::new(batches.into_iter().map(Ok), schema);
46
47 let params = WriteParams {
48 mode: WriteMode::Create,
49 ..WriteParams::default()
50 };
51
52 Dataset::write(reader, &uri, Some(params))
53 .await
54 .map_err(|e| StorageError::Lance(e.to_string()))?;
55
56 info!("Successfully wrote Lance dataset to {}", uri);
57 Ok(())
58 }
59
60 #[cfg(not(feature = "official-lance"))]
61 async fn write_lance_batch_async(&self, uri: String, batch: RecordBatch) -> StorageResult<()> {
62 info!("Writing Lance dataset (in-house v2.1) to {}", uri);
63 let path = uri_to_path(&uri)?;
64 tokio::task::spawn_blocking(move || crate::lancefmt::write_dataset(&batch, &path))
65 .await
66 .map_err(|e| StorageError::Io(format!("lancefmt writer task failed: {e}")))?
67 }
68
69 #[cfg(feature = "official-lance")]
71 async fn read_lance_all_batches_async(&self, uri: String) -> StorageResult<RecordBatch> {
72 info!("Reading Lance dataset from {}", uri);
73
74 let dataset = Dataset::open(&uri)
75 .await
76 .map_err(|e| StorageError::Lance(e.to_string()))?;
77 let scanner = dataset.scan();
78 let mut stream = scanner
79 .try_into_stream()
80 .await
81 .map_err(|e| StorageError::Lance(e.to_string()))?;
82
83 let mut batches = Vec::new();
84 while let Some(batch_result) = stream.next().await {
85 let batch = batch_result.map_err(|e| StorageError::Lance(e.to_string()))?;
86 batches.push(batch);
87 }
88
89 if batches.is_empty() {
90 return Err(StorageError::Invalid("Empty Lance dataset".into()));
91 }
92
93 let schema = batches[0].schema();
94 let combined = arrow::compute::concat_batches(&schema, &batches)
95 .map_err(|e| StorageError::Lance(format!("Failed to concatenate batches: {}", e)))?;
96
97 debug!(
98 "Combined Lance batch for {:?} has {} rows",
99 uri,
100 combined.num_rows()
101 );
102 Ok(combined)
103 }
104
105 #[cfg(not(feature = "official-lance"))]
106 async fn read_lance_all_batches_async(&self, uri: String) -> StorageResult<RecordBatch> {
107 info!("Reading Lance dataset (in-house v2.1) from {}", uri);
108 let path = uri_to_path(&uri)?;
109 let combined = tokio::task::spawn_blocking(move || crate::lancefmt::scan_all(&path))
110 .await
111 .map_err(|e| StorageError::Io(format!("lancefmt reader task failed: {e}")))??;
112 debug!(
113 "Combined Lance batch for {:?} has {} rows",
114 uri,
115 combined.num_rows()
116 );
117 Ok(combined)
118 }
119
120 async fn save_primitive_column<T: ArrowPrimitiveType>(
127 &self,
128 key: &str,
129 field_name: &str,
130 values: Vec<T::Native>,
131 md_path: &Path,
132 ) -> StorageResult<()>
133 where
134 Self: StorageBackend,
135 {
136 self.validate_initialized(md_path)?;
137 let path = self.file_path(key);
138 let len = values.len();
139 info!("Saving {} values for {} (field {})", len, key, field_name);
140
141 let schema = Schema::new(vec![Field::new(field_name, T::DATA_TYPE, false)]);
142 let batch = RecordBatch::try_new(
143 Arc::new(schema),
144 vec![Arc::new(PrimitiveArray::<T>::from_iter_values(values)) as ArrayRef],
145 )
146 .map_err(|e| StorageError::Lance(e.to_string()))?;
147
148 let mut metadata = self.load_metadata().await?;
149 metadata = metadata.add_file(
150 key,
151 FileInfo::new(
152 format!("{}_{}.lance", self.get_name(), key),
153 "vector",
154 (len, 1),
155 None,
156 None,
157 )?,
158 );
159 self.save_metadata(&metadata).await?;
160
161 let uri = Self::path_to_uri(&path)?;
162 self.write_lance_batch_async(uri, batch).await?;
163 info!("Vector {} saved successfully", key);
164 Ok(())
165 }
166}