1use super::manifest_feature_flags::{ensure_readable, ensure_writable};
10use arrow::array::builder::{ListBuilder, StringBuilder};
11use arrow::array::{Array, ListArray, RecordBatch, RecordBatchIterator, StringArray, UInt64Array};
12use arrow::datatypes::{DataType, Field, Schema as ArrowSchema, SchemaRef};
13use arrow_ipc::reader::StreamReader;
14use async_trait::async_trait;
15use bytes::Bytes;
16use datafusion_common::DataFusionError;
17use datafusion_physical_plan::{
18 SendableRecordBatchStream,
19 stream::RecordBatchStreamAdapter as DatafusionRecordBatchStreamAdapter,
20};
21use futures::{
22 FutureExt, TryStreamExt,
23 stream::{self, StreamExt},
24};
25use lance::dataset::index::LanceIndexStoreExt;
26use lance::dataset::transaction::{Operation, Transaction};
27use lance::dataset::{
28 InsertBuilder, ReadParams, WhenMatched, WriteMode, WriteParams, builder::DatasetBuilder,
29};
30use lance::session::Session;
31use lance::{Dataset, dataset::scanner::Scanner};
32use lance_core::Error as LanceError;
33use lance_core::datatypes::LANCE_UNENFORCED_PRIMARY_KEY_POSITION;
34use lance_core::{Error, ROW_ID, Result, box_error};
35use lance_index::progress::noop_progress;
36use lance_index::registry::IndexPluginRegistry;
37use lance_index::scalar::lance_format::LanceIndexStore;
38use lance_index::scalar::registry::VALUE_COLUMN_NAME;
39use lance_index::scalar::{
40 BuiltinIndexType, CreatedIndex, ScalarIndexParams, index_files_to_table,
41};
42use lance_io::object_store::{ObjectStore, ObjectStoreParams};
43use lance_io::stream::RecordBatchStream as LanceRecordBatchStream;
44use lance_namespace::LanceNamespace;
45use lance_namespace::error::NamespaceError;
46use lance_namespace::models::{
47 AlterTableAddColumnsRequest, AlterTableAddColumnsResponse, AlterTableAlterColumnsRequest,
48 AlterTableAlterColumnsResponse, AlterTableDropColumnsRequest, AlterTableDropColumnsResponse,
49 CreateNamespaceRequest, CreateNamespaceResponse, CreateTableRequest, CreateTableResponse,
50 DeclareTableRequest, DeclareTableResponse, DeregisterTableRequest, DeregisterTableResponse,
51 DescribeNamespaceRequest, DescribeNamespaceResponse, DescribeTableRequest,
52 DescribeTableResponse, DropNamespaceRequest, DropNamespaceResponse, DropTableRequest,
53 DropTableResponse, ListNamespacesRequest, ListNamespacesResponse, ListTablesRequest,
54 ListTablesResponse, NamespaceExistsRequest, RegisterTableRequest, RegisterTableResponse,
55 TableExistsRequest,
56};
57use lance_namespace::schema::arrow_schema_to_json;
58use lance_table::feature_flags::{apply_feature_flags, ensure_can_write_manifest};
59use lance_table::format::{Fragment, IndexMetadata, Manifest};
60use lance_table::io::commit::{
61 CommitError, CommitHandler, commit_handler_from_url, write_manifest_file_to_path,
62};
63use object_store::{Error as ObjectStoreError, path::Path};
64use roaring::RoaringBitmap;
65use std::io::Cursor;
66use std::time::{SystemTime, UNIX_EPOCH};
67use std::{
68 collections::{BTreeMap, HashMap, HashSet},
69 hash::{DefaultHasher, Hash, Hasher},
70 ops::{Deref, DerefMut},
71 sync::{Arc, LazyLock, Mutex as StdMutex, MutexGuard as StdMutexGuard},
72};
73use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
74use uuid::Uuid;
75
76const MANIFEST_TABLE_NAME: &str = "__manifest";
77const LANCE_DATA_DIR: &str = "data";
78const LANCE_INDICES_DIR: &str = "_indices";
79const DELIMITER: &str = "$";
80pub(crate) const DECLARED_FILTER_CONCURRENCY: usize = 16;
83
84const OBJECT_ID_INDEX_NAME: &str = "object_id_btree";
87const OBJECT_TYPE_INDEX_NAME: &str = "object_type_bitmap";
89const BASE_OBJECTS_INDEX_NAME: &str = "base_objects_label_list";
91static BASE_OBJECTS_VALUE_FIELD: LazyLock<Field> = LazyLock::new(|| {
94 Field::new(
95 VALUE_COLUMN_NAME,
96 DataType::List(Arc::new(Field::new("item", DataType::Utf8, true))),
97 true,
98 )
99});
100const DEFAULT_MANIFEST_REWRITE_COMMIT_RETRIES: u32 = 20;
103const MANIFEST_INDEX_BATCH_SIZE: usize = 8192;
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum ObjectType {
108 Namespace,
109 Table,
110}
111
112impl ObjectType {
113 pub fn as_str(&self) -> &'static str {
114 match self {
115 Self::Namespace => "namespace",
116 Self::Table => "table",
117 }
118 }
119
120 pub fn parse(s: &str) -> Result<Self> {
121 match s {
122 "namespace" => Ok(Self::Namespace),
123 "table" => Ok(Self::Table),
124 _ => Err(NamespaceError::Internal {
125 message: format!("Invalid object type: {}", s),
126 }
127 .into()),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133enum CreateTableMode {
134 Create,
135 ExistOk,
136 Overwrite,
137}
138
139impl CreateTableMode {
140 fn parse(mode: Option<&str>) -> Result<Self> {
141 match mode {
142 None => Ok(Self::Create),
143 Some(mode) if mode.eq_ignore_ascii_case("create") => Ok(Self::Create),
144 Some(mode)
145 if mode.eq_ignore_ascii_case("existok")
146 || mode.eq_ignore_ascii_case("exist_ok") =>
147 {
148 Ok(Self::ExistOk)
149 }
150 Some(mode) if mode.eq_ignore_ascii_case("overwrite") => Ok(Self::Overwrite),
151 Some(mode) => Err(NamespaceError::InvalidInput {
152 message: format!(
153 "Unsupported create_table mode '{}'. Supported modes are: 'Create', 'ExistOk', 'Overwrite'",
154 mode
155 ),
156 }
157 .into()),
158 }
159 }
160
161 fn write_mode(self) -> WriteMode {
162 match self {
163 Self::Overwrite => WriteMode::Overwrite,
164 Self::Create | Self::ExistOk => WriteMode::Create,
165 }
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct TableInfo {
172 pub namespace: Vec<String>,
173 pub name: String,
174 pub location: String,
175 pub metadata: Option<HashMap<String, String>>,
176}
177
178#[derive(Debug, Clone)]
183pub struct ManifestEntry {
184 pub object_id: String,
186 pub object_type: ObjectType,
188 pub location: Option<String>,
190 pub metadata: Option<String>,
192}
193
194struct CopyOnWriteMutation<T> {
195 result: T,
196 has_changes: bool,
197}
198
199impl<T> CopyOnWriteMutation<T> {
200 fn updated(result: T) -> Self {
201 Self {
202 result,
203 has_changes: true,
204 }
205 }
206
207 fn unchanged(result: T) -> Self {
208 Self {
209 result,
210 has_changes: false,
211 }
212 }
213}
214
215struct ManifestIndexBuildInput {
216 index_name: &'static str,
217 column_name: &'static str,
218 params: ScalarIndexParams,
219 field: Field,
220 stream: SendableRecordBatchStream,
221}
222
223struct ManifestTrainedIndex {
224 index_name: &'static str,
225 column_name: &'static str,
226 uuid: Uuid,
227 created_index: CreatedIndex,
228}
229
230struct ManifestRowValue {
231 object_id: String,
232 object_type: ObjectType,
233 location: Option<String>,
234 metadata: Option<String>,
235 base_objects: Option<Vec<String>>,
236}
237
238struct ManifestOutputRow<'a> {
239 object_id: &'a str,
240 object_type: ObjectType,
241 location: Option<&'a str>,
242 metadata: Option<&'a str>,
243 base_objects: Option<&'a [String]>,
244}
245
246#[derive(Default)]
247struct ManifestIndexAccumulator {
248 object_ids: BTreeMap<Arc<str>, u64>,
249 object_types: BTreeMap<&'static str, RoaringBitmap>,
250 base_objects_values: Vec<Option<Vec<String>>>,
251 base_objects_row_ids: Vec<u64>,
252 row_count: u64,
253}
254
255impl ManifestIndexAccumulator {
256 fn next_row_id(&self) -> Result<u64> {
257 if self.row_count >= u64::from(u32::MAX) {
258 return Err(NamespaceError::Internal {
259 message: format!(
260 "Manifest rewrite exceeded maximum single-fragment row count: {}",
261 self.row_count
262 ),
263 }
264 .into());
265 }
266 Ok(self.row_count)
267 }
268
269 fn push(&mut self, row: &ManifestOutputRow<'_>) -> Result<u64> {
270 let row_id = self.next_row_id()?;
271 if self
272 .object_ids
273 .insert(Arc::<str>::from(row.object_id), row_id)
274 .is_some()
275 {
276 return Err(NamespaceError::Internal {
277 message: format!("Manifest contains duplicate object_id '{}'", row.object_id),
278 }
279 .into());
280 }
281 self.object_types
282 .entry(row.object_type.as_str())
283 .or_default()
284 .insert(row_id as u32);
285 self.base_objects_values
286 .push(row.base_objects.map(|objects| objects.to_vec()));
287 self.base_objects_row_ids.push(row_id);
288 self.row_count += 1;
289 Ok(row_id)
290 }
291}
292
293struct ManifestBatchBuilder {
294 object_ids: Vec<String>,
295 object_types: Vec<&'static str>,
296 locations: Vec<Option<String>>,
297 metadatas: Vec<Option<String>>,
298 base_objects: Vec<Option<Vec<String>>>,
299}
300
301impl ManifestBatchBuilder {
302 fn new() -> Self {
303 Self {
304 object_ids: Vec::new(),
305 object_types: Vec::new(),
306 locations: Vec::new(),
307 metadatas: Vec::new(),
308 base_objects: Vec::new(),
309 }
310 }
311
312 fn is_empty(&self) -> bool {
313 self.object_ids.is_empty()
314 }
315
316 fn append(
317 &mut self,
318 index_data: &mut ManifestIndexAccumulator,
319 row: ManifestOutputRow<'_>,
320 ) -> Result<()> {
321 index_data.push(&row)?;
322 self.object_ids.push(row.object_id.to_string());
323 self.object_types.push(row.object_type.as_str());
324 self.locations.push(row.location.map(ToString::to_string));
325 self.metadatas.push(row.metadata.map(ToString::to_string));
326 self.base_objects
327 .push(row.base_objects.map(|objects| objects.to_vec()));
328 Ok(())
329 }
330
331 fn finish(self) -> Result<RecordBatch> {
332 let base_objects_array = ManifestNamespace::base_objects_array(&self.base_objects);
333 RecordBatch::try_new(
334 ManifestNamespace::manifest_schema(),
335 vec![
336 Arc::new(StringArray::from(self.object_ids)),
337 Arc::new(StringArray::from(self.object_types)),
338 Arc::new(StringArray::from(self.locations)),
339 Arc::new(StringArray::from(self.metadatas)),
340 Arc::new(base_objects_array),
341 ],
342 )
343 .map_err(|e| {
344 lance_core::Error::from(NamespaceError::Internal {
345 message: format!("Failed to create manifest snapshot batch: {:?}", e),
346 })
347 })
348 }
349}
350
351enum ConflictResolution<O> {
354 Retry,
357 FailIfExists(Vec<String>),
361 SucceedIfAbsent { object_id: String, output: O },
364}
365
366trait ManifestStreamMutation: Send {
367 type Output: Clone + Send + 'static;
368
369 fn process_existing_row(
370 &mut self,
371 row: ManifestRowValue,
372 output: &mut ManifestBatchBuilder,
373 index_data: &mut ManifestIndexAccumulator,
374 ) -> Result<()>;
375
376 fn append_rows(
377 &mut self,
378 output: &mut ManifestBatchBuilder,
379 index_data: &mut ManifestIndexAccumulator,
380 ) -> Result<()>;
381
382 fn finish(&self) -> CopyOnWriteMutation<Self::Output>;
383
384 fn conflict_resolution(&self) -> ConflictResolution<Self::Output> {
387 ConflictResolution::Retry
388 }
389}
390
391struct ManifestRewriteShared<M: ManifestStreamMutation> {
392 mutation: M,
393 index_data: Option<ManifestIndexAccumulator>,
394 result: Option<CopyOnWriteMutation<M::Output>>,
395 error: Option<LanceError>,
396}
397
398impl<M: ManifestStreamMutation> ManifestRewriteShared<M> {
399 fn new(mutation: M) -> Self {
400 Self {
401 mutation,
402 index_data: Some(ManifestIndexAccumulator::default()),
403 result: None,
404 error: None,
405 }
406 }
407}
408
409struct UpsertManifestMutation {
410 entries: Vec<ManifestEntry>,
411 base_objects: Vec<Option<Vec<String>>>,
412 entry_positions: HashMap<String, usize>,
413 matched: Vec<bool>,
414 when_matched: WhenMatched,
415}
416
417impl UpsertManifestMutation {
418 fn new(
419 entries: Vec<ManifestEntry>,
420 base_objects: Option<Vec<String>>,
421 when_matched: WhenMatched,
422 ) -> Self {
423 let entry_positions = entries
424 .iter()
425 .enumerate()
426 .map(|(index, entry)| (entry.object_id.clone(), index))
427 .collect();
428 let matched = vec![false; entries.len()];
429 let mut entry_base_objects = vec![None; entries.len()];
430 if !entry_base_objects.is_empty() {
431 entry_base_objects[0] = base_objects;
432 }
433 Self {
434 entries,
435 base_objects: entry_base_objects,
436 entry_positions,
437 matched,
438 when_matched,
439 }
440 }
441
442 fn entry_row(&self, index: usize) -> ManifestOutputRow<'_> {
443 let entry = &self.entries[index];
444 ManifestOutputRow {
445 object_id: &entry.object_id,
446 object_type: entry.object_type,
447 location: entry.location.as_deref(),
448 metadata: entry.metadata.as_deref(),
449 base_objects: self.base_objects[index].as_deref(),
450 }
451 }
452}
453
454impl ManifestStreamMutation for UpsertManifestMutation {
455 type Output = ();
456
457 fn process_existing_row(
458 &mut self,
459 row: ManifestRowValue,
460 output: &mut ManifestBatchBuilder,
461 index_data: &mut ManifestIndexAccumulator,
462 ) -> Result<()> {
463 if let Some(index) = self.entry_positions.get(&row.object_id).copied() {
464 match self.when_matched {
465 WhenMatched::Fail => {
466 return Err(NamespaceError::ConcurrentModification {
467 message: format!(
468 "Object '{}' was concurrently created by another operation",
469 row.object_id
470 ),
471 }
472 .into());
473 }
474 WhenMatched::UpdateAll => {
475 self.matched[index] = true;
476 output.append(index_data, self.entry_row(index))?;
477 return Ok(());
478 }
479 _ => {
480 return Err(NamespaceError::Internal {
481 message: format!(
482 "Unsupported manifest rewrite matched action: {:?}",
483 self.when_matched
484 ),
485 }
486 .into());
487 }
488 }
489 }
490
491 output.append(
492 index_data,
493 ManifestOutputRow {
494 object_id: &row.object_id,
495 object_type: row.object_type,
496 location: row.location.as_deref(),
497 metadata: row.metadata.as_deref(),
498 base_objects: row.base_objects.as_deref(),
499 },
500 )
501 }
502
503 fn append_rows(
504 &mut self,
505 output: &mut ManifestBatchBuilder,
506 index_data: &mut ManifestIndexAccumulator,
507 ) -> Result<()> {
508 for index in 0..self.entries.len() {
509 if !self.matched[index] {
510 output.append(index_data, self.entry_row(index))?;
511 }
512 }
513 Ok(())
514 }
515
516 fn finish(&self) -> CopyOnWriteMutation<Self::Output> {
517 CopyOnWriteMutation::updated(())
518 }
519
520 fn conflict_resolution(&self) -> ConflictResolution<Self::Output> {
521 match self.when_matched {
522 WhenMatched::Fail => ConflictResolution::FailIfExists(
525 self.entries.iter().map(|e| e.object_id.clone()).collect(),
526 ),
527 _ => ConflictResolution::Retry,
529 }
530 }
531}
532
533struct DeleteObjectMutation {
534 object_id: String,
535 deleted: bool,
536}
537
538impl ManifestStreamMutation for DeleteObjectMutation {
539 type Output = ();
540
541 fn process_existing_row(
542 &mut self,
543 row: ManifestRowValue,
544 output: &mut ManifestBatchBuilder,
545 index_data: &mut ManifestIndexAccumulator,
546 ) -> Result<()> {
547 if row.object_id == self.object_id {
548 self.deleted = true;
549 return Ok(());
550 }
551
552 output.append(
553 index_data,
554 ManifestOutputRow {
555 object_id: &row.object_id,
556 object_type: row.object_type,
557 location: row.location.as_deref(),
558 metadata: row.metadata.as_deref(),
559 base_objects: row.base_objects.as_deref(),
560 },
561 )
562 }
563
564 fn append_rows(
565 &mut self,
566 _output: &mut ManifestBatchBuilder,
567 _index_data: &mut ManifestIndexAccumulator,
568 ) -> Result<()> {
569 Ok(())
570 }
571
572 fn finish(&self) -> CopyOnWriteMutation<Self::Output> {
573 if self.deleted {
574 CopyOnWriteMutation::updated(())
575 } else {
576 CopyOnWriteMutation::unchanged(())
577 }
578 }
579
580 fn conflict_resolution(&self) -> ConflictResolution<Self::Output> {
581 ConflictResolution::SucceedIfAbsent {
583 object_id: self.object_id.clone(),
584 output: (),
585 }
586 }
587}
588
589#[derive(Debug, Clone)]
591pub struct NamespaceInfo {
592 pub namespace: Vec<String>,
593 pub name: String,
594 pub metadata: Option<HashMap<String, String>>,
595}
596
597#[derive(Debug, Clone)]
604pub struct DatasetConsistencyWrapper(Arc<RwLock<Dataset>>);
605
606impl DatasetConsistencyWrapper {
607 pub fn new(dataset: Dataset) -> Self {
609 debug_assert!(
610 !dataset
611 .manifest()
612 .config
613 .keys()
614 .any(|key| key.starts_with("lance.auto_cleanup.")),
615 "the directory manifest dataset must not enable old-version cleanup"
616 );
617 Self(Arc::new(RwLock::new(dataset)))
618 }
619
620 pub async fn get(&self) -> Result<DatasetReadGuard<'_>> {
623 self.reload().await?;
624 let guard = DatasetReadGuard {
625 guard: self.0.read().await,
626 };
627 ensure_readable(guard.metadata())?;
630 Ok(guard)
631 }
632
633 pub async fn get_refreshed(&self) -> Result<DatasetReadGuard<'_>> {
635 self.reload().await?;
636 let guard = DatasetReadGuard {
637 guard: self.0.read().await,
638 };
639 ensure_readable(guard.metadata())?;
640 Ok(guard)
641 }
642
643 pub async fn get_mut(&self) -> Result<DatasetWriteGuard<'_>> {
649 let mut write_guard = self.0.write().await;
650 Self::reload_under_write_lock(&mut write_guard).await?;
651 let guard = DatasetWriteGuard { guard: write_guard };
652 ensure_readable(guard.metadata())?;
653 ensure_writable(guard.metadata())?;
654 Ok(guard)
655 }
656
657 pub async fn set_latest(&self, dataset: Dataset) {
662 let mut write_guard = self.0.write().await;
663 if dataset.manifest().version > write_guard.manifest().version {
664 *write_guard = dataset;
665 }
666 }
667
668 async fn reload(&self) -> Result<()> {
673 let read_guard = self.0.read().await;
675 let dataset_uri = read_guard.uri().to_string();
676 let current_version = read_guard.version().version;
677 log::debug!(
678 "Reload starting for uri={}, current_version={}",
679 dataset_uri,
680 current_version
681 );
682 let has_successor_version = read_guard.has_successor_version().await.map_err(|e| {
687 lance_core::Error::from(NamespaceError::Internal {
688 message: format!("Failed to check dataset staleness: {:?}", e),
689 })
690 })?;
691 log::debug!(
692 "Reload checked successor_version_exists={} for uri={}, current_version={}",
693 has_successor_version,
694 dataset_uri,
695 current_version
696 );
697 drop(read_guard);
698
699 if !has_successor_version {
701 log::debug!("Already up-to-date for uri={}", dataset_uri);
702 return Ok(());
703 }
704
705 let mut write_guard = self.0.write().await;
707 Self::reload_under_write_lock(&mut write_guard).await
708 }
709
710 async fn reload_under_write_lock(
712 dataset: &mut tokio::sync::RwLockWriteGuard<'_, Dataset>,
713 ) -> Result<()> {
714 let dataset_uri = dataset.uri().to_string();
715 let current_version = dataset.version().version;
716 log::debug!(
717 "Reload (under write lock) for uri={}, current_version={}",
718 dataset_uri,
719 current_version
720 );
721
722 let has_successor_version = dataset.has_successor_version().await.map_err(|e| {
723 lance_core::Error::from(NamespaceError::Internal {
724 message: format!("Failed to check dataset staleness: {:?}", e),
725 })
726 })?;
727
728 if has_successor_version {
729 dataset.checkout_latest().await.map_err(|e| {
730 lance_core::Error::from(NamespaceError::Internal {
731 message: format!("Failed to checkout latest: {:?}", e),
732 })
733 })?;
734 }
735
736 Ok(())
737 }
738}
739
740pub struct DatasetReadGuard<'a> {
741 guard: RwLockReadGuard<'a, Dataset>,
742}
743
744impl Deref for DatasetReadGuard<'_> {
745 type Target = Dataset;
746
747 fn deref(&self) -> &Self::Target {
748 &self.guard
749 }
750}
751
752pub struct DatasetWriteGuard<'a> {
753 guard: RwLockWriteGuard<'a, Dataset>,
754}
755
756impl Deref for DatasetWriteGuard<'_> {
757 type Target = Dataset;
758
759 fn deref(&self) -> &Self::Target {
760 &self.guard
761 }
762}
763
764impl DerefMut for DatasetWriteGuard<'_> {
765 fn deref_mut(&mut self) -> &mut Self::Target {
766 &mut self.guard
767 }
768}
769
770pub struct ManifestNamespace {
774 root: String,
775 storage_options: Option<HashMap<String, String>>,
776 session: Option<Arc<Session>>,
777 object_store: Arc<ObjectStore>,
778 base_path: Path,
779 manifest_dataset: DatasetConsistencyWrapper,
780 dir_listing_enabled: bool,
784 inline_optimization_enabled: bool,
787 commit_retries: Option<u32>,
790 manifest_mutation_lock: Arc<Mutex<()>>,
793}
794
795impl std::fmt::Debug for ManifestNamespace {
796 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
797 f.debug_struct("ManifestNamespace")
798 .field("root", &self.root)
799 .field("storage_options", &self.storage_options)
800 .field("dir_listing_enabled", &self.dir_listing_enabled)
801 .field(
802 "inline_optimization_enabled",
803 &self.inline_optimization_enabled,
804 )
805 .finish()
806 }
807}
808
809fn convert_lance_commit_error(e: &LanceError, operation: &str, object_id: Option<&str>) -> Error {
818 match e {
819 LanceError::CommitConflict { .. } => NamespaceError::Throttling {
821 message: format!("Too many concurrent writes, please retry later: {:?}", e),
822 }
823 .into(),
824 LanceError::TooMuchWriteContention { .. } | LanceError::IncompatibleTransaction { .. } => {
827 let message = if let Some(id) = object_id {
828 format!(
829 "Object '{}' was concurrently modified by another operation: {:?}",
830 id, e
831 )
832 } else {
833 format!(
834 "Object was concurrently modified by another operation: {:?}",
835 e
836 )
837 };
838 NamespaceError::ConcurrentModification { message }.into()
839 }
840 _ => {
842 let error_msg = e.to_string();
843 if error_msg.contains("matched")
844 || error_msg.contains("duplicate")
845 || error_msg.contains("already exists")
846 {
847 let message = if let Some(id) = object_id {
848 format!(
849 "Object '{}' was concurrently created by another operation: {:?}",
850 id, e
851 )
852 } else {
853 format!(
854 "Object was concurrently created by another operation: {:?}",
855 e
856 )
857 };
858 return NamespaceError::ConcurrentModification { message }.into();
859 }
860 lance_core::Error::from(NamespaceError::Internal {
861 message: format!("{}: {:?}", operation, e),
862 })
863 }
864 }
865}
866
867impl ManifestNamespace {
868 #[allow(clippy::too_many_arguments)]
870 pub async fn from_directory(
871 root: String,
872 storage_options: Option<HashMap<String, String>>,
873 session: Option<Arc<Session>>,
874 object_store: Arc<ObjectStore>,
875 base_path: Path,
876 dir_listing_enabled: bool,
877 inline_optimization_enabled: bool,
878 commit_retries: Option<u32>,
879 ) -> Result<Self> {
880 let manifest_dataset =
881 Self::ensure_manifest_table_up_to_date(&root, &storage_options, session.clone())
882 .await?;
883
884 Ok(Self::new(
885 root,
886 storage_options,
887 session,
888 object_store,
889 base_path,
890 manifest_dataset,
891 dir_listing_enabled,
892 inline_optimization_enabled,
893 commit_retries,
894 ))
895 }
896
897 #[allow(clippy::too_many_arguments)]
899 pub async fn open_from_directory(
900 root: String,
901 storage_options: Option<HashMap<String, String>>,
902 session: Option<Arc<Session>>,
903 object_store: Arc<ObjectStore>,
904 base_path: Path,
905 dir_listing_enabled: bool,
906 inline_optimization_enabled: bool,
907 commit_retries: Option<u32>,
908 ) -> Result<Self> {
909 let manifest_dataset =
910 Self::open_manifest_table(&root, &storage_options, session.clone()).await?;
911
912 Ok(Self::new(
913 root,
914 storage_options,
915 session,
916 object_store,
917 base_path,
918 manifest_dataset,
919 dir_listing_enabled,
920 inline_optimization_enabled,
921 commit_retries,
922 ))
923 }
924
925 #[allow(clippy::too_many_arguments)]
926 fn new(
927 root: String,
928 storage_options: Option<HashMap<String, String>>,
929 session: Option<Arc<Session>>,
930 object_store: Arc<ObjectStore>,
931 base_path: Path,
932 manifest_dataset: DatasetConsistencyWrapper,
933 dir_listing_enabled: bool,
934 inline_optimization_enabled: bool,
935 commit_retries: Option<u32>,
936 ) -> Self {
937 Self {
938 root,
939 storage_options,
940 session,
941 object_store,
942 base_path,
943 manifest_dataset,
944 dir_listing_enabled,
945 inline_optimization_enabled,
946 commit_retries,
947 manifest_mutation_lock: Arc::new(Mutex::new(())),
948 }
949 }
950
951 pub fn build_object_id(namespace: &[String], name: &str) -> String {
953 if namespace.is_empty() {
954 name.to_string()
955 } else {
956 let mut id = namespace.join(DELIMITER);
957 id.push_str(DELIMITER);
958 id.push_str(name);
959 id
960 }
961 }
962
963 pub fn parse_object_id(object_id: &str) -> (Vec<String>, String) {
965 let parts: Vec<&str> = object_id.split(DELIMITER).collect();
966 if parts.len() == 1 {
967 (Vec::new(), parts[0].to_string())
968 } else {
969 let namespace = parts[..parts.len() - 1]
970 .iter()
971 .map(|s| s.to_string())
972 .collect();
973 let name = parts[parts.len() - 1].to_string();
974 (namespace, name)
975 }
976 }
977
978 pub fn split_object_id(object_id: &[String]) -> (Vec<String>, String) {
980 if object_id.len() == 1 {
981 (vec![], object_id[0].clone())
982 } else {
983 (
984 object_id[..object_id.len() - 1].to_vec(),
985 object_id[object_id.len() - 1].clone(),
986 )
987 }
988 }
989
990 pub fn str_object_id(object_id: &[String]) -> String {
992 object_id.join(DELIMITER)
993 }
994
995 fn format_table_id(table_id: &[String]) -> String {
996 format!("table id '{}'", Self::str_object_id(table_id))
997 }
998
999 pub fn generate_dir_name(object_id: &str) -> String {
1006 let random_num: u64 = rand::random();
1008
1009 let mut hasher = DefaultHasher::new();
1011 random_num.hash(&mut hasher);
1012 object_id.hash(&mut hasher);
1013 let hash = hasher.finish();
1014
1015 format!("{:08x}_{}", (hash & 0xFFFFFFFF) as u32, object_id)
1017 }
1018
1019 pub(crate) fn construct_full_uri(root: &str, relative_location: &str) -> Result<String> {
1021 let mut base_url = lance_io::object_store::uri_to_url(root)?;
1022
1023 if !base_url.path().ends_with('/') {
1028 base_url.set_path(&format!("{}/", base_url.path()));
1029 }
1030
1031 let mut full_url = base_url.clone();
1032 full_url
1033 .path_segments_mut()
1034 .map_err(|_| {
1035 lance_core::Error::from(NamespaceError::InvalidInput {
1036 message: format!("Cannot modify path segments for URI '{}'", root),
1037 })
1038 })?
1039 .pop_if_empty()
1040 .extend(
1041 relative_location
1042 .split('/')
1043 .filter(|segment| !segment.is_empty()),
1044 );
1045
1046 full_url.set_query(None);
1050
1051 Ok(full_url.to_string())
1052 }
1053
1054 fn string_list_array(values: &[Option<Vec<String>>], child_name: &str) -> ListArray {
1055 let string_builder = StringBuilder::new();
1056 let mut list_builder = ListBuilder::new(string_builder).with_field(Arc::new(Field::new(
1057 child_name,
1058 DataType::Utf8,
1059 true,
1060 )));
1061 for value in values {
1062 match value {
1063 Some(objects) => {
1064 for object in objects {
1065 list_builder.values().append_value(object);
1066 }
1067 list_builder.append(true);
1068 }
1069 None => list_builder.append_null(),
1070 }
1071 }
1072 list_builder.finish()
1073 }
1074
1075 fn base_objects_array(values: &[Option<Vec<String>>]) -> ListArray {
1076 Self::string_list_array(values, "object_id")
1077 }
1078
1079 fn value_row_id_schema(value_field: Field) -> SchemaRef {
1080 Arc::new(ArrowSchema::new(vec![
1081 value_field,
1082 Field::new(ROW_ID, DataType::UInt64, false),
1083 ]))
1084 }
1085
1086 fn string_row_id_batch(
1087 schema: SchemaRef,
1088 values: Vec<String>,
1089 row_ids: Vec<u64>,
1090 ) -> Result<RecordBatch> {
1091 RecordBatch::try_new(
1092 schema,
1093 vec![
1094 Arc::new(StringArray::from(values)),
1095 Arc::new(UInt64Array::from(row_ids)),
1096 ],
1097 )
1098 .map_err(Into::into)
1099 }
1100
1101 fn list_row_id_batch(
1102 schema: SchemaRef,
1103 values: Vec<Option<Vec<String>>>,
1104 row_ids: Vec<u64>,
1105 ) -> Result<RecordBatch> {
1106 RecordBatch::try_new(
1107 schema,
1108 vec![
1109 Arc::new(Self::string_list_array(&values, "item")),
1110 Arc::new(UInt64Array::from(row_ids)),
1111 ],
1112 )
1113 .map_err(Into::into)
1114 }
1115
1116 fn object_id_index_stream(object_ids: BTreeMap<Arc<str>, u64>) -> SendableRecordBatchStream {
1117 let schema =
1118 Self::value_row_id_schema(Field::new(VALUE_COLUMN_NAME, DataType::Utf8, false));
1119 let stream_schema = schema.clone();
1120 let stream = stream::unfold(
1121 (object_ids.into_iter(), false, schema),
1122 |(mut iter, emitted, schema)| async move {
1123 let mut values = Vec::with_capacity(MANIFEST_INDEX_BATCH_SIZE);
1124 let mut row_ids = Vec::with_capacity(MANIFEST_INDEX_BATCH_SIZE);
1125 for _ in 0..MANIFEST_INDEX_BATCH_SIZE {
1126 let Some((value, row_id)) = iter.next() else {
1127 break;
1128 };
1129 values.push(value.to_string());
1130 row_ids.push(row_id);
1131 }
1132 if values.is_empty() {
1133 if emitted {
1134 None
1135 } else {
1136 let batch = Self::string_row_id_batch(schema.clone(), values, row_ids)
1137 .map_err(|err| DataFusionError::External(Box::new(err)));
1138 Some((batch, (iter, true, schema)))
1139 }
1140 } else {
1141 let batch = Self::string_row_id_batch(schema.clone(), values, row_ids)
1142 .map_err(|err| DataFusionError::External(Box::new(err)));
1143 Some((batch, (iter, true, schema)))
1144 }
1145 },
1146 );
1147 Box::pin(DatafusionRecordBatchStreamAdapter::new(
1148 stream_schema,
1149 stream.fuse(),
1150 ))
1151 }
1152
1153 fn object_type_index_stream(
1154 object_types: BTreeMap<&'static str, RoaringBitmap>,
1155 ) -> SendableRecordBatchStream {
1156 let schema =
1157 Self::value_row_id_schema(Field::new(VALUE_COLUMN_NAME, DataType::Utf8, false));
1158 let stream_schema = schema.clone();
1159 let entries = object_types
1160 .into_iter()
1161 .map(|(value, bitmap)| {
1162 (
1163 value,
1164 Box::new(bitmap.into_iter()) as Box<dyn Iterator<Item = u32> + Send>,
1165 )
1166 })
1167 .collect::<Vec<_>>()
1168 .into_iter();
1169 let stream = stream::unfold(
1170 (entries, None, false, schema),
1171 |(mut entries, mut current, emitted, schema)| async move {
1172 let mut values = Vec::with_capacity(MANIFEST_INDEX_BATCH_SIZE);
1173 let mut row_ids = Vec::with_capacity(MANIFEST_INDEX_BATCH_SIZE);
1174 while values.len() < MANIFEST_INDEX_BATCH_SIZE {
1175 if current.is_none() {
1176 current = entries.next();
1177 }
1178 let Some((value, iter)) = current.as_mut() else {
1179 break;
1180 };
1181 if let Some(row_id) = iter.next() {
1182 values.push((*value).to_string());
1183 row_ids.push(u64::from(row_id));
1184 } else {
1185 current = None;
1186 }
1187 }
1188
1189 if values.is_empty() {
1190 if emitted {
1191 None
1192 } else {
1193 let batch = Self::string_row_id_batch(schema.clone(), values, row_ids)
1194 .map_err(|err| DataFusionError::External(Box::new(err)));
1195 Some((batch, (entries, current, true, schema)))
1196 }
1197 } else {
1198 let batch = Self::string_row_id_batch(schema.clone(), values, row_ids)
1199 .map_err(|err| DataFusionError::External(Box::new(err)));
1200 Some((batch, (entries, current, true, schema)))
1201 }
1202 },
1203 );
1204 Box::pin(DatafusionRecordBatchStreamAdapter::new(
1205 stream_schema,
1206 stream.fuse(),
1207 ))
1208 }
1209
1210 fn base_objects_index_stream(
1211 base_objects_values: Vec<Option<Vec<String>>>,
1212 base_objects_row_ids: Vec<u64>,
1213 ) -> SendableRecordBatchStream {
1214 let schema = Self::value_row_id_schema(BASE_OBJECTS_VALUE_FIELD.clone());
1215 let stream_schema = schema.clone();
1216 let stream = stream::unfold(
1217 (
1218 base_objects_values.into_iter().zip(base_objects_row_ids),
1219 false,
1220 schema,
1221 ),
1222 |(mut iter, emitted, schema)| async move {
1223 let mut values = Vec::with_capacity(MANIFEST_INDEX_BATCH_SIZE);
1224 let mut row_ids = Vec::with_capacity(MANIFEST_INDEX_BATCH_SIZE);
1225 for _ in 0..MANIFEST_INDEX_BATCH_SIZE {
1226 let Some((value, row_id)) = iter.next() else {
1227 break;
1228 };
1229 values.push(value);
1230 row_ids.push(row_id);
1231 }
1232 if values.is_empty() {
1233 if emitted {
1234 None
1235 } else {
1236 let batch = Self::list_row_id_batch(schema.clone(), values, row_ids)
1237 .map_err(|err| DataFusionError::External(Box::new(err)));
1238 Some((batch, (iter, true, schema)))
1239 }
1240 } else {
1241 let batch = Self::list_row_id_batch(schema.clone(), values, row_ids)
1242 .map_err(|err| DataFusionError::External(Box::new(err)));
1243 Some((batch, (iter, true, schema)))
1244 }
1245 },
1246 );
1247 Box::pin(DatafusionRecordBatchStreamAdapter::new(
1248 stream_schema,
1249 stream.fuse(),
1250 ))
1251 }
1252
1253 async fn train_manifest_index(
1254 dataset: &Dataset,
1255 registry: Arc<IndexPluginRegistry>,
1256 input: ManifestIndexBuildInput,
1257 index_uuid: Uuid,
1258 ) -> Result<ManifestTrainedIndex> {
1259 let index_store = LanceIndexStore::from_dataset_for_new(dataset, &index_uuid)?;
1260 let trainer = registry
1261 .get_plugin_by_name(&input.params.index_type)?
1262 .basic_trainer()
1263 .ok_or_else(|| {
1264 lance_core::Error::invalid_input_source(
1265 format!(
1266 "The '{}' index type does not support basic training, please refer to the index's documentation for more details on how to create this index.",
1267 input.params.index_type
1268 )
1269 .into(),
1270 )
1271 })?;
1272 let training_request = trainer
1273 .new_training_request(input.params.params.as_deref().unwrap_or("{}"), &input.field)?;
1274 let created_index = trainer
1275 .train_index(
1276 input.stream,
1277 &index_store,
1278 training_request,
1279 None,
1280 noop_progress(),
1281 )
1282 .await?;
1283 Ok(ManifestTrainedIndex {
1284 index_name: input.index_name,
1285 column_name: input.column_name,
1286 uuid: index_uuid,
1287 created_index,
1288 })
1289 }
1290
1291 fn manifest_index_metadata(
1292 lance_schema: &lance_core::datatypes::Schema,
1293 fragment_bitmap: &RoaringBitmap,
1294 dataset_version: u64,
1295 trained_index: ManifestTrainedIndex,
1296 ) -> Result<IndexMetadata> {
1297 Ok(IndexMetadata {
1298 uuid: trained_index.uuid,
1299 fields: vec![lance_schema.field_id(trained_index.column_name)?],
1300 covering_fields: vec![],
1301 name: trained_index.index_name.to_string(),
1302 dataset_version,
1303 fragment_bitmap: Some(fragment_bitmap.clone()),
1304 index_details: Some(Arc::new(trained_index.created_index.index_details)),
1305 index_version: trained_index.created_index.index_version as i32,
1306 created_at: None,
1307 base_id: None,
1308 files: Some(index_files_to_table(trained_index.created_index.files)),
1309 })
1310 }
1311
1312 fn manifest_fragment_bitmap(manifest: &Manifest) -> Result<RoaringBitmap> {
1313 let mut bitmap = RoaringBitmap::new();
1314 for fragment in manifest.fragments.iter() {
1315 let fragment_id = u32::try_from(fragment.id).map_err(|_| {
1316 lance_core::Error::from(NamespaceError::Internal {
1317 message: format!("Manifest fragment id {} exceeds u32", fragment.id),
1318 })
1319 })?;
1320 bitmap.insert(fragment_id);
1321 }
1322 Ok(bitmap)
1323 }
1324
1325 fn manifest_from_overwrite_transaction(
1326 previous: &Manifest,
1327 schema: lance_core::datatypes::Schema,
1328 fragments: &[Fragment],
1329 ) -> Manifest {
1330 let mut next_fragment_id = 0;
1331 let mut fragments = fragments
1332 .iter()
1333 .cloned()
1334 .map(|mut fragment| {
1335 if fragment.id == 0 {
1336 fragment.id = next_fragment_id;
1337 next_fragment_id += 1;
1338 }
1339 fragment
1340 })
1341 .collect::<Vec<_>>();
1342 fragments.sort_by_key(|fragment| fragment.id);
1343 Manifest::new_from_previous(previous, schema, Arc::new(fragments))
1344 }
1345
1346 async fn build_manifest_indices(
1347 dataset: &Dataset,
1348 manifest: &Manifest,
1349 index_data: ManifestIndexAccumulator,
1350 index_uuids: [Uuid; 3],
1351 ) -> Result<Vec<IndexMetadata>> {
1352 let fragment_bitmap = Self::manifest_fragment_bitmap(manifest)?;
1353 let schema = &manifest.schema;
1354 let ManifestIndexAccumulator {
1355 object_ids,
1356 object_types,
1357 base_objects_values,
1358 base_objects_row_ids,
1359 ..
1360 } = index_data;
1361 let [object_id_uuid, object_type_uuid, base_objects_uuid] = index_uuids;
1362 let registry = IndexPluginRegistry::with_default_plugins();
1363
1364 let dataset_version = manifest.version;
1365 let object_id_index_fut = Self::build_manifest_index(
1366 dataset,
1367 registry.clone(),
1368 schema,
1369 ManifestIndexBuildInput {
1370 index_name: OBJECT_ID_INDEX_NAME,
1371 column_name: "object_id",
1372 params: ScalarIndexParams::for_builtin(BuiltinIndexType::BTree),
1373 field: Field::new(VALUE_COLUMN_NAME, DataType::Utf8, false),
1374 stream: Self::object_id_index_stream(object_ids),
1375 },
1376 &fragment_bitmap,
1377 dataset_version,
1378 object_id_uuid,
1379 );
1380 let object_type_index_fut = Self::build_manifest_index(
1381 dataset,
1382 registry.clone(),
1383 schema,
1384 ManifestIndexBuildInput {
1385 index_name: OBJECT_TYPE_INDEX_NAME,
1386 column_name: "object_type",
1387 params: ScalarIndexParams::for_builtin(BuiltinIndexType::Bitmap),
1388 field: Field::new(VALUE_COLUMN_NAME, DataType::Utf8, false),
1389 stream: Self::object_type_index_stream(object_types),
1390 },
1391 &fragment_bitmap,
1392 dataset_version,
1393 object_type_uuid,
1394 );
1395 let base_objects_index_fut = Self::build_manifest_index(
1396 dataset,
1397 registry,
1398 schema,
1399 ManifestIndexBuildInput {
1400 index_name: BASE_OBJECTS_INDEX_NAME,
1401 column_name: "base_objects",
1402 params: ScalarIndexParams::for_builtin(BuiltinIndexType::LabelList),
1403 field: BASE_OBJECTS_VALUE_FIELD.clone(),
1404 stream: Self::base_objects_index_stream(base_objects_values, base_objects_row_ids),
1405 },
1406 &fragment_bitmap,
1407 dataset_version,
1408 base_objects_uuid,
1409 );
1410
1411 let (object_id_index, object_type_index, base_objects_index) = futures::join!(
1412 object_id_index_fut,
1413 object_type_index_fut,
1414 base_objects_index_fut
1415 );
1416
1417 Ok(vec![
1418 object_id_index?,
1419 object_type_index?,
1420 base_objects_index?,
1421 ])
1422 }
1423
1424 async fn build_manifest_index(
1425 dataset: &Dataset,
1426 registry: Arc<IndexPluginRegistry>,
1427 lance_schema: &lance_core::datatypes::Schema,
1428 input: ManifestIndexBuildInput,
1429 fragment_bitmap: &RoaringBitmap,
1430 dataset_version: u64,
1431 index_uuid: Uuid,
1432 ) -> Result<IndexMetadata> {
1433 let trained_index =
1434 Self::train_manifest_index(dataset, registry, input, index_uuid).await?;
1435 Self::manifest_index_metadata(
1436 lance_schema,
1437 fragment_bitmap,
1438 dataset_version,
1439 trained_index,
1440 )
1441 }
1442
1443 fn manifest_schema() -> Arc<ArrowSchema> {
1445 Arc::new(ArrowSchema::new(vec![
1446 Field::new("object_id", DataType::Utf8, false).with_metadata(
1448 [(
1449 LANCE_UNENFORCED_PRIMARY_KEY_POSITION.to_string(),
1450 "0".to_string(),
1451 )]
1452 .into_iter()
1453 .collect(),
1454 ),
1455 Field::new("object_type", DataType::Utf8, false),
1456 Field::new("location", DataType::Utf8, true),
1457 Field::new("metadata", DataType::Utf8, true),
1458 Field::new(
1459 "base_objects",
1460 DataType::List(Arc::new(Field::new("object_id", DataType::Utf8, true))),
1461 true,
1462 ),
1463 ]))
1464 }
1465
1466 async fn manifest_scanner(&self) -> Result<Scanner> {
1468 let dataset_guard = self.manifest_dataset.get().await?;
1469 Ok(dataset_guard.scan())
1470 }
1471
1472 async fn execute_scanner(scanner: Scanner) -> Result<Vec<RecordBatch>> {
1474 let mut stream = scanner.try_into_stream().await.map_err(|e| {
1475 lance_core::Error::from(NamespaceError::Internal {
1476 message: format!("Failed to create stream: {:?}", e),
1477 })
1478 })?;
1479
1480 let mut batches = Vec::new();
1481 while let Some(batch) = stream.next().await {
1482 batches.push(batch.map_err(|e| {
1483 lance_core::Error::from(NamespaceError::Internal {
1484 message: format!("Failed to read batch: {:?}", e),
1485 })
1486 })?);
1487 }
1488
1489 Ok(batches)
1490 }
1491
1492 fn get_string_column<'a>(batch: &'a RecordBatch, column_name: &str) -> Result<&'a StringArray> {
1494 let column = batch.column_by_name(column_name).ok_or_else(|| {
1495 lance_core::Error::from(NamespaceError::Internal {
1496 message: format!("Column '{}' not found", column_name),
1497 })
1498 })?;
1499 column
1500 .as_any()
1501 .downcast_ref::<StringArray>()
1502 .ok_or_else(|| {
1503 lance_core::Error::from(NamespaceError::Internal {
1504 message: format!("Column '{}' is not a string array", column_name),
1505 })
1506 })
1507 }
1508
1509 fn required_string_value<'a>(
1510 array: &'a StringArray,
1511 row: usize,
1512 column_name: &str,
1513 ) -> Result<&'a str> {
1514 if array.is_null(row) {
1515 return Err(NamespaceError::Internal {
1516 message: format!("Manifest column '{}' has null at row {}", column_name, row),
1517 }
1518 .into());
1519 }
1520 Ok(array.value(row))
1521 }
1522
1523 fn optional_string_value(array: &StringArray, row: usize) -> Option<String> {
1524 (!array.is_null(row)).then(|| array.value(row).to_string())
1525 }
1526
1527 fn base_objects_column_values(batch: &RecordBatch) -> Result<Vec<Option<Vec<String>>>> {
1528 let Some(column) = batch.column_by_name("base_objects") else {
1529 return Ok(vec![None; batch.num_rows()]);
1530 };
1531 let array = column.as_any().downcast_ref::<ListArray>().ok_or_else(|| {
1532 lance_core::Error::from(NamespaceError::Internal {
1533 message: format!(
1534 "Column 'base_objects' is not a list array: {:?}",
1535 column.data_type()
1536 ),
1537 })
1538 })?;
1539
1540 let mut values = Vec::with_capacity(batch.num_rows());
1541 for row in 0..batch.num_rows() {
1542 if array.is_null(row) {
1543 values.push(None);
1544 continue;
1545 }
1546 let row_values = array.value(row);
1547 let row_values = row_values
1548 .as_any()
1549 .downcast_ref::<StringArray>()
1550 .ok_or_else(|| {
1551 lance_core::Error::from(NamespaceError::Internal {
1552 message: "Column 'base_objects' values are not strings".to_string(),
1553 })
1554 })?;
1555 let mut objects = Vec::with_capacity(row_values.len());
1556 for value_index in 0..row_values.len() {
1557 if row_values.is_null(value_index) {
1558 return Err(NamespaceError::Internal {
1559 message: format!(
1560 "Manifest column 'base_objects' has null item at row {} item {}",
1561 row, value_index
1562 ),
1563 }
1564 .into());
1565 }
1566 objects.push(row_values.value(value_index).to_string());
1567 }
1568 values.push(Some(objects));
1569 }
1570 Ok(values)
1571 }
1572
1573 async fn manifest_projected_stream(dataset: &Dataset) -> Result<SendableRecordBatchStream> {
1574 let mut scanner = dataset.scan();
1575 scanner
1576 .project(&[
1577 "object_id",
1578 "object_type",
1579 "location",
1580 "metadata",
1581 "base_objects",
1582 ])
1583 .map_err(|e| {
1584 lance_core::Error::from(NamespaceError::Internal {
1585 message: format!("Failed to project manifest columns: {:?}", e),
1586 })
1587 })?;
1588 let stream = scanner.try_into_stream().await.map_err(|e| {
1589 lance_core::Error::from(NamespaceError::Internal {
1590 message: format!("Failed to create manifest stream: {:?}", e),
1591 })
1592 })?;
1593 let schema = stream.schema();
1594 let stream = stream.map_err(|err| DataFusionError::External(Box::new(err)));
1595 Ok(Box::pin(DatafusionRecordBatchStreamAdapter::new(
1596 schema,
1597 stream.fuse(),
1598 )))
1599 }
1600
1601 fn manifest_rewrite_commit_retries(&self) -> u32 {
1602 self.commit_retries
1603 .unwrap_or(DEFAULT_MANIFEST_REWRITE_COMMIT_RETRIES)
1604 }
1605
1606 fn lock_manifest_rewrite_shared<M: ManifestStreamMutation>(
1607 shared: &Arc<StdMutex<ManifestRewriteShared<M>>>,
1608 ) -> Result<StdMutexGuard<'_, ManifestRewriteShared<M>>> {
1609 shared.lock().map_err(|_| {
1610 lance_core::Error::from(NamespaceError::Internal {
1611 message: "Manifest rewrite state mutex was poisoned".to_string(),
1612 })
1613 })
1614 }
1615
1616 fn set_manifest_rewrite_error<M: ManifestStreamMutation>(
1617 shared: &Arc<StdMutex<ManifestRewriteShared<M>>>,
1618 err: LanceError,
1619 ) {
1620 match shared.lock() {
1621 Ok(mut guard) => {
1622 guard.error = Some(err);
1623 }
1624 Err(poisoned) => {
1625 let mut guard = poisoned.into_inner();
1626 guard.error = Some(err);
1627 }
1628 }
1629 }
1630
1631 fn take_manifest_rewrite_error<M: ManifestStreamMutation>(
1632 shared: &Arc<StdMutex<ManifestRewriteShared<M>>>,
1633 ) -> Result<Option<LanceError>> {
1634 let mut guard = Self::lock_manifest_rewrite_shared(shared)?;
1635 Ok(guard.error.take())
1636 }
1637
1638 fn process_manifest_rewrite_batch<M: ManifestStreamMutation>(
1639 batch: RecordBatch,
1640 shared: &Arc<StdMutex<ManifestRewriteShared<M>>>,
1641 ) -> Result<Option<RecordBatch>> {
1642 let object_ids = Self::get_string_column(&batch, "object_id")?;
1643 let object_types = Self::get_string_column(&batch, "object_type")?;
1644 let locations = Self::get_string_column(&batch, "location")?;
1645 let metadatas = Self::get_string_column(&batch, "metadata")?;
1646 let base_objects = Self::base_objects_column_values(&batch)?;
1647 let mut output = ManifestBatchBuilder::new();
1648 let mut guard = Self::lock_manifest_rewrite_shared(shared)?;
1649 let mut index_data = guard.index_data.take().ok_or_else(|| {
1650 lance_core::Error::from(NamespaceError::Internal {
1651 message: "Manifest rewrite index state is unavailable".to_string(),
1652 })
1653 })?;
1654 for (row, base_objects) in base_objects.into_iter().enumerate().take(batch.num_rows()) {
1655 let row_value = ManifestRowValue {
1656 object_id: Self::required_string_value(object_ids, row, "object_id")?.to_string(),
1657 object_type: ObjectType::parse(Self::required_string_value(
1658 object_types,
1659 row,
1660 "object_type",
1661 )?)?,
1662 location: Self::optional_string_value(locations, row),
1663 metadata: Self::optional_string_value(metadatas, row),
1664 base_objects,
1665 };
1666 guard
1667 .mutation
1668 .process_existing_row(row_value, &mut output, &mut index_data)?;
1669 }
1670 guard.index_data = Some(index_data);
1671 if output.is_empty() {
1672 return Ok(None);
1673 }
1674 Ok(Some(output.finish()?))
1675 }
1676
1677 fn finish_manifest_rewrite_stream<M: ManifestStreamMutation>(
1678 shared: &Arc<StdMutex<ManifestRewriteShared<M>>>,
1679 ) -> Result<Option<RecordBatch>> {
1680 let mut output = ManifestBatchBuilder::new();
1681 let mut guard = Self::lock_manifest_rewrite_shared(shared)?;
1682 let mut index_data = guard.index_data.take().ok_or_else(|| {
1683 lance_core::Error::from(NamespaceError::Internal {
1684 message: "Manifest rewrite index state is unavailable".to_string(),
1685 })
1686 })?;
1687 guard.mutation.append_rows(&mut output, &mut index_data)?;
1688 let result = guard.mutation.finish();
1689 let force_empty_batch = index_data.row_count == 0;
1690 guard.result = Some(result);
1691 guard.index_data = Some(index_data);
1692 if output.is_empty() && !force_empty_batch {
1693 Ok(None)
1694 } else {
1695 Ok(Some(output.finish()?))
1696 }
1697 }
1698
1699 fn manifest_rewrite_output_stream<M: ManifestStreamMutation + 'static>(
1700 source: SendableRecordBatchStream,
1701 shared: Arc<StdMutex<ManifestRewriteShared<M>>>,
1702 ) -> SendableRecordBatchStream {
1703 enum Phase {
1704 Source,
1705 Finish,
1706 Done,
1707 }
1708
1709 let schema = Self::manifest_schema();
1710 let stream = stream::unfold(
1711 (source, shared, Phase::Source),
1712 |(mut source, shared, mut phase)| async move {
1713 loop {
1714 match phase {
1715 Phase::Source => match source.next().await {
1716 Some(Ok(batch)) => {
1717 match Self::process_manifest_rewrite_batch(batch, &shared) {
1718 Ok(Some(batch)) => {
1719 return Some((Ok(batch), (source, shared, phase)));
1720 }
1721 Ok(None) => continue,
1722 Err(err) => {
1723 let message = err.to_string();
1724 Self::set_manifest_rewrite_error(&shared, err);
1725 return Some((
1726 Err(DataFusionError::External(Box::new(
1727 std::io::Error::other(message),
1728 ))),
1729 (source, shared, Phase::Done),
1730 ));
1731 }
1732 }
1733 }
1734 Some(Err(err)) => {
1735 return Some((Err(err), (source, shared, Phase::Done)));
1736 }
1737 None => phase = Phase::Finish,
1738 },
1739 Phase::Finish => {
1740 phase = Phase::Done;
1741 match Self::finish_manifest_rewrite_stream(&shared) {
1742 Ok(Some(batch)) => {
1743 return Some((Ok(batch), (source, shared, phase)));
1744 }
1745 Ok(None) => continue,
1746 Err(err) => {
1747 let message = err.to_string();
1748 Self::set_manifest_rewrite_error(&shared, err);
1749 return Some((
1750 Err(DataFusionError::External(Box::new(
1751 std::io::Error::other(message),
1752 ))),
1753 (source, shared, Phase::Done),
1754 ));
1755 }
1756 }
1757 }
1758 Phase::Done => return None,
1759 }
1760 }
1761 },
1762 );
1763 Box::pin(DatafusionRecordBatchStreamAdapter::new(
1764 schema,
1765 stream.fuse(),
1766 ))
1767 }
1768
1769 fn take_manifest_rewrite_result<M: ManifestStreamMutation>(
1770 shared: &Arc<StdMutex<ManifestRewriteShared<M>>>,
1771 ) -> Result<(CopyOnWriteMutation<M::Output>, ManifestIndexAccumulator)> {
1772 let mut guard = Self::lock_manifest_rewrite_shared(shared)?;
1773 let result = guard.result.take().ok_or_else(|| {
1774 lance_core::Error::from(NamespaceError::Internal {
1775 message: "Manifest rewrite stream did not finish".to_string(),
1776 })
1777 })?;
1778 let index_data = guard.index_data.take().ok_or_else(|| {
1779 lance_core::Error::from(NamespaceError::Internal {
1780 message: "Manifest rewrite index state is unavailable".to_string(),
1781 })
1782 })?;
1783 Ok((result, index_data))
1784 }
1785
1786 async fn cleanup_staged_manifest_files(
1791 &self,
1792 object_store: &ObjectStore,
1793 data_files: &HashSet<String>,
1794 index_uuids: &[Uuid],
1795 ) {
1796 let data_dir = self
1797 .base_path
1798 .clone()
1799 .join(MANIFEST_TABLE_NAME)
1800 .join(LANCE_DATA_DIR);
1801 for path in data_files {
1802 let data_path = data_dir.clone().join(path.as_str());
1803 if let Err(err) = object_store.delete(&data_path).await {
1804 log::warn!(
1805 "Failed to clean up uncommitted manifest rewrite data file '{}': {}",
1806 data_path,
1807 err
1808 );
1809 }
1810 }
1811 self.cleanup_uncommitted_manifest_index_dirs(object_store, index_uuids.iter().copied())
1812 .await;
1813 }
1814
1815 async fn cleanup_uncommitted_manifest_index_dirs(
1816 &self,
1817 object_store: &ObjectStore,
1818 index_uuids: impl IntoIterator<Item = Uuid>,
1819 ) {
1820 for index_uuid in index_uuids {
1821 let index_dir = self
1822 .base_path
1823 .clone()
1824 .join(MANIFEST_TABLE_NAME)
1825 .join(LANCE_INDICES_DIR)
1826 .join(index_uuid.to_string());
1827 if let Err(err) = object_store.remove_dir_all(index_dir.clone()).await
1828 && !matches!(err, LanceError::NotFound { .. })
1829 {
1830 log::warn!(
1831 "Failed to clean up uncommitted manifest rewrite index directory '{}': {}",
1832 index_dir,
1833 err
1834 );
1835 }
1836 }
1837 }
1838
1839 async fn manifest_commit_handler(&self) -> Result<Arc<dyn CommitHandler>> {
1841 commit_handler_from_url(&self.root, &None)
1842 .await
1843 .map_err(|e| {
1844 lance_core::Error::from(NamespaceError::Internal {
1845 message: format!("Failed to resolve manifest commit handler: {:?}", e),
1846 })
1847 })
1848 }
1849
1850 async fn commit_manifest_overwrite(
1854 &self,
1855 dataset: &Dataset,
1856 commit_handler: &dyn CommitHandler,
1857 manifest: &mut Manifest,
1858 indices: Option<Vec<IndexMetadata>>,
1859 transaction: Transaction,
1860 ) -> std::result::Result<(), CommitError> {
1861 ensure_can_write_manifest(manifest).map_err(CommitError::from)?;
1862 apply_feature_flags(manifest, false, false).map_err(CommitError::from)?;
1863 let timestamp_nanos = SystemTime::now()
1864 .duration_since(UNIX_EPOCH)
1865 .map(|d| d.as_nanos())
1866 .unwrap_or(0);
1867 manifest.set_timestamp(timestamp_nanos);
1868 manifest.update_max_fragment_id();
1869
1870 let object_store = dataset
1874 .object_store(None)
1875 .await
1876 .map_err(CommitError::from)?;
1877 let base_path = self.base_path.clone().join(MANIFEST_TABLE_NAME);
1878 let naming_scheme = dataset.manifest_location().naming_scheme;
1879 commit_handler
1880 .commit(
1881 manifest,
1882 indices,
1883 &base_path,
1884 &object_store,
1885 write_manifest_file_to_path,
1886 naming_scheme,
1887 Some((&transaction).into()),
1888 )
1889 .await
1890 .map(|_location| ())
1891 }
1892
1893 async fn manifest_commit_landed(
1898 &self,
1899 dataset: &Dataset,
1900 target_version: u64,
1901 data_files: &HashSet<String>,
1902 ) -> bool {
1903 let Ok(committed) = dataset.checkout_version(target_version).await else {
1904 return false;
1905 };
1906 committed.manifest().fragments.iter().any(|fragment| {
1907 fragment
1908 .files
1909 .iter()
1910 .any(|file| data_files.contains(file.path.as_str()))
1911 })
1912 }
1913
1914 async fn resolve_manifest_conflict<O: Clone>(
1918 &self,
1919 resolution: &ConflictResolution<O>,
1920 ) -> Result<Option<O>> {
1921 match resolution {
1922 ConflictResolution::Retry => Ok(None),
1923 ConflictResolution::FailIfExists(object_ids) => {
1924 for object_id in object_ids {
1925 if self.manifest_contains_object(object_id).await? {
1926 return Err(NamespaceError::ConcurrentModification {
1927 message: format!(
1928 "Object '{}' was concurrently created by another operation",
1929 object_id
1930 ),
1931 }
1932 .into());
1933 }
1934 }
1935 Ok(None)
1936 }
1937 ConflictResolution::SucceedIfAbsent { object_id, output } => {
1938 if self.manifest_contains_object(object_id).await? {
1939 Ok(None)
1940 } else {
1941 Ok(Some(output.clone()))
1942 }
1943 }
1944 }
1945 }
1946
1947 async fn ensure_manifest_writable(&self) -> Result<()> {
1953 let dataset_guard = self.manifest_dataset.get().await?;
1954 ensure_can_write_manifest(dataset_guard.manifest())?;
1955 ensure_writable(dataset_guard.metadata())
1956 }
1957
1958 async fn rewrite_manifest<M, F>(
1959 &self,
1960 operation: &str,
1961 mut make_mutation: F,
1962 ) -> Result<M::Output>
1963 where
1964 M: ManifestStreamMutation + 'static,
1965 F: FnMut() -> M,
1966 {
1967 let _mutation_guard = self.manifest_mutation_lock.lock().await;
1968 let max_retries = self.manifest_rewrite_commit_retries();
1969 let mut retries = 0;
1970 let build_indices = self.inline_optimization_enabled;
1971 let commit_handler = self.manifest_commit_handler().await?;
1972
1973 loop {
1974 let dataset_guard = self.manifest_dataset.get_refreshed().await?;
1975 ensure_can_write_manifest(dataset_guard.manifest())?;
1976 let dataset = Arc::new(dataset_guard.clone());
1977 drop(dataset_guard);
1978 ensure_writable(dataset.metadata())?;
1981 let object_store = dataset.object_store(None).await?;
1984
1985 let source = Self::manifest_projected_stream(&dataset).await?;
1986 let resolution = make_mutation().conflict_resolution();
1987 let shared = Arc::new(StdMutex::new(ManifestRewriteShared::new(make_mutation())));
1988 let output_stream = Self::manifest_rewrite_output_stream(source, shared.clone());
1989 let write_params = WriteParams {
1994 mode: WriteMode::Overwrite,
1995 session: self.session.clone(),
1996 max_rows_per_file: u32::MAX as usize,
1997 max_bytes_per_file: usize::MAX,
1998 skip_auto_cleanup: true,
1999 ..WriteParams::default()
2000 };
2001
2002 let transaction = match InsertBuilder::new(dataset.clone())
2003 .with_params(&write_params)
2004 .execute_uncommitted_stream(output_stream)
2005 .await
2006 {
2007 Ok(transaction) => transaction,
2008 Err(err) => {
2009 if let Some(stream_err) = Self::take_manifest_rewrite_error(&shared)? {
2010 return Err(stream_err);
2011 }
2012 return Err(convert_lance_commit_error(&err, operation, None));
2013 }
2014 };
2015
2016 let (mutation, index_data) = Self::take_manifest_rewrite_result(&shared)?;
2017
2018 let Operation::Overwrite {
2019 fragments, schema, ..
2020 } = &transaction.operation
2021 else {
2022 return Err(NamespaceError::Internal {
2023 message: "Manifest rewrite transaction is not an overwrite".to_string(),
2024 }
2025 .into());
2026 };
2027 let staged_data_files = fragments
2030 .iter()
2031 .flat_map(|fragment| fragment.files.iter())
2032 .filter(|file| file.base_id.is_none())
2033 .map(|file| file.path.clone())
2034 .collect::<HashSet<_>>();
2035
2036 if !mutation.has_changes {
2037 self.cleanup_staged_manifest_files(&object_store, &staged_data_files, &[])
2038 .await;
2039 return Ok(mutation.result);
2040 }
2041
2042 let mut manifest = Self::manifest_from_overwrite_transaction(
2043 dataset.manifest(),
2044 schema.clone(),
2045 fragments,
2046 );
2047 let target_version = manifest.version;
2048
2049 let index_uuids = [Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()];
2050 let indices = if build_indices {
2051 match Self::build_manifest_indices(&dataset, &manifest, index_data, index_uuids)
2052 .await
2053 {
2054 Ok(indices) => Some(indices),
2055 Err(err) => {
2056 self.cleanup_staged_manifest_files(
2057 &object_store,
2058 &staged_data_files,
2059 &index_uuids,
2060 )
2061 .await;
2062 return Err(err);
2063 }
2064 }
2065 } else {
2066 None
2067 };
2068 let staged_index_uuids: &[Uuid] = if build_indices { &index_uuids } else { &[] };
2069
2070 let commit_result = self
2071 .commit_manifest_overwrite(
2072 &dataset,
2073 commit_handler.as_ref(),
2074 &mut manifest,
2075 indices,
2076 transaction,
2077 )
2078 .await;
2079
2080 match commit_result {
2081 Ok(()) => {
2082 let _ = self.manifest_dataset.get_refreshed().await;
2083 return Ok(mutation.result);
2084 }
2085 Err(err) => {
2086 if self
2090 .manifest_commit_landed(&dataset, target_version, &staged_data_files)
2091 .await
2092 {
2093 let _ = self.manifest_dataset.get_refreshed().await;
2094 return Ok(mutation.result);
2095 }
2096 self.cleanup_staged_manifest_files(
2097 &object_store,
2098 &staged_data_files,
2099 staged_index_uuids,
2100 )
2101 .await;
2102 match err {
2103 CommitError::CommitConflict => {
2104 if let Some(output) =
2105 self.resolve_manifest_conflict(&resolution).await?
2106 {
2107 return Ok(output);
2108 }
2109 if retries >= max_retries {
2110 return Err(NamespaceError::ConcurrentModification {
2111 message: format!(
2112 "{}: still conflicting after {} retries",
2113 operation, max_retries
2114 ),
2115 }
2116 .into());
2117 }
2118 retries += 1;
2119 tokio::time::sleep(std::time::Duration::from_millis(
2120 10 * u64::from(retries),
2121 ))
2122 .await;
2123 }
2124 CommitError::OtherError(err) => {
2125 return Err(convert_lance_commit_error(&err, operation, None));
2126 }
2127 }
2128 }
2129 }
2130 }
2131 }
2132
2133 async fn manifest_contains_object(&self, object_id: &str) -> Result<bool> {
2135 let escaped_id = object_id.replace('\'', "''");
2136 let filter = format!("object_id = '{}'", escaped_id);
2137
2138 let dataset_guard = self.manifest_dataset.get().await?;
2139 let mut scanner = dataset_guard.scan();
2140
2141 scanner.filter(&filter).map_err(|e| {
2142 lance_core::Error::from(NamespaceError::Internal {
2143 message: format!("Failed to filter: {:?}", e),
2144 })
2145 })?;
2146
2147 scanner.project::<&str>(&[]).map_err(|e| {
2149 lance_core::Error::from(NamespaceError::Internal {
2150 message: format!("Failed to project: {:?}", e),
2151 })
2152 })?;
2153
2154 scanner.with_row_id();
2155
2156 let count = scanner.count_rows().await.map_err(|e| {
2157 lance_core::Error::from(NamespaceError::Internal {
2158 message: format!("Failed to count rows: {:?}", e),
2159 })
2160 })?;
2161
2162 Ok(count > 0)
2163 }
2164
2165 async fn query_manifest_for_table(&self, object_id: &str) -> Result<Option<TableInfo>> {
2167 let escaped_id = object_id.replace('\'', "''");
2168 let filter = format!("object_id = '{}' AND object_type = 'table'", escaped_id);
2169 let mut scanner = self.manifest_scanner().await?;
2170 scanner.filter(&filter).map_err(|e| {
2171 lance_core::Error::from(NamespaceError::Internal {
2172 message: format!("Failed to filter: {:?}", e),
2173 })
2174 })?;
2175 scanner
2176 .project(&["object_id", "location", "metadata"])
2177 .map_err(|e| {
2178 lance_core::Error::from(NamespaceError::Internal {
2179 message: format!("Failed to project: {:?}", e),
2180 })
2181 })?;
2182 let batches = Self::execute_scanner(scanner).await?;
2183
2184 let mut found_result: Option<TableInfo> = None;
2185 let mut total_rows = 0;
2186
2187 for batch in batches {
2188 if batch.num_rows() == 0 {
2189 continue;
2190 }
2191
2192 total_rows += batch.num_rows();
2193 if total_rows > 1 {
2194 return Err(NamespaceError::Internal {
2195 message: format!(
2196 "Expected exactly 1 table with id '{}', found {}",
2197 object_id, total_rows
2198 ),
2199 }
2200 .into());
2201 }
2202
2203 let object_id_array = Self::get_string_column(&batch, "object_id")?;
2204 let location_array = Self::get_string_column(&batch, "location")?;
2205 let metadata_array = Self::get_string_column(&batch, "metadata")?;
2206 let location = location_array.value(0).to_string();
2207 let metadata = if !metadata_array.is_null(0) {
2208 let metadata_str = metadata_array.value(0);
2209 match serde_json::from_str::<HashMap<String, String>>(metadata_str) {
2210 Ok(map) => Some(map),
2211 Err(e) => {
2212 return Err(NamespaceError::Internal {
2213 message: format!(
2214 "Failed to deserialize metadata for table '{}': {}",
2215 object_id, e
2216 ),
2217 }
2218 .into());
2219 }
2220 }
2221 } else {
2222 None
2223 };
2224 let (namespace, name) = Self::parse_object_id(object_id_array.value(0));
2225 found_result = Some(TableInfo {
2226 namespace,
2227 name,
2228 location,
2229 metadata,
2230 });
2231 }
2232
2233 Ok(found_result)
2234 }
2235
2236 fn serialize_metadata(
2237 properties: Option<&HashMap<String, String>>,
2238 object_type: &str,
2239 object_id: &str,
2240 ) -> Result<Option<String>> {
2241 match properties {
2242 Some(properties) if !properties.is_empty() => {
2243 serde_json::to_string(properties).map(Some).map_err(|e| {
2244 LanceError::from(NamespaceError::Internal {
2245 message: format!(
2246 "Failed to serialize {} metadata for '{}': {}",
2247 object_type, object_id, e
2248 ),
2249 })
2250 })
2251 }
2252 _ => Ok(None),
2253 }
2254 }
2255
2256 pub(crate) async fn path_has_actual_manifests(
2257 object_store: &ObjectStore,
2258 table_path: &Path,
2259 ) -> Result<bool> {
2260 let versions_path = table_path
2261 .clone()
2262 .join(lance_table::io::commit::VERSIONS_DIR);
2263 Ok(object_store
2266 .list(Some(versions_path))
2267 .try_next()
2268 .await?
2269 .is_some())
2270 }
2271
2272 async fn location_has_actual_manifests(&self, location: &str) -> Result<bool> {
2273 Self::path_has_actual_manifests(&self.object_store, &self.base_path.clone().join(location))
2274 .await
2275 }
2276
2277 pub(crate) fn is_not_found_load_error(err: &LanceError) -> bool {
2278 match err {
2279 LanceError::NotFound { .. } => true,
2280 LanceError::IO { source, .. } => source
2281 .downcast_ref::<ObjectStoreError>()
2282 .is_some_and(|source| matches!(source, ObjectStoreError::NotFound { .. })),
2283 LanceError::DatasetNotFound { source, .. } => {
2284 source
2285 .downcast_ref::<LanceError>()
2286 .is_some_and(|source| matches!(source, LanceError::NotFound { .. }))
2287 || source
2288 .downcast_ref::<ObjectStoreError>()
2289 .is_some_and(|source| matches!(source, ObjectStoreError::NotFound { .. }))
2290 }
2291 _ => false,
2292 }
2293 }
2294
2295 pub async fn list_manifest_table_locations(&self) -> Result<std::collections::HashSet<String>> {
2298 let filter = "object_type = 'table' AND NOT contains(object_id, '$')";
2299 let mut scanner = self.manifest_scanner().await?;
2300 scanner.filter(filter).map_err(|e| {
2301 lance_core::Error::from(NamespaceError::Internal {
2302 message: format!("Failed to filter: {:?}", e),
2303 })
2304 })?;
2305 scanner.project(&["location"]).map_err(|e| {
2306 lance_core::Error::from(NamespaceError::Internal {
2307 message: format!("Failed to project: {:?}", e),
2308 })
2309 })?;
2310
2311 let batches = Self::execute_scanner(scanner).await?;
2312 let mut locations = std::collections::HashSet::new();
2313
2314 for batch in batches {
2315 if batch.num_rows() == 0 {
2316 continue;
2317 }
2318 let location_array = Self::get_string_column(&batch, "location")?;
2319 for i in 0..location_array.len() {
2320 locations.insert(location_array.value(i).to_string());
2321 }
2322 }
2323
2324 Ok(locations)
2325 }
2326
2327 async fn insert_into_manifest(
2329 &self,
2330 object_id: String,
2331 object_type: ObjectType,
2332 location: Option<String>,
2333 ) -> Result<()> {
2334 self.insert_into_manifest_with_metadata(
2335 vec![ManifestEntry {
2336 object_id,
2337 object_type,
2338 location,
2339 metadata: None,
2340 }],
2341 None,
2342 )
2343 .await
2344 }
2345
2346 pub async fn insert_into_manifest_with_metadata(
2351 &self,
2352 entries: Vec<ManifestEntry>,
2353 base_objects: Option<Vec<String>>,
2354 ) -> Result<()> {
2355 self.merge_into_manifest_with_metadata(entries, base_objects, WhenMatched::Fail)
2356 .await
2357 }
2358
2359 async fn upsert_into_manifest_with_metadata(
2360 &self,
2361 entries: Vec<ManifestEntry>,
2362 base_objects: Option<Vec<String>>,
2363 ) -> Result<()> {
2364 self.merge_into_manifest_with_metadata(entries, base_objects, WhenMatched::UpdateAll)
2365 .await
2366 }
2367
2368 async fn merge_into_manifest_with_metadata(
2369 &self,
2370 entries: Vec<ManifestEntry>,
2371 base_objects: Option<Vec<String>>,
2372 when_matched: WhenMatched,
2373 ) -> Result<()> {
2374 if entries.is_empty() {
2375 return Ok(());
2376 }
2377
2378 self.rewrite_manifest("Failed to overwrite manifest", || {
2379 UpsertManifestMutation::new(entries.clone(), base_objects.clone(), when_matched.clone())
2380 })
2381 .await
2382 }
2383
2384 pub async fn delete_from_manifest(&self, object_id: &str) -> Result<()> {
2386 let object_id = object_id.to_string();
2387 self.rewrite_manifest("Failed to delete from manifest", || DeleteObjectMutation {
2388 object_id: object_id.clone(),
2389 deleted: false,
2390 })
2391 .await
2392 }
2393
2394 pub async fn register_table(&self, name: &str, location: String) -> Result<()> {
2396 let object_id = Self::build_object_id(&[], name);
2397 if self.manifest_contains_object(&object_id).await? {
2398 return Err(NamespaceError::Internal {
2399 message: format!("Table '{}' already exists", name),
2400 }
2401 .into());
2402 }
2403
2404 self.insert_into_manifest(object_id, ObjectType::Table, Some(location))
2405 .await
2406 }
2407
2408 async fn validate_namespace_levels_exist(&self, namespace_path: &[String]) -> Result<()> {
2410 for i in 1..=namespace_path.len() {
2411 let partial_path = &namespace_path[..i];
2412 let object_id = partial_path.join(DELIMITER);
2413 if !self.manifest_contains_object(&object_id).await? {
2414 return Err(NamespaceError::NamespaceNotFound {
2415 message: format!("parent namespace '{}'", object_id),
2416 }
2417 .into());
2418 }
2419 }
2420 Ok(())
2421 }
2422
2423 async fn query_manifest_for_namespace(&self, object_id: &str) -> Result<Option<NamespaceInfo>> {
2425 let escaped_id = object_id.replace('\'', "''");
2426 let filter = format!("object_id = '{}' AND object_type = 'namespace'", escaped_id);
2427 let mut scanner = self.manifest_scanner().await?;
2428 scanner.filter(&filter).map_err(|e| {
2429 lance_core::Error::from(NamespaceError::Internal {
2430 message: format!("Failed to filter: {:?}", e),
2431 })
2432 })?;
2433 scanner.project(&["object_id", "metadata"]).map_err(|e| {
2434 lance_core::Error::from(NamespaceError::Internal {
2435 message: format!("Failed to project: {:?}", e),
2436 })
2437 })?;
2438 let batches = Self::execute_scanner(scanner).await?;
2439
2440 let mut found_result: Option<NamespaceInfo> = None;
2441 let mut total_rows = 0;
2442
2443 for batch in batches {
2444 if batch.num_rows() == 0 {
2445 continue;
2446 }
2447
2448 total_rows += batch.num_rows();
2449 if total_rows > 1 {
2450 return Err(NamespaceError::Internal {
2451 message: format!(
2452 "Expected exactly 1 namespace with id '{}', found {}",
2453 object_id, total_rows
2454 ),
2455 }
2456 .into());
2457 }
2458
2459 let object_id_array = Self::get_string_column(&batch, "object_id")?;
2460 let metadata_array = Self::get_string_column(&batch, "metadata")?;
2461
2462 let object_id_str = object_id_array.value(0);
2463 let metadata = if !metadata_array.is_null(0) {
2464 let metadata_str = metadata_array.value(0);
2465 match serde_json::from_str::<HashMap<String, String>>(metadata_str) {
2466 Ok(map) => Some(map),
2467 Err(e) => {
2468 return Err(NamespaceError::Internal {
2469 message: format!(
2470 "Failed to deserialize metadata for namespace '{}': {}",
2471 object_id, e
2472 ),
2473 }
2474 .into());
2475 }
2476 }
2477 } else {
2478 None
2479 };
2480
2481 let (namespace, name) = Self::parse_object_id(object_id_str);
2482 found_result = Some(NamespaceInfo {
2483 namespace,
2484 name,
2485 metadata,
2486 });
2487 }
2488
2489 Ok(found_result)
2490 }
2491
2492 async fn open_manifest_table(
2494 root: &str,
2495 storage_options: &Option<HashMap<String, String>>,
2496 session: Option<Arc<Session>>,
2497 ) -> Result<DatasetConsistencyWrapper> {
2498 let manifest_path = format!("{}/{}", root, MANIFEST_TABLE_NAME);
2499 log::debug!("Attempting to load manifest from {}", manifest_path);
2500 let store_options = ObjectStoreParams {
2501 storage_options_accessor: storage_options.as_ref().map(|opts| {
2502 Arc::new(
2503 lance_io::object_store::StorageOptionsAccessor::with_static_options(
2504 opts.clone(),
2505 ),
2506 )
2507 }),
2508 ..Default::default()
2509 };
2510 let read_params = ReadParams {
2511 session,
2512 store_options: Some(store_options),
2513 ..Default::default()
2514 };
2515 let dataset = DatasetBuilder::from_uri(&manifest_path)
2516 .with_read_params(read_params)
2517 .load()
2518 .await?;
2519 ensure_readable(dataset.metadata())?;
2520 Ok(DatasetConsistencyWrapper::new(dataset))
2521 }
2522
2523 async fn ensure_manifest_table_up_to_date(
2530 root: &str,
2531 storage_options: &Option<HashMap<String, String>>,
2532 session: Option<Arc<Session>>,
2533 ) -> Result<DatasetConsistencyWrapper> {
2534 let manifest_path = format!("{}/{}", root, MANIFEST_TABLE_NAME);
2535 log::debug!("Attempting to load manifest from {}", manifest_path);
2536 let store_options = ObjectStoreParams {
2537 storage_options_accessor: storage_options.as_ref().map(|opts| {
2538 Arc::new(
2539 lance_io::object_store::StorageOptionsAccessor::with_static_options(
2540 opts.clone(),
2541 ),
2542 )
2543 }),
2544 ..Default::default()
2545 };
2546 let read_params = ReadParams {
2547 session: session.clone(),
2548 store_options: Some(store_options.clone()),
2549 ..Default::default()
2550 };
2551 let dataset_result = DatasetBuilder::from_uri(&manifest_path)
2552 .with_read_params(read_params)
2553 .load()
2554 .await;
2555 match dataset_result {
2556 Ok(mut dataset) => {
2557 ensure_readable(dataset.metadata())?;
2560
2561 let needs_pk_migration = dataset
2563 .schema()
2564 .field("object_id")
2565 .map(|f| {
2566 !f.metadata
2567 .contains_key(LANCE_UNENFORCED_PRIMARY_KEY_POSITION)
2568 })
2569 .unwrap_or(false);
2570
2571 if needs_pk_migration {
2572 ensure_writable(dataset.metadata())?;
2575 log::info!(
2576 "Migrating __manifest table to add primary key metadata on object_id"
2577 );
2578 dataset
2579 .update_field_metadata()
2580 .update("object_id", [(LANCE_UNENFORCED_PRIMARY_KEY_POSITION, "0")])
2581 .map_err(|e| {
2582 lance_core::Error::from(NamespaceError::Internal {
2583 message: format!(
2584 "Failed to find object_id field for migration: {:?}",
2585 e
2586 ),
2587 })
2588 })?
2589 .await
2590 .map_err(|e| {
2591 lance_core::Error::from(NamespaceError::Internal {
2592 message: format!("Failed to migrate primary key metadata: {:?}", e),
2593 })
2594 })?;
2595 }
2596
2597 Ok(DatasetConsistencyWrapper::new(dataset))
2598 }
2599 Err(err) if Self::is_not_found_load_error(&err) => {
2600 log::info!("Creating new manifest table at {}", manifest_path);
2601 let schema = Self::manifest_schema();
2602 let empty_batch = RecordBatch::new_empty(schema.clone());
2603 let reader = RecordBatchIterator::new(vec![Ok(empty_batch)], schema.clone());
2604
2605 let store_params = ObjectStoreParams {
2606 storage_options_accessor: storage_options.as_ref().map(|opts| {
2607 Arc::new(
2608 lance_io::object_store::StorageOptionsAccessor::with_static_options(
2609 opts.clone(),
2610 ),
2611 )
2612 }),
2613 ..Default::default()
2614 };
2615 let write_params = WriteParams {
2616 session: session.clone(),
2617 store_params: Some(store_params),
2618 ..Default::default()
2619 };
2620
2621 let dataset =
2622 Dataset::write(Box::new(reader), &manifest_path, Some(write_params)).await;
2623
2624 match dataset {
2626 Ok(dataset) => {
2627 log::info!(
2628 "Successfully created manifest table at {}, version={}, uri={}",
2629 manifest_path,
2630 dataset.version().version,
2631 dataset.uri()
2632 );
2633 Ok(DatasetConsistencyWrapper::new(dataset))
2634 }
2635 Err(ref e)
2636 if matches!(
2637 e,
2638 LanceError::DatasetAlreadyExists { .. }
2639 | LanceError::CommitConflict { .. }
2640 | LanceError::IncompatibleTransaction { .. }
2641 | LanceError::RetryableCommitConflict { .. }
2642 ) =>
2643 {
2644 log::info!(
2646 "Manifest table was created by another process, loading it: {}",
2647 manifest_path
2648 );
2649 let recovery_store_options = ObjectStoreParams {
2650 storage_options_accessor: storage_options.as_ref().map(|opts| {
2651 Arc::new(
2652 lance_io::object_store::StorageOptionsAccessor::with_static_options(
2653 opts.clone(),
2654 ),
2655 )
2656 }),
2657 ..Default::default()
2658 };
2659 let recovery_read_params = ReadParams {
2660 session,
2661 store_options: Some(recovery_store_options),
2662 ..Default::default()
2663 };
2664 let dataset = DatasetBuilder::from_uri(&manifest_path)
2665 .with_read_params(recovery_read_params)
2666 .load()
2667 .await
2668 .map_err(|e| {
2669 lance_core::Error::from(NamespaceError::Internal {
2670 message: format!(
2671 "Failed to load manifest dataset after creation conflict: {}",
2672 e
2673 ),
2674 })
2675 })?;
2676 Ok(DatasetConsistencyWrapper::new(dataset))
2677 }
2678 Err(e) => Err(lance_core::Error::from(NamespaceError::Internal {
2679 message: format!("Failed to create manifest dataset: {:?}", e),
2680 })),
2681 }
2682 }
2683 Err(err) => Err(err),
2684 }
2685 }
2686
2687 fn apply_pagination(
2692 names: &mut Vec<String>,
2693 page_token: Option<String>,
2694 limit: Option<i32>,
2695 ) -> Option<String> {
2696 names.sort();
2697
2698 if let Some(start_after) = page_token {
2699 if let Some(index) = names
2700 .iter()
2701 .position(|name| name.as_str() > start_after.as_str())
2702 {
2703 names.drain(0..index);
2704 } else {
2705 names.clear();
2706 }
2707 }
2708
2709 if let Some(limit) = limit
2710 && limit >= 0
2711 {
2712 let limit = limit as usize;
2713 if names.len() > limit {
2714 let next_page_token = if limit > 0 {
2715 Some(names[limit - 1].clone())
2716 } else {
2717 None
2718 };
2719 names.truncate(limit);
2720 return next_page_token;
2721 }
2722 }
2723
2724 None
2725 }
2726}
2727
2728#[async_trait]
2729impl LanceNamespace for ManifestNamespace {
2730 fn namespace_id(&self) -> String {
2731 self.root.clone()
2732 }
2733
2734 async fn list_tables(&self, request: ListTablesRequest) -> Result<ListTablesResponse> {
2735 let namespace_id = request.id.as_ref().ok_or_else(|| {
2736 lance_core::Error::from(NamespaceError::InvalidInput {
2737 message: "Namespace ID is required".to_string(),
2738 })
2739 })?;
2740
2741 let filter = if namespace_id.is_empty() {
2743 "object_type = 'table' AND NOT contains(object_id, '$')".to_string()
2745 } else {
2746 let prefix = namespace_id.join(DELIMITER);
2748 format!(
2749 "object_type = 'table' AND starts_with(object_id, '{}{}') AND NOT contains(substring(object_id, {}), '$')",
2750 prefix,
2751 DELIMITER,
2752 prefix.len() + 2
2753 )
2754 };
2755
2756 let mut scanner = self.manifest_scanner().await?;
2757 scanner.filter(&filter).map_err(|e| {
2758 lance_core::Error::from(NamespaceError::Internal {
2759 message: format!("Failed to filter: {:?}", e),
2760 })
2761 })?;
2762 scanner.project(&["object_id", "location"]).map_err(|e| {
2763 lance_core::Error::from(NamespaceError::Internal {
2764 message: format!("Failed to project: {:?}", e),
2765 })
2766 })?;
2767
2768 let batches = Self::execute_scanner(scanner).await?;
2769
2770 let mut table_entries = Vec::new();
2771 for batch in batches {
2772 if batch.num_rows() == 0 {
2773 continue;
2774 }
2775
2776 let object_id_array = Self::get_string_column(&batch, "object_id")?;
2777 let location_array = Self::get_string_column(&batch, "location")?;
2778 for i in 0..batch.num_rows() {
2779 let object_id = object_id_array.value(i);
2780 let location = location_array.value(i);
2781 let (_namespace, name) = Self::parse_object_id(object_id);
2782 table_entries.push((name, location.to_string()));
2783 }
2784 }
2785
2786 let mut tables: Vec<String> = if request.include_declared.unwrap_or(true) {
2787 table_entries.into_iter().map(|(name, _)| name).collect()
2788 } else {
2789 let mut stream = futures::stream::iter(table_entries.into_iter().map(
2790 |(name, location)| async move {
2791 if self.location_has_actual_manifests(&location).await? {
2796 Ok::<Option<String>, Error>(Some(name))
2797 } else {
2798 Ok::<Option<String>, Error>(None)
2799 }
2800 },
2801 ))
2802 .buffered(DECLARED_FILTER_CONCURRENCY);
2803
2804 let mut filtered = Vec::new();
2805 while let Some(result) = stream.next().await {
2806 if let Some(name) = result? {
2807 filtered.push(name);
2808 }
2809 }
2810 filtered
2811 };
2812
2813 let next_page_token =
2814 Self::apply_pagination(&mut tables, request.page_token, request.limit);
2815 let mut response = ListTablesResponse::new(tables);
2816 response.page_token = next_page_token;
2817 Ok(response)
2818 }
2819
2820 async fn describe_table(&self, request: DescribeTableRequest) -> Result<DescribeTableResponse> {
2821 let table_id = request.id.as_ref().ok_or_else(|| {
2822 lance_core::Error::from(NamespaceError::InvalidInput {
2823 message: "Table ID is required".to_string(),
2824 })
2825 })?;
2826
2827 if table_id.is_empty() {
2828 return Err(NamespaceError::InvalidInput {
2829 message: "Table ID cannot be empty".to_string(),
2830 }
2831 .into());
2832 }
2833
2834 let object_id = Self::str_object_id(table_id);
2835 let table_info = self.query_manifest_for_table(&object_id).boxed().await?;
2836
2837 let table_name = table_id.last().cloned().unwrap_or_default();
2839 let namespace_id: Vec<String> = if table_id.len() > 1 {
2840 table_id[..table_id.len() - 1].to_vec()
2841 } else {
2842 vec![]
2843 };
2844
2845 let load_detailed_metadata = request.load_detailed_metadata.unwrap_or(false);
2846 let should_check_declared =
2847 load_detailed_metadata || request.check_declared.unwrap_or(false);
2848 let vend_credentials = request.vend_credentials.unwrap_or(true);
2850
2851 match table_info {
2852 Some(info) => {
2853 let table_uri = Self::construct_full_uri(&self.root, &info.location)?;
2855
2856 let storage_options = if vend_credentials {
2857 self.storage_options.clone()
2858 } else {
2859 None
2860 };
2861 let is_only_declared = if should_check_declared {
2862 Some(!self.location_has_actual_manifests(&info.location).await?)
2863 } else {
2864 None
2865 };
2866
2867 if !load_detailed_metadata {
2868 return Ok(DescribeTableResponse {
2869 table: Some(table_name),
2870 namespace: Some(namespace_id),
2871 location: Some(table_uri.clone()),
2872 table_uri: Some(table_uri),
2873 storage_options,
2874 properties: info.metadata,
2875 is_only_declared,
2876 ..Default::default()
2877 });
2878 }
2879
2880 if is_only_declared == Some(true) {
2881 return Ok(DescribeTableResponse {
2882 table: Some(table_name),
2883 namespace: Some(namespace_id),
2884 location: Some(table_uri.clone()),
2885 table_uri: Some(table_uri),
2886 storage_options,
2887 properties: info.metadata,
2888 is_only_declared,
2889 ..Default::default()
2890 });
2891 }
2892
2893 let mut builder = DatasetBuilder::from_uri(&table_uri);
2894 if let Some(opts) = &self.storage_options {
2895 builder = builder.with_storage_options(opts.clone());
2896 }
2897 if let Some(session) = &self.session {
2898 builder = builder.with_session(session.clone());
2899 }
2900
2901 match builder.load().await {
2902 Ok(mut dataset) => {
2903 if let Some(requested_version) = request.version {
2905 dataset = dataset.checkout_version(requested_version as u64).await?;
2906 }
2907
2908 let version = dataset.version().version;
2909 let lance_schema = dataset.schema();
2910 let arrow_schema: arrow_schema::Schema = lance_schema.into();
2911 let json_schema = arrow_schema_to_json(&arrow_schema)?;
2912
2913 Ok(DescribeTableResponse {
2914 table: Some(table_name.clone()),
2915 namespace: Some(namespace_id.clone()),
2916 version: Some(version as i64),
2917 location: Some(table_uri.clone()),
2918 table_uri: Some(table_uri),
2919 schema: Some(Box::new(json_schema)),
2920 storage_options,
2921 properties: info.metadata.clone(),
2922 is_only_declared,
2923 ..Default::default()
2924 })
2925 }
2926 Err(err) => Err(NamespaceError::Internal {
2927 message: format!(
2928 "Table exists in manifest but failed to load dataset '{}': {}",
2929 object_id, err
2930 ),
2931 }
2932 .into()),
2933 }
2934 }
2935 None => Err(NamespaceError::TableNotFound {
2936 message: Self::format_table_id(table_id),
2937 }
2938 .into()),
2939 }
2940 }
2941
2942 async fn table_exists(&self, request: TableExistsRequest) -> Result<()> {
2943 let table_id = request.id.as_ref().ok_or_else(|| {
2944 lance_core::Error::from(NamespaceError::InvalidInput {
2945 message: "Table ID is required".to_string(),
2946 })
2947 })?;
2948
2949 if table_id.is_empty() {
2950 return Err(NamespaceError::InvalidInput {
2951 message: "Table ID cannot be empty".to_string(),
2952 }
2953 .into());
2954 }
2955
2956 let object_id = Self::str_object_id(table_id);
2957 let exists = self.manifest_contains_object(&object_id).await?;
2958 if exists {
2959 Ok(())
2960 } else {
2961 Err(NamespaceError::TableNotFound {
2962 message: Self::format_table_id(table_id),
2963 }
2964 .into())
2965 }
2966 }
2967
2968 async fn create_table(
2969 &self,
2970 request: CreateTableRequest,
2971 data: Bytes,
2972 ) -> Result<CreateTableResponse> {
2973 let table_id = request.id.as_ref().ok_or_else(|| {
2974 lance_core::Error::from(NamespaceError::InvalidInput {
2975 message: "Table ID is required".to_string(),
2976 })
2977 })?;
2978
2979 if table_id.is_empty() {
2980 return Err(NamespaceError::InvalidInput {
2981 message: "Table ID cannot be empty".to_string(),
2982 }
2983 .into());
2984 }
2985
2986 let (namespace, table_name) = Self::split_object_id(table_id);
2987 let object_id = Self::build_object_id(&namespace, &table_name);
2988
2989 self.ensure_manifest_writable().await?;
2992
2993 let existing_table = self.query_manifest_for_table(&object_id).await?;
2994 let existing_has_manifests = if let Some(existing_table) = &existing_table {
2995 Some(
2996 self.location_has_actual_manifests(&existing_table.location)
2997 .await?,
2998 )
2999 } else {
3000 None
3001 };
3002
3003 if existing_has_manifests == Some(false)
3004 && request
3005 .properties
3006 .as_ref()
3007 .is_some_and(|properties| !properties.is_empty())
3008 {
3009 return Err(NamespaceError::InvalidInput {
3010 message: format!(
3011 "create_table cannot set properties for already declared table '{}'",
3012 object_id
3013 ),
3014 }
3015 .into());
3016 }
3017
3018 let create_mode = if existing_has_manifests == Some(false) {
3019 CreateTableMode::Create
3020 } else {
3021 CreateTableMode::parse(request.mode.as_deref())?
3022 };
3023 let dir_name = if let Some(existing_table) = &existing_table {
3024 existing_table.location.clone()
3025 } else if namespace.is_empty() && self.dir_listing_enabled {
3026 format!("{}.lance", table_name)
3027 } else {
3028 Self::generate_dir_name(&object_id)
3029 };
3030 let table_uri = Self::construct_full_uri(&self.root, &dir_name)?;
3031 let overwriting_existing_table =
3032 existing_has_manifests == Some(true) && create_mode == CreateTableMode::Overwrite;
3033
3034 if existing_has_manifests == Some(true) {
3035 match create_mode {
3036 CreateTableMode::Create => {
3037 return Err(NamespaceError::TableAlreadyExists {
3038 message: table_name.clone(),
3039 }
3040 .into());
3041 }
3042 CreateTableMode::ExistOk => {
3043 let properties = existing_table
3044 .as_ref()
3045 .and_then(|table| table.metadata.clone());
3046 return Ok(CreateTableResponse {
3047 location: Some(table_uri),
3048 storage_options: self.storage_options.clone(),
3049 properties,
3050 ..Default::default()
3051 });
3052 }
3053 CreateTableMode::Overwrite => {}
3054 }
3055 }
3056
3057 if data.is_empty() {
3059 return Err(NamespaceError::InvalidInput {
3060 message: "Request data (Arrow IPC stream) is required for create_table".to_string(),
3061 }
3062 .into());
3063 }
3064
3065 let cursor = Cursor::new(data.to_vec());
3067 let stream_reader = StreamReader::try_new(cursor, None).map_err(|e| {
3068 lance_core::Error::from(NamespaceError::Internal {
3069 message: format!("Failed to read IPC stream: {:?}", e),
3070 })
3071 })?;
3072
3073 let batches: Vec<RecordBatch> = stream_reader
3074 .collect::<std::result::Result<Vec<_>, _>>()
3075 .map_err(|e| {
3076 lance_core::Error::from(NamespaceError::Internal {
3077 message: format!("Failed to collect batches: {:?}", e),
3078 })
3079 })?;
3080
3081 if batches.is_empty() {
3082 return Err(NamespaceError::Internal {
3083 message: "No data provided for table creation".to_string(),
3084 }
3085 .into());
3086 }
3087
3088 let schema = batches[0].schema();
3089 let batch_results: Vec<std::result::Result<RecordBatch, arrow_schema::ArrowError>> =
3090 batches.into_iter().map(Ok).collect();
3091 let reader = RecordBatchIterator::new(batch_results, schema);
3092
3093 let mut write_storage_options = self.storage_options.clone().unwrap_or_default();
3094 if let Some(request_storage_options) = request.storage_options.as_ref() {
3095 write_storage_options.extend(request_storage_options.clone());
3096 }
3097
3098 let store_params = ObjectStoreParams {
3099 storage_options_accessor: (!write_storage_options.is_empty()).then(|| {
3100 Arc::new(
3101 lance_io::object_store::StorageOptionsAccessor::with_static_options(
3102 write_storage_options,
3103 ),
3104 )
3105 }),
3106 ..Default::default()
3107 };
3108 let write_params = WriteParams {
3109 mode: create_mode.write_mode(),
3110 session: self.session.clone(),
3111 store_params: Some(store_params),
3112 ..Default::default()
3113 };
3114 let dataset = Dataset::write(Box::new(reader), &table_uri, Some(write_params))
3115 .await
3116 .map_err(|e| {
3117 lance_core::Error::from(NamespaceError::Internal {
3118 message: format!("Failed to write dataset: {:?}", e),
3119 })
3120 })?;
3121 let version = dataset.version().version as i64;
3122
3123 if overwriting_existing_table {
3124 let metadata =
3125 Self::serialize_metadata(request.properties.as_ref(), "table", &object_id)?;
3126 self.upsert_into_manifest_with_metadata(
3127 vec![ManifestEntry {
3128 object_id,
3129 object_type: ObjectType::Table,
3130 location: Some(dir_name),
3131 metadata,
3132 }],
3133 None,
3134 )
3135 .await?;
3136
3137 Ok(CreateTableResponse {
3138 version: Some(version),
3139 location: Some(table_uri),
3140 storage_options: self.storage_options.clone(),
3141 properties: request.properties,
3142 ..Default::default()
3143 })
3144 } else {
3145 match existing_table {
3146 Some(existing_table) => Ok(CreateTableResponse {
3147 version: Some(version),
3148 location: Some(table_uri),
3149 storage_options: self.storage_options.clone(),
3150 properties: existing_table.metadata,
3151 ..Default::default()
3152 }),
3153 None => {
3154 let metadata =
3155 Self::serialize_metadata(request.properties.as_ref(), "table", &object_id)?;
3156 self.insert_into_manifest_with_metadata(
3158 vec![ManifestEntry {
3159 object_id,
3160 object_type: ObjectType::Table,
3161 location: Some(dir_name.clone()),
3162 metadata,
3163 }],
3164 None,
3165 )
3166 .await?;
3167
3168 Ok(CreateTableResponse {
3169 version: Some(version),
3170 location: Some(table_uri),
3171 storage_options: self.storage_options.clone(),
3172 properties: request.properties,
3173 ..Default::default()
3174 })
3175 }
3176 }
3177 }
3178 }
3179
3180 async fn drop_table(&self, request: DropTableRequest) -> Result<DropTableResponse> {
3181 let table_id = request.id.as_ref().ok_or_else(|| {
3182 lance_core::Error::from(NamespaceError::InvalidInput {
3183 message: "Table ID is required".to_string(),
3184 })
3185 })?;
3186
3187 if table_id.is_empty() {
3188 return Err(NamespaceError::InvalidInput {
3189 message: "Table ID cannot be empty".to_string(),
3190 }
3191 .into());
3192 }
3193
3194 let (namespace, table_name) = Self::split_object_id(table_id);
3195 let object_id = Self::build_object_id(&namespace, &table_name);
3196
3197 let table_info = self.query_manifest_for_table(&object_id).boxed().await?;
3199
3200 match table_info {
3201 Some(info) => {
3202 self.delete_from_manifest(&object_id).boxed().await?;
3204
3205 let table_path = self.base_path.clone().join(info.location.as_str());
3207 let table_uri = Self::construct_full_uri(&self.root, &info.location)?;
3208
3209 self.object_store
3211 .remove_dir_all(table_path)
3212 .boxed()
3213 .await
3214 .map_err(|e| {
3215 lance_core::Error::from(NamespaceError::Internal {
3216 message: format!("Failed to delete table directory: {:?}", e),
3217 })
3218 })?;
3219
3220 Ok(DropTableResponse {
3221 id: request.id.clone(),
3222 location: Some(table_uri),
3223 ..Default::default()
3224 })
3225 }
3226 None => Err(NamespaceError::TableNotFound {
3227 message: table_name.to_string(),
3228 }
3229 .into()),
3230 }
3231 }
3232
3233 async fn list_namespaces(
3234 &self,
3235 request: ListNamespacesRequest,
3236 ) -> Result<ListNamespacesResponse> {
3237 let parent_namespace = request.id.as_ref().ok_or_else(|| {
3238 lance_core::Error::from(NamespaceError::InvalidInput {
3239 message: "Namespace ID is required".to_string(),
3240 })
3241 })?;
3242
3243 let filter = if parent_namespace.is_empty() {
3245 "object_type = 'namespace' AND NOT contains(object_id, '$')".to_string()
3247 } else {
3248 let prefix = parent_namespace.join(DELIMITER);
3250 format!(
3251 "object_type = 'namespace' AND starts_with(object_id, '{}{}') AND NOT contains(substring(object_id, {}), '$')",
3252 prefix,
3253 DELIMITER,
3254 prefix.len() + 2
3255 )
3256 };
3257
3258 let mut scanner = self.manifest_scanner().await?;
3259 scanner.filter(&filter).map_err(|e| {
3260 lance_core::Error::from(NamespaceError::Internal {
3261 message: format!("Failed to filter: {:?}", e),
3262 })
3263 })?;
3264 scanner.project(&["object_id"]).map_err(|e| {
3265 lance_core::Error::from(NamespaceError::Internal {
3266 message: format!("Failed to project: {:?}", e),
3267 })
3268 })?;
3269
3270 let batches = Self::execute_scanner(scanner).await?;
3271 let mut namespaces = Vec::new();
3272
3273 for batch in batches {
3274 if batch.num_rows() == 0 {
3275 continue;
3276 }
3277
3278 let object_id_array = Self::get_string_column(&batch, "object_id")?;
3279 for i in 0..batch.num_rows() {
3280 let object_id = object_id_array.value(i);
3281 let (_namespace, name) = Self::parse_object_id(object_id);
3282 namespaces.push(name);
3283 }
3284 }
3285
3286 let next_page_token =
3287 Self::apply_pagination(&mut namespaces, request.page_token, request.limit);
3288 let mut response = ListNamespacesResponse::new(namespaces);
3289 response.page_token = next_page_token;
3290 Ok(response)
3291 }
3292
3293 async fn describe_namespace(
3294 &self,
3295 request: DescribeNamespaceRequest,
3296 ) -> Result<DescribeNamespaceResponse> {
3297 let namespace_id = request.id.as_ref().ok_or_else(|| {
3298 lance_core::Error::from(NamespaceError::InvalidInput {
3299 message: "Namespace ID is required".to_string(),
3300 })
3301 })?;
3302
3303 if namespace_id.is_empty() {
3305 #[allow(clippy::needless_update)]
3306 return Ok(DescribeNamespaceResponse {
3307 properties: Some(HashMap::new()),
3308 ..Default::default()
3309 });
3310 }
3311
3312 let object_id = namespace_id.join(DELIMITER);
3314 let namespace_info = self.query_manifest_for_namespace(&object_id).await?;
3315
3316 match namespace_info {
3317 #[allow(clippy::needless_update)]
3318 Some(info) => Ok(DescribeNamespaceResponse {
3319 properties: info.metadata,
3320 ..Default::default()
3321 }),
3322 None => Err(NamespaceError::NamespaceNotFound {
3323 message: object_id.to_string(),
3324 }
3325 .into()),
3326 }
3327 }
3328
3329 async fn create_namespace(
3330 &self,
3331 request: CreateNamespaceRequest,
3332 ) -> Result<CreateNamespaceResponse> {
3333 let namespace_id = request.id.as_ref().ok_or_else(|| {
3334 lance_core::Error::from(NamespaceError::InvalidInput {
3335 message: "Namespace ID is required".to_string(),
3336 })
3337 })?;
3338
3339 if namespace_id.is_empty() {
3341 return Err(NamespaceError::NamespaceAlreadyExists {
3342 message: "root namespace".to_string(),
3343 }
3344 .into());
3345 }
3346
3347 if namespace_id.len() > 1 {
3349 self.validate_namespace_levels_exist(&namespace_id[..namespace_id.len() - 1])
3350 .await?;
3351 }
3352
3353 let object_id = namespace_id.join(DELIMITER);
3354 if self.manifest_contains_object(&object_id).await? {
3355 return Err(NamespaceError::NamespaceAlreadyExists {
3356 message: object_id.to_string(),
3357 }
3358 .into());
3359 }
3360
3361 let metadata =
3362 Self::serialize_metadata(request.properties.as_ref(), "namespace", &object_id)?;
3363
3364 self.insert_into_manifest_with_metadata(
3365 vec![ManifestEntry {
3366 object_id,
3367 object_type: ObjectType::Namespace,
3368 location: None,
3369 metadata,
3370 }],
3371 None,
3372 )
3373 .await?;
3374
3375 Ok(CreateNamespaceResponse {
3376 properties: request.properties,
3377 ..Default::default()
3378 })
3379 }
3380
3381 async fn drop_namespace(&self, request: DropNamespaceRequest) -> Result<DropNamespaceResponse> {
3382 let namespace_id = request.id.as_ref().ok_or_else(|| {
3383 lance_core::Error::from(NamespaceError::InvalidInput {
3384 message: "Namespace ID is required".to_string(),
3385 })
3386 })?;
3387
3388 if namespace_id.is_empty() {
3390 return Err(NamespaceError::InvalidInput {
3391 message: "Root namespace cannot be dropped".to_string(),
3392 }
3393 .into());
3394 }
3395
3396 let object_id = namespace_id.join(DELIMITER);
3397
3398 if !self.manifest_contains_object(&object_id).boxed().await? {
3400 return Err(NamespaceError::NamespaceNotFound {
3401 message: object_id.to_string(),
3402 }
3403 .into());
3404 }
3405
3406 let escaped_id = object_id.replace('\'', "''");
3408 let prefix = format!("{}{}", escaped_id, DELIMITER);
3409 let filter = format!("starts_with(object_id, '{}')", prefix);
3410 let mut scanner = self.manifest_scanner().boxed().await?;
3411 scanner.filter(&filter).map_err(|e| {
3412 lance_core::Error::from(NamespaceError::Internal {
3413 message: format!("Failed to filter: {:?}", e),
3414 })
3415 })?;
3416 scanner.project::<&str>(&[]).map_err(|e| {
3417 lance_core::Error::from(NamespaceError::Internal {
3418 message: format!("Failed to project: {:?}", e),
3419 })
3420 })?;
3421 scanner.with_row_id();
3422 let count = scanner.count_rows().boxed().await.map_err(|e| {
3423 lance_core::Error::from(NamespaceError::Internal {
3424 message: format!("Failed to count rows: {:?}", e),
3425 })
3426 })?;
3427
3428 if count > 0 {
3429 return Err(NamespaceError::NamespaceNotEmpty {
3430 message: format!("'{}' (contains {} child objects)", object_id, count),
3431 }
3432 .into());
3433 }
3434
3435 self.delete_from_manifest(&object_id).boxed().await?;
3436
3437 Ok(DropNamespaceResponse::default())
3438 }
3439
3440 async fn namespace_exists(&self, request: NamespaceExistsRequest) -> Result<()> {
3441 let namespace_id = request.id.as_ref().ok_or_else(|| {
3442 lance_core::Error::from(NamespaceError::InvalidInput {
3443 message: "Namespace ID is required".to_string(),
3444 })
3445 })?;
3446
3447 if namespace_id.is_empty() {
3449 return Ok(());
3450 }
3451
3452 let object_id = namespace_id.join(DELIMITER);
3453 if self.manifest_contains_object(&object_id).await? {
3454 Ok(())
3455 } else {
3456 Err(NamespaceError::NamespaceNotFound {
3457 message: object_id.to_string(),
3458 }
3459 .into())
3460 }
3461 }
3462
3463 async fn declare_table(&self, request: DeclareTableRequest) -> Result<DeclareTableResponse> {
3464 let table_id = request.id.as_ref().ok_or_else(|| {
3465 lance_core::Error::from(NamespaceError::InvalidInput {
3466 message: "Table ID is required".to_string(),
3467 })
3468 })?;
3469
3470 if table_id.is_empty() {
3471 return Err(NamespaceError::InvalidInput {
3472 message: "Table ID cannot be empty".to_string(),
3473 }
3474 .into());
3475 }
3476
3477 let (namespace, table_name) = Self::split_object_id(table_id);
3478 let object_id = Self::build_object_id(&namespace, &table_name);
3479
3480 let existing = self.query_manifest_for_table(&object_id).await?;
3482 if existing.is_some() {
3483 return Err(NamespaceError::TableAlreadyExists {
3484 message: table_name.to_string(),
3485 }
3486 .into());
3487 }
3488
3489 let dir_name = if namespace.is_empty() && self.dir_listing_enabled {
3493 format!("{}.lance", table_name)
3495 } else {
3496 Self::generate_dir_name(&object_id)
3498 };
3499 let table_path = self.base_path.clone().join(dir_name.as_str());
3500 let table_uri = Self::construct_full_uri(&self.root, &dir_name)?;
3501
3502 if let Some(req_location) = &request.location {
3504 let req_location = req_location.trim_end_matches('/');
3505 if req_location != table_uri {
3506 return Err(NamespaceError::InvalidInput {
3507 message: format!(
3508 "Cannot declare table {} at location {}, must be at location {}",
3509 table_name, req_location, table_uri
3510 ),
3511 }
3512 .into());
3513 }
3514 }
3515
3516 self.ensure_manifest_writable().await?;
3517
3518 let reserved_file_path = table_path.clone().join(".lance-reserved");
3522 super::put_marker_file_atomic(
3523 &self.object_store,
3524 &reserved_file_path,
3525 &format!("table {}", table_name),
3526 )
3527 .await
3528 .map_err(|e| match e {
3529 super::MarkerFileError::AlreadyExists { .. } => {
3530 lance_core::Error::from(NamespaceError::TableAlreadyExists {
3531 message: table_name.to_string(),
3532 })
3533 }
3534 super::MarkerFileError::Other { message } => {
3535 lance_core::Error::from(NamespaceError::Internal { message })
3536 }
3537 })?;
3538
3539 let metadata = Self::serialize_metadata(request.properties.as_ref(), "table", &object_id)?;
3540
3541 self.insert_into_manifest_with_metadata(
3543 vec![ManifestEntry {
3544 object_id,
3545 object_type: ObjectType::Table,
3546 location: Some(dir_name),
3547 metadata,
3548 }],
3549 None,
3550 )
3551 .await?;
3552
3553 log::info!(
3554 "Declared table '{}' in manifest at {}",
3555 table_name,
3556 table_uri
3557 );
3558
3559 let vend_credentials = request.vend_credentials.unwrap_or(true);
3561 let storage_options = if vend_credentials {
3562 self.storage_options.clone()
3563 } else {
3564 None
3565 };
3566
3567 Ok(DeclareTableResponse {
3568 location: Some(table_uri),
3569 storage_options,
3570 properties: request.properties,
3571 ..Default::default()
3572 })
3573 }
3574
3575 async fn register_table(&self, request: RegisterTableRequest) -> Result<RegisterTableResponse> {
3576 let table_id = request.id.as_ref().ok_or_else(|| {
3577 lance_core::Error::from(NamespaceError::InvalidInput {
3578 message: "Table ID is required".to_string(),
3579 })
3580 })?;
3581
3582 if table_id.is_empty() {
3583 return Err(NamespaceError::InvalidInput {
3584 message: "Table ID cannot be empty".to_string(),
3585 }
3586 .into());
3587 }
3588
3589 let location = request.location.clone();
3590
3591 if location.contains("://") {
3594 return Err(NamespaceError::InvalidInput {
3595 message: format!(
3596 "Absolute URIs are not allowed for register_table. Location must be a relative path within the root directory: {}",
3597 location
3598 ),
3599 }
3600 .into());
3601 }
3602
3603 if location.starts_with('/') {
3604 return Err(NamespaceError::InvalidInput {
3605 message: format!(
3606 "Absolute paths are not allowed for register_table. Location must be a relative path within the root directory: {}",
3607 location
3608 ),
3609 }
3610 .into());
3611 }
3612
3613 if location.contains("..") {
3615 return Err(NamespaceError::InvalidInput {
3616 message: format!(
3617 "Path traversal is not allowed. Location must be a relative path within the root directory: {}",
3618 location
3619 ),
3620 }
3621 .into());
3622 }
3623
3624 let (namespace, table_name) = Self::split_object_id(table_id);
3625 let object_id = Self::build_object_id(&namespace, &table_name);
3626
3627 if !namespace.is_empty() {
3629 self.validate_namespace_levels_exist(&namespace).await?;
3630 }
3631
3632 if self.manifest_contains_object(&object_id).await? {
3634 return Err(NamespaceError::TableAlreadyExists {
3635 message: object_id.to_string(),
3636 }
3637 .into());
3638 }
3639
3640 self.insert_into_manifest(object_id, ObjectType::Table, Some(location.clone()))
3642 .await?;
3643
3644 Ok(RegisterTableResponse {
3645 location: Some(location),
3646 ..Default::default()
3647 })
3648 }
3649
3650 async fn deregister_table(
3651 &self,
3652 request: DeregisterTableRequest,
3653 ) -> Result<DeregisterTableResponse> {
3654 let table_id = request.id.as_ref().ok_or_else(|| {
3655 lance_core::Error::from(NamespaceError::InvalidInput {
3656 message: "Table ID is required".to_string(),
3657 })
3658 })?;
3659
3660 if table_id.is_empty() {
3661 return Err(NamespaceError::InvalidInput {
3662 message: "Table ID cannot be empty".to_string(),
3663 }
3664 .into());
3665 }
3666
3667 let (namespace, table_name) = Self::split_object_id(table_id);
3668 let object_id = Self::build_object_id(&namespace, &table_name);
3669
3670 let table_info = self.query_manifest_for_table(&object_id).await?;
3672
3673 let table_uri = match table_info {
3674 Some(info) => {
3675 self.delete_from_manifest(&object_id).boxed().await?;
3677 Self::construct_full_uri(&self.root, &info.location)?
3678 }
3679 None => {
3680 return Err(NamespaceError::TableNotFound {
3681 message: object_id.to_string(),
3682 }
3683 .into());
3684 }
3685 };
3686
3687 Ok(DeregisterTableResponse {
3688 id: request.id.clone(),
3689 location: Some(table_uri),
3690 ..Default::default()
3691 })
3692 }
3693
3694 async fn alter_table_add_columns(
3699 &self,
3700 request: AlterTableAddColumnsRequest,
3701 ) -> Result<AlterTableAddColumnsResponse> {
3702 let table_id = request
3703 .id
3704 .as_ref()
3705 .ok_or_else(|| Error::invalid_input_source("Table ID is required".into()))?;
3706
3707 if table_id.is_empty() {
3708 return Err(Error::invalid_input_source(
3709 "Table ID cannot be empty".into(),
3710 ));
3711 }
3712
3713 let object_id = Self::str_object_id(table_id);
3714 let table_info = self.query_manifest_for_table(&object_id).boxed().await?;
3715
3716 match table_info {
3717 Some(info) => {
3718 let table_uri = Self::construct_full_uri(&self.root, &info.location)?;
3719 let mut builder = DatasetBuilder::from_uri(&table_uri);
3722 if let Some(opts) = &self.storage_options {
3723 builder = builder.with_storage_options(opts.clone());
3724 }
3725 if let Some(session) = &self.session {
3726 builder = builder.with_session(session.clone());
3727 }
3728 let mut dataset = builder.load().await.map_err(|e| {
3729 Error::io_source(box_error(std::io::Error::other(format!(
3730 "Failed to open dataset: {}",
3731 e
3732 ))))
3733 })?;
3734
3735 let sql_expressions = super::build_sql_expressions(&request.new_columns)?;
3737
3738 dataset
3739 .add_columns(
3740 lance::dataset::NewColumnTransform::SqlExpressions(sql_expressions),
3741 None,
3742 None,
3743 )
3744 .await
3745 .map_err(|e| {
3746 convert_lance_commit_error(&e, "add_columns", Some(&object_id))
3750 })?;
3751
3752 let version = dataset.version().version as i64;
3753 Ok(AlterTableAddColumnsResponse::new(version))
3754 }
3755 None => Err(NamespaceError::TableNotFound { message: object_id }.into()),
3756 }
3757 }
3758
3759 async fn alter_table_alter_columns(
3764 &self,
3765 request: AlterTableAlterColumnsRequest,
3766 ) -> Result<AlterTableAlterColumnsResponse> {
3767 let table_id = request
3768 .id
3769 .as_ref()
3770 .ok_or_else(|| Error::invalid_input_source("Table ID is required".into()))?;
3771
3772 if table_id.is_empty() {
3773 return Err(Error::invalid_input_source(
3774 "Table ID cannot be empty".into(),
3775 ));
3776 }
3777
3778 let object_id = Self::str_object_id(table_id);
3779 let table_info = self.query_manifest_for_table(&object_id).boxed().await?;
3780
3781 match table_info {
3782 Some(info) => {
3783 let table_uri = Self::construct_full_uri(&self.root, &info.location)?;
3784 let mut builder = DatasetBuilder::from_uri(&table_uri);
3785 if let Some(opts) = &self.storage_options {
3786 builder = builder.with_storage_options(opts.clone());
3787 }
3788 if let Some(session) = &self.session {
3789 builder = builder.with_session(session.clone());
3790 }
3791 let mut dataset = builder.load().await.map_err(|e| {
3792 Error::io_source(box_error(std::io::Error::other(format!(
3793 "Failed to open dataset: {}",
3794 e
3795 ))))
3796 })?;
3797
3798 let alterations = super::build_column_alterations(&request.alterations)?;
3800
3801 dataset.alter_columns(&alterations).await.map_err(|e| {
3802 convert_lance_commit_error(&e, "alter_columns", Some(&object_id))
3803 })?;
3804
3805 let version = dataset.version().version as i64;
3806 Ok(AlterTableAlterColumnsResponse::new(version))
3807 }
3808 None => Err(NamespaceError::TableNotFound { message: object_id }.into()),
3809 }
3810 }
3811
3812 async fn alter_table_drop_columns(
3816 &self,
3817 request: AlterTableDropColumnsRequest,
3818 ) -> Result<AlterTableDropColumnsResponse> {
3819 let table_id = request
3820 .id
3821 .as_ref()
3822 .ok_or_else(|| Error::invalid_input_source("Table ID is required".into()))?;
3823
3824 if table_id.is_empty() {
3825 return Err(Error::invalid_input_source(
3826 "Table ID cannot be empty".into(),
3827 ));
3828 }
3829
3830 let object_id = Self::str_object_id(table_id);
3831 let table_info = self.query_manifest_for_table(&object_id).boxed().await?;
3832
3833 match table_info {
3834 Some(info) => {
3835 let table_uri = Self::construct_full_uri(&self.root, &info.location)?;
3836 let mut builder = DatasetBuilder::from_uri(&table_uri);
3837 if let Some(opts) = &self.storage_options {
3838 builder = builder.with_storage_options(opts.clone());
3839 }
3840 if let Some(session) = &self.session {
3841 builder = builder.with_session(session.clone());
3842 }
3843 let mut dataset = builder.load().await.map_err(|e| {
3844 Error::io_source(box_error(std::io::Error::other(format!(
3845 "Failed to open dataset: {}",
3846 e
3847 ))))
3848 })?;
3849
3850 let columns: Vec<&str> = request.columns.iter().map(|s| s.as_str()).collect();
3851 dataset.drop_columns(&columns).await.map_err(|e| {
3852 convert_lance_commit_error(&e, "drop_columns", Some(&object_id))
3853 })?;
3854
3855 let version = dataset.version().version as i64;
3856 Ok(AlterTableDropColumnsResponse::new(version))
3857 }
3858 None => Err(NamespaceError::TableNotFound { message: object_id }.into()),
3859 }
3860 }
3861}
3862
3863#[cfg(test)]
3864mod tests {
3865 use super::{
3866 BASE_OBJECTS_INDEX_NAME, ConflictResolution, CopyOnWriteMutation, DeleteObjectMutation,
3867 LANCE_DATA_DIR, LANCE_INDICES_DIR, MANIFEST_TABLE_NAME, ManifestBatchBuilder,
3868 ManifestEntry, ManifestIndexAccumulator, ManifestNamespace, ManifestOutputRow,
3869 ManifestRowValue, ManifestStreamMutation, OBJECT_ID_INDEX_NAME, OBJECT_TYPE_INDEX_NAME,
3870 ObjectType,
3871 };
3872 use crate::DirectoryNamespaceBuilder;
3873 use arrow::datatypes::DataType;
3874 use bytes::Bytes;
3875 use futures::StreamExt;
3876 use lance::index::DatasetIndexExt;
3877 use lance_core::utils::tempfile::TempStdDir;
3878 use lance_io::object_store::{ObjectStore, ObjectStoreParams, ObjectStoreRegistry};
3879 use lance_namespace::LanceNamespace;
3880 use lance_namespace::models::{
3881 CreateNamespaceRequest, CreateTableRequest, DeclareTableRequest, DescribeTableRequest,
3882 DropTableRequest, ListTablesRequest, TableExistsRequest,
3883 };
3884 use lance_table::feature_flags::FLAG_UNKNOWN;
3885 use lance_table::format::Fragment;
3886 use rstest::rstest;
3887 use std::collections::{HashMap, HashSet};
3888 use std::sync::Arc;
3889 use std::time::Duration;
3890 use tokio::sync::oneshot;
3891 use tokio::time::timeout;
3892
3893 async fn create_manifest_namespace(
3894 root: &str,
3895 inline_optimization_enabled: bool,
3896 ) -> ManifestNamespace {
3897 create_manifest_namespace_with_retries(root, inline_optimization_enabled, None).await
3898 }
3899
3900 async fn create_manifest_namespace_with_retries(
3901 root: &str,
3902 inline_optimization_enabled: bool,
3903 commit_retries: Option<u32>,
3904 ) -> ManifestNamespace {
3905 let (object_store, base_path) = ObjectStore::from_uri_and_params(
3906 Arc::new(ObjectStoreRegistry::default()),
3907 root,
3908 &ObjectStoreParams::default(),
3909 )
3910 .await
3911 .unwrap();
3912 ManifestNamespace::from_directory(
3913 root.to_string(),
3914 None,
3915 None,
3916 object_store,
3917 base_path,
3918 true,
3919 inline_optimization_enabled,
3920 commit_retries,
3921 )
3922 .await
3923 .unwrap()
3924 }
3925
3926 struct CommitConflictAfterRewriteMutation {
3927 root: String,
3928 conflict_object_id: String,
3929 }
3930
3931 impl ManifestStreamMutation for CommitConflictAfterRewriteMutation {
3932 type Output = ();
3933
3934 fn process_existing_row(
3935 &mut self,
3936 row: ManifestRowValue,
3937 output: &mut ManifestBatchBuilder,
3938 index_data: &mut ManifestIndexAccumulator,
3939 ) -> lance_core::Result<()> {
3940 output.append(
3941 index_data,
3942 ManifestOutputRow {
3943 object_id: &row.object_id,
3944 object_type: row.object_type,
3945 location: row.location.as_deref(),
3946 metadata: row.metadata.as_deref(),
3947 base_objects: row.base_objects.as_deref(),
3948 },
3949 )
3950 }
3951
3952 fn append_rows(
3953 &mut self,
3954 output: &mut ManifestBatchBuilder,
3955 index_data: &mut ManifestIndexAccumulator,
3956 ) -> lance_core::Result<()> {
3957 output.append(
3958 index_data,
3959 ManifestOutputRow {
3960 object_id: "attempted_table",
3961 object_type: ObjectType::Table,
3962 location: Some("attempted_table.lance"),
3963 metadata: None,
3964 base_objects: None,
3965 },
3966 )
3967 }
3968
3969 fn finish(&self) -> CopyOnWriteMutation<Self::Output> {
3970 let root = self.root.clone();
3971 let object_id = self.conflict_object_id.clone();
3972 std::thread::spawn(move || {
3973 let runtime = tokio::runtime::Runtime::new().unwrap();
3974 runtime.block_on(async move {
3975 let writer = create_manifest_namespace(&root, false).await;
3976 writer
3977 .insert_into_manifest_with_metadata(
3978 vec![ManifestEntry {
3979 object_id,
3980 object_type: ObjectType::Table,
3981 location: Some("conflicting_table.lance".to_string()),
3982 metadata: None,
3983 }],
3984 None,
3985 )
3986 .await
3987 .unwrap();
3988 });
3989 })
3990 .join()
3991 .unwrap();
3992 CopyOnWriteMutation::updated(())
3993 }
3994 }
3995
3996 struct ConcurrentDeleteBeforeCommitMutation {
4000 inner: DeleteObjectMutation,
4001 root: String,
4002 target: String,
4003 }
4004
4005 impl ManifestStreamMutation for ConcurrentDeleteBeforeCommitMutation {
4006 type Output = ();
4007
4008 fn process_existing_row(
4009 &mut self,
4010 row: ManifestRowValue,
4011 output: &mut ManifestBatchBuilder,
4012 index_data: &mut ManifestIndexAccumulator,
4013 ) -> lance_core::Result<()> {
4014 self.inner.process_existing_row(row, output, index_data)
4015 }
4016
4017 fn append_rows(
4018 &mut self,
4019 output: &mut ManifestBatchBuilder,
4020 index_data: &mut ManifestIndexAccumulator,
4021 ) -> lance_core::Result<()> {
4022 self.inner.append_rows(output, index_data)
4023 }
4024
4025 fn finish(&self) -> CopyOnWriteMutation<Self::Output> {
4026 let root = self.root.clone();
4027 let target = self.target.clone();
4028 std::thread::spawn(move || {
4029 let runtime = tokio::runtime::Runtime::new().unwrap();
4030 runtime.block_on(async move {
4031 let writer = create_manifest_namespace(&root, false).await;
4032 writer.delete_from_manifest(&target).await.unwrap();
4033 });
4034 })
4035 .join()
4036 .unwrap();
4037 self.inner.finish()
4038 }
4039
4040 fn conflict_resolution(&self) -> ConflictResolution<Self::Output> {
4041 ConflictResolution::SucceedIfAbsent {
4042 object_id: self.target.clone(),
4043 output: (),
4044 }
4045 }
4046 }
4047
4048 async fn manifest_base_objects(
4049 manifest_ns: &ManifestNamespace,
4050 ) -> HashMap<String, Option<Vec<String>>> {
4051 let mut scanner = manifest_ns.manifest_scanner().await.unwrap();
4052 scanner.project(&["object_id", "base_objects"]).unwrap();
4053 let batches = ManifestNamespace::execute_scanner(scanner).await.unwrap();
4054 let mut rows = HashMap::new();
4055 for batch in batches {
4056 let object_ids = ManifestNamespace::get_string_column(&batch, "object_id").unwrap();
4057 let base_objects = ManifestNamespace::base_objects_column_values(&batch).unwrap();
4058 for (row, value) in base_objects.into_iter().enumerate() {
4059 rows.insert(object_ids.value(row).to_string(), value);
4060 }
4061 }
4062 rows
4063 }
4064
4065 async fn manifest_data_paths(manifest_ns: &ManifestNamespace) -> HashSet<String> {
4066 let data_dir = manifest_ns
4067 .base_path
4068 .clone()
4069 .join(MANIFEST_TABLE_NAME)
4070 .join(LANCE_DATA_DIR);
4071 let mut stream = manifest_ns.object_store.read_dir_all(&data_dir, None);
4072 let mut paths = HashSet::new();
4073 while let Some(meta) = stream.next().await.transpose().unwrap() {
4074 paths.insert(meta.location.to_string());
4075 }
4076 paths
4077 }
4078
4079 async fn manifest_index_paths(manifest_ns: &ManifestNamespace) -> HashSet<String> {
4080 let index_dir = manifest_ns
4081 .base_path
4082 .clone()
4083 .join(MANIFEST_TABLE_NAME)
4084 .join(LANCE_INDICES_DIR);
4085 let mut stream = manifest_ns.object_store.read_dir_all(&index_dir, None);
4086 let mut paths = HashSet::new();
4087 while let Some(meta) = stream.next().await.transpose().unwrap() {
4088 paths.insert(meta.location.to_string());
4089 }
4090 paths
4091 }
4092
4093 fn create_test_ipc_data() -> Vec<u8> {
4094 use arrow::array::{Int32Array, StringArray};
4095 use arrow::datatypes::{DataType, Field, Schema};
4096 use arrow::ipc::writer::StreamWriter;
4097 use arrow::record_batch::RecordBatch;
4098 use std::sync::Arc;
4099
4100 let schema = Arc::new(Schema::new(vec![
4101 Field::new("id", DataType::Int32, false),
4102 Field::new("name", DataType::Utf8, false),
4103 ]));
4104
4105 let batch = RecordBatch::try_new(
4106 schema.clone(),
4107 vec![
4108 Arc::new(Int32Array::from(vec![1, 2, 3])),
4109 Arc::new(StringArray::from(vec!["a", "b", "c"])),
4110 ],
4111 )
4112 .unwrap();
4113
4114 let mut buffer = Vec::new();
4115 {
4116 let mut writer = StreamWriter::try_new(&mut buffer, &schema).unwrap();
4117 writer.write(&batch).unwrap();
4118 writer.finish().unwrap();
4119 }
4120 buffer
4121 }
4122
4123 async fn set_manifest_table_metadata(temp_path: &str, key: &str, value: &str) {
4126 use lance::dataset::builder::DatasetBuilder;
4127 let mut ds = DatasetBuilder::from_uri(format!("{}/{}", temp_path, MANIFEST_TABLE_NAME))
4128 .load()
4129 .await
4130 .unwrap();
4131 ds.update_metadata([(key, value)]).await.unwrap();
4132 }
4133
4134 async fn create_namespace_with_one_table(temp_path: &str) {
4135 let ns = DirectoryNamespaceBuilder::new(temp_path)
4136 .build()
4137 .await
4138 .unwrap();
4139 let mut create_request = CreateTableRequest::new();
4140 create_request.id = Some(vec!["t1".to_string()]);
4141 ns.create_table(create_request, Bytes::from(create_test_ipc_data()))
4142 .await
4143 .unwrap();
4144 }
4145
4146 #[tokio::test]
4149 async fn test_manifest_has_no_feature_flags_by_default() {
4150 use lance::dataset::builder::DatasetBuilder;
4151 let temp_dir = TempStdDir::default();
4152 let temp_path = temp_dir.to_str().unwrap();
4153 create_namespace_with_one_table(temp_path).await;
4154
4155 let ds = DatasetBuilder::from_uri(format!("{}/{}", temp_path, MANIFEST_TABLE_NAME))
4156 .load()
4157 .await
4158 .unwrap();
4159 assert!(
4160 !ds.metadata()
4161 .contains_key(crate::dir::manifest_feature_flags::READER_FEATURE_FLAGS_KEY)
4162 );
4163 assert!(
4164 !ds.metadata()
4165 .contains_key(crate::dir::manifest_feature_flags::WRITER_FEATURE_FLAGS_KEY)
4166 );
4167 }
4168
4169 #[tokio::test]
4172 async fn test_unknown_reader_flag_blocks_access() {
4173 let temp_dir = TempStdDir::default();
4174 let temp_path = temp_dir.to_str().unwrap();
4175 create_namespace_with_one_table(temp_path).await;
4176 set_manifest_table_metadata(
4177 temp_path,
4178 crate::dir::manifest_feature_flags::READER_FEATURE_FLAGS_KEY,
4179 "1",
4180 )
4181 .await;
4182
4183 let err = DirectoryNamespaceBuilder::new(temp_path)
4184 .build()
4185 .await
4186 .expect_err("opening a manifest with an unknown reader flag should fail");
4187 assert!(
4188 err.to_string().to_lowercase().contains("upgrade"),
4189 "expected an upgrade error, got: {err}"
4190 );
4191 }
4192
4193 #[tokio::test]
4195 async fn test_unknown_writer_flag_blocks_writes_but_allows_reads() {
4196 let temp_dir = TempStdDir::default();
4197 let temp_path = temp_dir.to_str().unwrap();
4198 create_namespace_with_one_table(temp_path).await;
4199 set_manifest_table_metadata(
4200 temp_path,
4201 crate::dir::manifest_feature_flags::WRITER_FEATURE_FLAGS_KEY,
4202 "1",
4203 )
4204 .await;
4205
4206 let ns = DirectoryNamespaceBuilder::new(temp_path)
4207 .build()
4208 .await
4209 .expect("reads should still be allowed with only a writer flag set");
4210 let mut list_request = ListTablesRequest::new();
4211 list_request.id = Some(vec![]);
4212 assert_eq!(ns.list_tables(list_request).await.unwrap().tables.len(), 1);
4213
4214 let entries_before = dir_entry_names(temp_path);
4216 let mut create_request = CreateTableRequest::new();
4217 create_request.id = Some(vec!["t2".to_string()]);
4218 let err = ns
4219 .create_table(create_request, Bytes::from(create_test_ipc_data()))
4220 .await
4221 .expect_err("writing through an unknown writer flag should fail");
4222 assert!(
4223 err.to_string().to_lowercase().contains("upgrade"),
4224 "expected an upgrade error, got: {err}"
4225 );
4226 assert_eq!(
4227 entries_before,
4228 dir_entry_names(temp_path),
4229 "a refused create_table must not create an orphaned table directory"
4230 );
4231
4232 let mut create_ns = CreateNamespaceRequest::new();
4237 create_ns.id = Some(vec!["ns1".to_string()]);
4238 let err = ns
4239 .create_namespace(create_ns)
4240 .await
4241 .expect_err("create_namespace through an unknown writer flag should fail");
4242 assert!(
4243 err.to_string().to_lowercase().contains("upgrade"),
4244 "expected an upgrade error, got: {err}"
4245 );
4246
4247 let mut drop_request = DropTableRequest::new();
4248 drop_request.id = Some(vec!["t1".to_string()]);
4249 let err = ns
4250 .drop_table(drop_request)
4251 .await
4252 .expect_err("drop_table through an unknown writer flag should fail");
4253 assert!(
4254 err.to_string().to_lowercase().contains("upgrade"),
4255 "expected an upgrade error, got: {err}"
4256 );
4257 }
4258
4259 fn dir_entry_names(path: &str) -> std::collections::BTreeSet<String> {
4260 std::fs::read_dir(path)
4261 .unwrap()
4262 .map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
4263 .collect()
4264 }
4265
4266 #[tokio::test]
4267 async fn test_manifest_rewrite_preserves_utf8_metadata_and_base_objects() {
4268 let temp_dir = TempStdDir::default();
4269 let temp_path = temp_dir.to_str().unwrap();
4270 let manifest_ns = create_manifest_namespace(temp_path, true).await;
4271
4272 manifest_ns
4273 .insert_into_manifest_with_metadata(
4274 vec![ManifestEntry {
4275 object_id: "view".to_string(),
4276 object_type: ObjectType::Table,
4277 location: Some("view.lance".to_string()),
4278 metadata: Some(r#"{"kind":"view"}"#.to_string()),
4279 }],
4280 Some(vec!["base_a".to_string(), "base_b".to_string()]),
4281 )
4282 .await
4283 .unwrap();
4284 manifest_ns
4285 .insert_into_manifest_with_metadata(
4286 vec![ManifestEntry {
4287 object_id: "other".to_string(),
4288 object_type: ObjectType::Namespace,
4289 location: None,
4290 metadata: Some(r#"{"kind":"namespace"}"#.to_string()),
4291 }],
4292 None,
4293 )
4294 .await
4295 .unwrap();
4296
4297 let dataset_guard = manifest_ns.manifest_dataset.get().await.unwrap();
4298 let metadata_field = dataset_guard.schema().field("metadata").unwrap();
4299 assert_eq!(metadata_field.data_type(), DataType::Utf8);
4300 drop(dataset_guard);
4301
4302 let base_objects = manifest_base_objects(&manifest_ns).await;
4303 assert_eq!(
4304 base_objects.get("view").cloned().unwrap(),
4305 Some(vec!["base_a".to_string(), "base_b".to_string()])
4306 );
4307 assert_eq!(base_objects.get("other").cloned().unwrap(), None);
4308 }
4309
4310 #[tokio::test]
4311 async fn test_manifest_rewrite_replacement_indices_are_versioned() {
4312 let temp_dir = TempStdDir::default();
4313 let temp_path = temp_dir.to_str().unwrap();
4314 let manifest_ns = create_manifest_namespace(temp_path, true).await;
4315
4316 manifest_ns
4317 .insert_into_manifest_with_metadata(
4318 vec![ManifestEntry {
4319 object_id: "table".to_string(),
4320 object_type: ObjectType::Table,
4321 location: Some("table.lance".to_string()),
4322 metadata: None,
4323 }],
4324 Some(vec!["base".to_string()]),
4325 )
4326 .await
4327 .unwrap();
4328
4329 let dataset_guard = manifest_ns.manifest_dataset.get().await.unwrap();
4330 let dataset_version = dataset_guard.version().version;
4331 let indices = dataset_guard.load_indices().await.unwrap();
4332 let names = indices
4333 .iter()
4334 .map(|index| index.name.as_str())
4335 .collect::<HashSet<_>>();
4336 assert!(names.contains(OBJECT_ID_INDEX_NAME));
4337 assert!(names.contains(OBJECT_TYPE_INDEX_NAME));
4338 assert!(names.contains(BASE_OBJECTS_INDEX_NAME));
4339 for index in indices.iter() {
4340 assert_eq!(index.dataset_version, dataset_version);
4341 assert!(!index.fragment_bitmap.as_ref().unwrap().is_empty());
4342 }
4343 }
4344
4345 #[tokio::test]
4346 async fn test_manifest_rewrite_empty_manifest_keeps_replacement_indices_valid() {
4347 let temp_dir = TempStdDir::default();
4348 let temp_path = temp_dir.to_str().unwrap();
4349 let manifest_ns = create_manifest_namespace(temp_path, true).await;
4350
4351 manifest_ns
4352 .insert_into_manifest_with_metadata(
4353 vec![ManifestEntry {
4354 object_id: "table".to_string(),
4355 object_type: ObjectType::Table,
4356 location: Some("table.lance".to_string()),
4357 metadata: None,
4358 }],
4359 None,
4360 )
4361 .await
4362 .unwrap();
4363 manifest_ns.delete_from_manifest("table").await.unwrap();
4364
4365 assert!(!manifest_ns.manifest_contains_object("table").await.unwrap());
4366 let mut scanner = manifest_ns.manifest_scanner().await.unwrap();
4367 scanner.project(&["object_id"]).unwrap();
4368 let rows = ManifestNamespace::execute_scanner(scanner)
4369 .await
4370 .unwrap()
4371 .into_iter()
4372 .map(|batch| batch.num_rows())
4373 .sum::<usize>();
4374 assert_eq!(rows, 0);
4375
4376 let dataset_guard = manifest_ns.manifest_dataset.get().await.unwrap();
4377 let dataset_version = dataset_guard.version().version;
4378 let indices = dataset_guard.load_indices().await.unwrap();
4379 let names = indices
4380 .iter()
4381 .map(|index| index.name.as_str())
4382 .collect::<HashSet<_>>();
4383 assert!(names.contains(OBJECT_ID_INDEX_NAME));
4384 assert!(names.contains(OBJECT_TYPE_INDEX_NAME));
4385 assert!(names.contains(BASE_OBJECTS_INDEX_NAME));
4386 for index in indices.iter() {
4387 assert_eq!(index.dataset_version, dataset_version);
4388 }
4389 }
4390
4391 #[tokio::test]
4392 async fn test_manifest_rewrite_fragment_bitmap_uses_overwrite_fragment_ids() {
4393 let temp_dir = TempStdDir::default();
4394 let temp_path = temp_dir.to_str().unwrap();
4395 let manifest_ns = create_manifest_namespace(temp_path, false).await;
4396 let dataset_guard = manifest_ns.manifest_dataset.get().await.unwrap();
4397 let fragments = vec![Fragment::new(0), Fragment::new(0), Fragment::new(7)];
4398
4399 let manifest = ManifestNamespace::manifest_from_overwrite_transaction(
4400 dataset_guard.manifest(),
4401 dataset_guard.manifest().schema.clone(),
4402 &fragments,
4403 );
4404
4405 let fragment_ids = manifest
4406 .fragments
4407 .iter()
4408 .map(|fragment| fragment.id)
4409 .collect::<Vec<_>>();
4410 assert_eq!(fragment_ids, vec![0, 1, 7]);
4411 assert_eq!(
4412 ManifestNamespace::manifest_fragment_bitmap(&manifest)
4413 .unwrap()
4414 .into_iter()
4415 .collect::<Vec<_>>(),
4416 vec![0, 1, 7]
4417 );
4418 }
4419
4420 #[tokio::test]
4421 async fn test_manifest_writes_reject_unknown_writer_flag_before_staging() {
4422 let temp_dir = TempStdDir::default();
4423 let temp_path = temp_dir.to_str().unwrap();
4424 let manifest_ns = create_manifest_namespace(temp_path, false).await;
4425 let data_paths_before = manifest_data_paths(&manifest_ns).await;
4426 let original_version = {
4427 let mut dataset = manifest_ns.manifest_dataset.get_mut().await.unwrap();
4428 let mut manifest = dataset.manifest().clone();
4429 manifest.writer_feature_flags |= FLAG_UNKNOWN << 1;
4430 let version = manifest.version;
4431 dataset.manifest = Arc::new(manifest);
4432 version
4433 };
4434
4435 let entries_before = dir_entry_names(temp_path);
4436 let mut declare_request = DeclareTableRequest::new();
4437 declare_request.id = Some(vec!["declared_table".to_string()]);
4438 let error = manifest_ns
4439 .declare_table(declare_request)
4440 .await
4441 .unwrap_err();
4442 assert!(
4443 error.to_string().to_lowercase().contains("upgrade"),
4444 "expected an upgrade error, got: {error}"
4445 );
4446 assert_eq!(dir_entry_names(temp_path), entries_before);
4447
4448 let mut create_request = CreateTableRequest::new();
4449 create_request.id = Some(vec!["new_table".to_string()]);
4450 let error = manifest_ns
4451 .create_table(create_request, Bytes::from(create_test_ipc_data()))
4452 .await
4453 .unwrap_err();
4454 assert!(
4455 error.to_string().to_lowercase().contains("upgrade"),
4456 "expected an upgrade error, got: {error}"
4457 );
4458 assert_eq!(dir_entry_names(temp_path), entries_before);
4459
4460 let error = manifest_ns
4461 .insert_into_manifest_with_metadata(
4462 vec![ManifestEntry {
4463 object_id: "table".to_string(),
4464 object_type: ObjectType::Table,
4465 location: Some("table.lance".to_string()),
4466 metadata: None,
4467 }],
4468 None,
4469 )
4470 .await
4471 .unwrap_err();
4472
4473 assert!(
4474 error.to_string().to_lowercase().contains("upgrade"),
4475 "expected an upgrade error, got: {error}"
4476 );
4477 assert_eq!(
4478 manifest_ns
4479 .manifest_dataset
4480 .get()
4481 .await
4482 .unwrap()
4483 .version()
4484 .version,
4485 original_version
4486 );
4487 assert_eq!(manifest_data_paths(&manifest_ns).await, data_paths_before);
4488 }
4489
4490 #[tokio::test]
4497 async fn test_get_mut_is_served_before_later_readers() {
4498 let temp_dir = TempStdDir::default();
4499 let manifest_ns = create_manifest_namespace(temp_dir.to_str().unwrap(), false).await;
4500 let wrapper = manifest_ns.manifest_dataset.clone();
4501
4502 let blocker = wrapper.0.write().await;
4506 let writer = {
4507 let wrapper = wrapper.clone();
4508 tokio::spawn(async move {
4509 wrapper.get_mut().await.unwrap();
4510 })
4511 };
4512 tokio::task::yield_now().await;
4513
4514 let (release_tx, release_rx) = oneshot::channel::<()>();
4519 let late_reader = {
4520 let wrapper = wrapper.clone();
4521 tokio::spawn(async move {
4522 let guard = wrapper.0.read().await;
4523 release_rx.await.unwrap();
4524 drop(guard);
4525 })
4526 };
4527 tokio::task::yield_now().await;
4528
4529 drop(blocker);
4530 timeout(Duration::from_secs(10), writer)
4531 .await
4532 .expect("get_mut() waited on a reader that arrived after it")
4533 .unwrap();
4534 release_tx.send(()).unwrap();
4535 timeout(Duration::from_secs(10), late_reader)
4536 .await
4537 .expect("the late reader did not finish after the writer released the lock")
4538 .unwrap();
4539 }
4540
4541 #[tokio::test]
4542 async fn test_manifest_noop_delete_uses_latest_snapshot() {
4543 let temp_dir = TempStdDir::default();
4544 let temp_path = temp_dir.to_str().unwrap();
4545 let stale_ns = create_manifest_namespace(temp_path, false).await;
4546 let writer_ns = create_manifest_namespace(temp_path, false).await;
4547
4548 writer_ns
4549 .insert_into_manifest_with_metadata(
4550 vec![ManifestEntry {
4551 object_id: "late_table".to_string(),
4552 object_type: ObjectType::Table,
4553 location: Some("late_table.lance".to_string()),
4554 metadata: None,
4555 }],
4556 None,
4557 )
4558 .await
4559 .unwrap();
4560
4561 stale_ns.delete_from_manifest("late_table").await.unwrap();
4562
4563 let check_ns = create_manifest_namespace(temp_path, false).await;
4564 assert!(
4565 !check_ns
4566 .manifest_contains_object("late_table")
4567 .await
4568 .unwrap()
4569 );
4570 }
4571
4572 #[tokio::test]
4573 async fn test_manifest_noop_delete_cleans_uncommitted_data_file() {
4574 let temp_dir = TempStdDir::default();
4575 let temp_path = temp_dir.to_str().unwrap();
4576 let manifest_ns = create_manifest_namespace(temp_path, false).await;
4577
4578 manifest_ns
4579 .insert_into_manifest_with_metadata(
4580 vec![ManifestEntry {
4581 object_id: "table".to_string(),
4582 object_type: ObjectType::Table,
4583 location: Some("table.lance".to_string()),
4584 metadata: None,
4585 }],
4586 None,
4587 )
4588 .await
4589 .unwrap();
4590
4591 let before = manifest_data_paths(&manifest_ns).await;
4592 assert!(!before.is_empty());
4593
4594 manifest_ns
4595 .delete_from_manifest("missing_table")
4596 .await
4597 .unwrap();
4598
4599 let after = manifest_data_paths(&manifest_ns).await;
4600 assert_eq!(after, before);
4601 }
4602
4603 #[tokio::test]
4604 async fn test_manifest_final_commit_failure_cleans_uncommitted_rewrite_files() {
4605 let temp_dir = TempStdDir::default();
4606 let temp_path = temp_dir.to_str().unwrap();
4607 let manifest_ns = create_manifest_namespace_with_retries(temp_path, true, Some(0)).await;
4608
4609 manifest_ns
4610 .insert_into_manifest_with_metadata(
4611 vec![ManifestEntry {
4612 object_id: "table".to_string(),
4613 object_type: ObjectType::Table,
4614 location: Some("table.lance".to_string()),
4615 metadata: None,
4616 }],
4617 None,
4618 )
4619 .await
4620 .unwrap();
4621
4622 let before_data_paths = manifest_data_paths(&manifest_ns).await;
4623 let before_index_paths = manifest_index_paths(&manifest_ns).await;
4624
4625 let result = manifest_ns
4626 .rewrite_manifest("Failed to test manifest cleanup", || {
4627 CommitConflictAfterRewriteMutation {
4628 root: temp_path.to_string(),
4629 conflict_object_id: "conflicting_table".to_string(),
4630 }
4631 })
4632 .await;
4633 assert!(result.is_err());
4634
4635 let after_data_paths = manifest_data_paths(&manifest_ns).await;
4636 assert!(before_data_paths.is_subset(&after_data_paths));
4637 assert_eq!(after_data_paths.len(), before_data_paths.len() + 1);
4638 assert_eq!(manifest_index_paths(&manifest_ns).await, before_index_paths);
4639 assert!(
4640 manifest_ns
4641 .manifest_contains_object("conflicting_table")
4642 .await
4643 .unwrap()
4644 );
4645 assert!(
4646 !manifest_ns
4647 .manifest_contains_object("attempted_table")
4648 .await
4649 .unwrap()
4650 );
4651 }
4652
4653 #[tokio::test]
4654 async fn test_manifest_commit_visible_on_memory_store() {
4655 let manifest_ns = create_manifest_namespace("memory://test_commit_visible", false).await;
4660 manifest_ns
4661 .insert_into_manifest_with_metadata(
4662 vec![ManifestEntry {
4663 object_id: "table".to_string(),
4664 object_type: ObjectType::Table,
4665 location: Some("table.lance".to_string()),
4666 metadata: None,
4667 }],
4668 None,
4669 )
4670 .await
4671 .unwrap();
4672 assert!(manifest_ns.manifest_contains_object("table").await.unwrap());
4673 manifest_ns
4675 .insert_into_manifest_with_metadata(
4676 vec![ManifestEntry {
4677 object_id: "table2".to_string(),
4678 object_type: ObjectType::Table,
4679 location: Some("table2.lance".to_string()),
4680 metadata: None,
4681 }],
4682 None,
4683 )
4684 .await
4685 .unwrap();
4686 assert!(
4687 manifest_ns
4688 .manifest_contains_object("table2")
4689 .await
4690 .unwrap()
4691 );
4692 }
4693
4694 #[tokio::test]
4695 async fn test_manifest_commit_uses_inline_transaction() {
4696 let temp_dir = TempStdDir::default();
4697 let temp_path = temp_dir.to_str().unwrap();
4698 let manifest_ns = create_manifest_namespace(temp_path, false).await;
4699
4700 manifest_ns
4701 .insert_into_manifest_with_metadata(
4702 vec![ManifestEntry {
4703 object_id: "table".to_string(),
4704 object_type: ObjectType::Table,
4705 location: Some("table.lance".to_string()),
4706 metadata: None,
4707 }],
4708 None,
4709 )
4710 .await
4711 .unwrap();
4712
4713 let dataset_guard = manifest_ns.manifest_dataset.get().await.unwrap();
4714 let manifest = dataset_guard.manifest();
4715 assert!(manifest.transaction_section.is_some());
4718 assert!(manifest.transaction_file.is_none());
4719 }
4720
4721 #[tokio::test]
4722 async fn test_manifest_commit_landed_attributes_data_file() {
4723 let temp_dir = TempStdDir::default();
4724 let temp_path = temp_dir.to_str().unwrap();
4725 let manifest_ns = create_manifest_namespace(temp_path, false).await;
4726
4727 manifest_ns
4728 .insert_into_manifest_with_metadata(
4729 vec![ManifestEntry {
4730 object_id: "table".to_string(),
4731 object_type: ObjectType::Table,
4732 location: Some("table.lance".to_string()),
4733 metadata: None,
4734 }],
4735 None,
4736 )
4737 .await
4738 .unwrap();
4739
4740 let dataset = Arc::new(manifest_ns.manifest_dataset.get().await.unwrap().clone());
4741 let version = dataset.manifest().version;
4742 let our_files = dataset
4743 .manifest()
4744 .fragments
4745 .iter()
4746 .flat_map(|fragment| fragment.files.iter())
4747 .map(|file| file.path.clone())
4748 .collect::<HashSet<_>>();
4749 assert!(!our_files.is_empty());
4750
4751 assert!(
4754 manifest_ns
4755 .manifest_commit_landed(&dataset, version, &our_files)
4756 .await
4757 );
4758 let other = HashSet::from(["missing.lance".to_string()]);
4760 assert!(
4761 !manifest_ns
4762 .manifest_commit_landed(&dataset, version, &other)
4763 .await
4764 );
4765 assert!(
4767 !manifest_ns
4768 .manifest_commit_landed(&dataset, version + 100, &our_files)
4769 .await
4770 );
4771 }
4772
4773 #[tokio::test]
4774 async fn test_manifest_delete_conflict_with_concurrent_delete_succeeds() {
4775 let temp_dir = TempStdDir::default();
4776 let temp_path = temp_dir.to_str().unwrap();
4777 let manifest_ns = create_manifest_namespace_with_retries(temp_path, false, Some(0)).await;
4778
4779 manifest_ns
4780 .insert_into_manifest_with_metadata(
4781 vec![ManifestEntry {
4782 object_id: "table".to_string(),
4783 object_type: ObjectType::Table,
4784 location: Some("table.lance".to_string()),
4785 metadata: None,
4786 }],
4787 None,
4788 )
4789 .await
4790 .unwrap();
4791 assert!(manifest_ns.manifest_contains_object("table").await.unwrap());
4792
4793 let result = manifest_ns
4797 .rewrite_manifest("Failed to delete from manifest", || {
4798 ConcurrentDeleteBeforeCommitMutation {
4799 inner: DeleteObjectMutation {
4800 object_id: "table".to_string(),
4801 deleted: false,
4802 },
4803 root: temp_path.to_string(),
4804 target: "table".to_string(),
4805 }
4806 })
4807 .await;
4808
4809 assert!(result.is_ok(), "delete should succeed: {result:?}");
4810 assert!(!manifest_ns.manifest_contains_object("table").await.unwrap());
4811 }
4812
4813 #[rstest]
4814 #[case::with_optimization(true)]
4815 #[case::without_optimization(false)]
4816 #[tokio::test]
4817 async fn test_manifest_namespace_basic_create_and_list(#[case] inline_optimization: bool) {
4818 let temp_dir = TempStdDir::default();
4819 let temp_path = temp_dir.to_str().unwrap();
4820
4821 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
4823 .inline_optimization_enabled(inline_optimization)
4824 .build()
4825 .await
4826 .unwrap();
4827
4828 let mut request = ListTablesRequest::new();
4830 request.id = Some(vec![]);
4831 let response = dir_namespace.list_tables(request).await.unwrap();
4832 assert_eq!(response.tables.len(), 0);
4833
4834 let buffer = create_test_ipc_data();
4836 let mut create_request = CreateTableRequest::new();
4837 create_request.id = Some(vec!["test_table".to_string()]);
4838
4839 let _response = dir_namespace
4840 .create_table(create_request, Bytes::from(buffer))
4841 .await
4842 .unwrap();
4843
4844 let mut request = ListTablesRequest::new();
4846 request.id = Some(vec![]);
4847 let response = dir_namespace.list_tables(request).await.unwrap();
4848 assert_eq!(response.tables.len(), 1);
4849 assert_eq!(response.tables[0], "test_table");
4850 }
4851
4852 #[rstest]
4853 #[case::with_optimization(true)]
4854 #[case::without_optimization(false)]
4855 #[tokio::test]
4856 async fn test_manifest_namespace_table_exists(#[case] inline_optimization: bool) {
4857 let temp_dir = TempStdDir::default();
4858 let temp_path = temp_dir.to_str().unwrap();
4859
4860 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
4861 .inline_optimization_enabled(inline_optimization)
4862 .build()
4863 .await
4864 .unwrap();
4865
4866 let mut request = TableExistsRequest::new();
4868 request.id = Some(vec!["nonexistent".to_string()]);
4869 let result = dir_namespace.table_exists(request).await;
4870 assert!(result.is_err());
4871
4872 let buffer = create_test_ipc_data();
4874 let mut create_request = CreateTableRequest::new();
4875 create_request.id = Some(vec!["test_table".to_string()]);
4876 dir_namespace
4877 .create_table(create_request, Bytes::from(buffer))
4878 .await
4879 .unwrap();
4880
4881 let mut request = TableExistsRequest::new();
4883 request.id = Some(vec!["test_table".to_string()]);
4884 let result = dir_namespace.table_exists(request).await;
4885 assert!(result.is_ok());
4886 }
4887
4888 #[rstest]
4889 #[case::with_optimization(true)]
4890 #[case::without_optimization(false)]
4891 #[tokio::test]
4892 async fn test_manifest_namespace_describe_table(#[case] inline_optimization: bool) {
4893 let temp_dir = TempStdDir::default();
4894 let temp_path = temp_dir.to_str().unwrap();
4895
4896 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
4897 .inline_optimization_enabled(inline_optimization)
4898 .build()
4899 .await
4900 .unwrap();
4901
4902 let mut request = DescribeTableRequest::new();
4904 request.id = Some(vec!["nonexistent".to_string()]);
4905 let result = dir_namespace.describe_table(request).await;
4906 assert!(result.is_err());
4907
4908 let buffer = create_test_ipc_data();
4910 let mut create_request = CreateTableRequest::new();
4911 create_request.id = Some(vec!["test_table".to_string()]);
4912 dir_namespace
4913 .create_table(create_request, Bytes::from(buffer))
4914 .await
4915 .unwrap();
4916
4917 let mut request = DescribeTableRequest::new();
4919 request.id = Some(vec!["test_table".to_string()]);
4920 let response = dir_namespace.describe_table(request).await.unwrap();
4921 assert!(response.location.is_some());
4922 assert!(response.location.unwrap().contains("test_table"));
4923 }
4924
4925 #[rstest]
4926 #[case::with_optimization(true)]
4927 #[case::without_optimization(false)]
4928 #[tokio::test]
4929 async fn test_manifest_namespace_drop_table(#[case] inline_optimization: bool) {
4930 let temp_dir = TempStdDir::default();
4931 let temp_path = temp_dir.to_str().unwrap();
4932
4933 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
4934 .inline_optimization_enabled(inline_optimization)
4935 .build()
4936 .await
4937 .unwrap();
4938
4939 let buffer = create_test_ipc_data();
4941 let mut create_request = CreateTableRequest::new();
4942 create_request.id = Some(vec!["test_table".to_string()]);
4943 dir_namespace
4944 .create_table(create_request, Bytes::from(buffer))
4945 .await
4946 .unwrap();
4947
4948 let mut request = ListTablesRequest::new();
4950 request.id = Some(vec![]);
4951 let response = dir_namespace.list_tables(request).await.unwrap();
4952 assert_eq!(response.tables.len(), 1);
4953
4954 let mut drop_request = DropTableRequest::new();
4956 drop_request.id = Some(vec!["test_table".to_string()]);
4957 let _response = dir_namespace.drop_table(drop_request).await.unwrap();
4958
4959 let mut request = ListTablesRequest::new();
4961 request.id = Some(vec![]);
4962 let response = dir_namespace.list_tables(request).await.unwrap();
4963 assert_eq!(response.tables.len(), 0);
4964 }
4965
4966 #[tokio::test]
4967 async fn test_list_tables_pagination_limit_zero() {
4968 let temp_dir = TempStdDir::default();
4969 let temp_path = temp_dir.to_str().unwrap();
4970
4971 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
4972 .build()
4973 .await
4974 .unwrap();
4975
4976 let buffer = create_test_ipc_data();
4977 let mut create_request = CreateTableRequest::new();
4978 create_request.id = Some(vec!["alpha".to_string()]);
4979 dir_namespace
4980 .create_table(create_request, Bytes::from(buffer))
4981 .await
4982 .unwrap();
4983
4984 let response = dir_namespace
4985 .list_tables(ListTablesRequest {
4986 id: Some(vec![]),
4987 limit: Some(0),
4988 ..Default::default()
4989 })
4990 .await
4991 .unwrap();
4992
4993 assert!(response.tables.is_empty());
4994 assert!(response.page_token.is_none());
4995 }
4996
4997 #[rstest]
4998 #[case::with_optimization(true)]
4999 #[case::without_optimization(false)]
5000 #[tokio::test]
5001 async fn test_manifest_namespace_multiple_tables(#[case] inline_optimization: bool) {
5002 let temp_dir = TempStdDir::default();
5003 let temp_path = temp_dir.to_str().unwrap();
5004
5005 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5006 .inline_optimization_enabled(inline_optimization)
5007 .build()
5008 .await
5009 .unwrap();
5010
5011 let buffer = create_test_ipc_data();
5013 for i in 1..=3 {
5014 let mut create_request = CreateTableRequest::new();
5015 create_request.id = Some(vec![format!("table{}", i)]);
5016 dir_namespace
5017 .create_table(create_request, Bytes::from(buffer.clone()))
5018 .await
5019 .unwrap();
5020 }
5021
5022 let mut request = ListTablesRequest::new();
5024 request.id = Some(vec![]);
5025 let response = dir_namespace.list_tables(request).await.unwrap();
5026 assert_eq!(response.tables.len(), 3);
5027 assert!(response.tables.contains(&"table1".to_string()));
5028 assert!(response.tables.contains(&"table2".to_string()));
5029 assert!(response.tables.contains(&"table3".to_string()));
5030 }
5031
5032 #[rstest]
5033 #[case::with_optimization(true)]
5034 #[case::without_optimization(false)]
5035 #[tokio::test]
5036 async fn test_directory_only_mode(#[case] inline_optimization: bool) {
5037 let temp_dir = TempStdDir::default();
5038 let temp_path = temp_dir.to_str().unwrap();
5039
5040 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5042 .manifest_enabled(false)
5043 .inline_optimization_enabled(inline_optimization)
5044 .build()
5045 .await
5046 .unwrap();
5047
5048 let mut request = ListTablesRequest::new();
5050 request.id = Some(vec![]);
5051 let response = dir_namespace.list_tables(request).await.unwrap();
5052 assert_eq!(response.tables.len(), 0);
5053
5054 let buffer = create_test_ipc_data();
5056 let mut create_request = CreateTableRequest::new();
5057 create_request.id = Some(vec!["test_table".to_string()]);
5058
5059 let _response = dir_namespace
5061 .create_table(create_request, Bytes::from(buffer))
5062 .await
5063 .unwrap();
5064
5065 let mut request = ListTablesRequest::new();
5067 request.id = Some(vec![]);
5068 let response = dir_namespace.list_tables(request).await.unwrap();
5069 assert_eq!(response.tables.len(), 1);
5070 assert_eq!(response.tables[0], "test_table");
5071 }
5072
5073 #[rstest]
5074 #[case::with_optimization(true)]
5075 #[case::without_optimization(false)]
5076 #[tokio::test]
5077 async fn test_dual_mode_merge(#[case] inline_optimization: bool) {
5078 let temp_dir = TempStdDir::default();
5079 let temp_path = temp_dir.to_str().unwrap();
5080
5081 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5083 .manifest_enabled(true)
5084 .dir_listing_enabled(true)
5085 .inline_optimization_enabled(inline_optimization)
5086 .build()
5087 .await
5088 .unwrap();
5089
5090 let buffer = create_test_ipc_data();
5092 let mut create_request = CreateTableRequest::new();
5093 create_request.id = Some(vec!["table1".to_string()]);
5094 dir_namespace
5095 .create_table(create_request, Bytes::from(buffer))
5096 .await
5097 .unwrap();
5098
5099 let mut request = ListTablesRequest::new();
5101 request.id = Some(vec![]);
5102 let response = dir_namespace.list_tables(request).await.unwrap();
5103 assert_eq!(response.tables.len(), 1);
5104 assert_eq!(response.tables[0], "table1");
5105 }
5106
5107 #[rstest]
5108 #[case::with_optimization(true)]
5109 #[case::without_optimization(false)]
5110 #[tokio::test]
5111 async fn test_manifest_only_mode(#[case] inline_optimization: bool) {
5112 let temp_dir = TempStdDir::default();
5113 let temp_path = temp_dir.to_str().unwrap();
5114
5115 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5117 .manifest_enabled(true)
5118 .dir_listing_enabled(false)
5119 .inline_optimization_enabled(inline_optimization)
5120 .build()
5121 .await
5122 .unwrap();
5123
5124 let buffer = create_test_ipc_data();
5126 let mut create_request = CreateTableRequest::new();
5127 create_request.id = Some(vec!["test_table".to_string()]);
5128 dir_namespace
5129 .create_table(create_request, Bytes::from(buffer))
5130 .await
5131 .unwrap();
5132
5133 let mut request = ListTablesRequest::new();
5135 request.id = Some(vec![]);
5136 let response = dir_namespace.list_tables(request).await.unwrap();
5137 assert_eq!(response.tables.len(), 1);
5138 assert_eq!(response.tables[0], "test_table");
5139 }
5140
5141 #[rstest]
5142 #[case::with_optimization(true)]
5143 #[case::without_optimization(false)]
5144 #[tokio::test]
5145 async fn test_drop_nonexistent_table(#[case] inline_optimization: bool) {
5146 let temp_dir = TempStdDir::default();
5147 let temp_path = temp_dir.to_str().unwrap();
5148
5149 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5150 .inline_optimization_enabled(inline_optimization)
5151 .build()
5152 .await
5153 .unwrap();
5154
5155 let mut drop_request = DropTableRequest::new();
5157 drop_request.id = Some(vec!["nonexistent".to_string()]);
5158 let result = dir_namespace.drop_table(drop_request).await;
5159 assert!(result.is_err());
5160 }
5161
5162 #[rstest]
5163 #[case::with_optimization(true)]
5164 #[case::without_optimization(false)]
5165 #[tokio::test]
5166 async fn test_create_duplicate_table_fails(#[case] inline_optimization: bool) {
5167 let temp_dir = TempStdDir::default();
5168 let temp_path = temp_dir.to_str().unwrap();
5169
5170 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5171 .inline_optimization_enabled(inline_optimization)
5172 .build()
5173 .await
5174 .unwrap();
5175
5176 let buffer = create_test_ipc_data();
5178 let mut create_request = CreateTableRequest::new();
5179 create_request.id = Some(vec!["test_table".to_string()]);
5180 dir_namespace
5181 .create_table(create_request, Bytes::from(buffer.clone()))
5182 .await
5183 .unwrap();
5184
5185 let mut create_request = CreateTableRequest::new();
5187 create_request.id = Some(vec!["test_table".to_string()]);
5188 let result = dir_namespace
5189 .create_table(create_request, Bytes::from(buffer))
5190 .await;
5191 assert!(result.is_err());
5192 }
5193
5194 #[rstest]
5195 #[case::with_optimization(true)]
5196 #[case::without_optimization(false)]
5197 #[tokio::test]
5198 async fn test_create_child_namespace(#[case] inline_optimization: bool) {
5199 use lance_namespace::models::{
5200 CreateNamespaceRequest, ListNamespacesRequest, NamespaceExistsRequest,
5201 };
5202
5203 let temp_dir = TempStdDir::default();
5204 let temp_path = temp_dir.to_str().unwrap();
5205
5206 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5207 .inline_optimization_enabled(inline_optimization)
5208 .build()
5209 .await
5210 .unwrap();
5211
5212 let mut create_req = CreateNamespaceRequest::new();
5214 create_req.id = Some(vec!["ns1".to_string()]);
5215 let result = dir_namespace.create_namespace(create_req).await;
5216 assert!(
5217 result.is_ok(),
5218 "Failed to create child namespace: {:?}",
5219 result.err()
5220 );
5221
5222 let exists_req = NamespaceExistsRequest {
5224 id: Some(vec!["ns1".to_string()]),
5225 ..Default::default()
5226 };
5227 let result = dir_namespace.namespace_exists(exists_req).await;
5228 assert!(result.is_ok(), "Namespace should exist");
5229
5230 let list_req = ListNamespacesRequest {
5232 id: Some(vec![]),
5233 page_token: None,
5234 limit: None,
5235 ..Default::default()
5236 };
5237 let result = dir_namespace.list_namespaces(list_req).await;
5238 assert!(result.is_ok());
5239 let namespaces = result.unwrap();
5240 assert_eq!(namespaces.namespaces.len(), 1);
5241 assert_eq!(namespaces.namespaces[0], "ns1");
5242 }
5243
5244 #[rstest]
5245 #[case::with_optimization(true)]
5246 #[case::without_optimization(false)]
5247 #[tokio::test]
5248 async fn test_create_nested_namespace(#[case] inline_optimization: bool) {
5249 use lance_namespace::models::{
5250 CreateNamespaceRequest, ListNamespacesRequest, NamespaceExistsRequest,
5251 };
5252
5253 let temp_dir = TempStdDir::default();
5254 let temp_path = temp_dir.to_str().unwrap();
5255
5256 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5257 .inline_optimization_enabled(inline_optimization)
5258 .build()
5259 .await
5260 .unwrap();
5261
5262 let mut create_req = CreateNamespaceRequest::new();
5264 create_req.id = Some(vec!["parent".to_string()]);
5265 dir_namespace.create_namespace(create_req).await.unwrap();
5266
5267 let mut create_req = CreateNamespaceRequest::new();
5269 create_req.id = Some(vec!["parent".to_string(), "child".to_string()]);
5270 let result = dir_namespace.create_namespace(create_req).await;
5271 assert!(
5272 result.is_ok(),
5273 "Failed to create nested namespace: {:?}",
5274 result.err()
5275 );
5276
5277 let exists_req = NamespaceExistsRequest {
5279 id: Some(vec!["parent".to_string(), "child".to_string()]),
5280 ..Default::default()
5281 };
5282 let result = dir_namespace.namespace_exists(exists_req).await;
5283 assert!(result.is_ok(), "Nested namespace should exist");
5284
5285 let list_req = ListNamespacesRequest {
5287 id: Some(vec!["parent".to_string()]),
5288 page_token: None,
5289 limit: None,
5290 ..Default::default()
5291 };
5292 let result = dir_namespace.list_namespaces(list_req).await;
5293 assert!(result.is_ok());
5294 let namespaces = result.unwrap();
5295 assert_eq!(namespaces.namespaces.len(), 1);
5296 assert_eq!(namespaces.namespaces[0], "child");
5297 }
5298
5299 #[rstest]
5300 #[case::with_optimization(true)]
5301 #[case::without_optimization(false)]
5302 #[tokio::test]
5303 async fn test_create_namespace_without_parent_fails(#[case] inline_optimization: bool) {
5304 use lance_namespace::models::CreateNamespaceRequest;
5305
5306 let temp_dir = TempStdDir::default();
5307 let temp_path = temp_dir.to_str().unwrap();
5308
5309 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5310 .inline_optimization_enabled(inline_optimization)
5311 .build()
5312 .await
5313 .unwrap();
5314
5315 let mut create_req = CreateNamespaceRequest::new();
5317 create_req.id = Some(vec!["nonexistent_parent".to_string(), "child".to_string()]);
5318 let result = dir_namespace.create_namespace(create_req).await;
5319 assert!(result.is_err(), "Should fail when parent doesn't exist");
5320 }
5321
5322 #[rstest]
5323 #[case::with_optimization(true)]
5324 #[case::without_optimization(false)]
5325 #[tokio::test]
5326 async fn test_drop_child_namespace(#[case] inline_optimization: bool) {
5327 use lance_namespace::models::{
5328 CreateNamespaceRequest, DropNamespaceRequest, NamespaceExistsRequest,
5329 };
5330
5331 let temp_dir = TempStdDir::default();
5332 let temp_path = temp_dir.to_str().unwrap();
5333
5334 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5335 .inline_optimization_enabled(inline_optimization)
5336 .build()
5337 .await
5338 .unwrap();
5339
5340 let mut create_req = CreateNamespaceRequest::new();
5342 create_req.id = Some(vec!["ns1".to_string()]);
5343 dir_namespace.create_namespace(create_req).await.unwrap();
5344
5345 let mut drop_req = DropNamespaceRequest::new();
5347 drop_req.id = Some(vec!["ns1".to_string()]);
5348 let result = dir_namespace.drop_namespace(drop_req).await;
5349 assert!(
5350 result.is_ok(),
5351 "Failed to drop namespace: {:?}",
5352 result.err()
5353 );
5354
5355 let exists_req = NamespaceExistsRequest {
5357 id: Some(vec!["ns1".to_string()]),
5358 ..Default::default()
5359 };
5360 let result = dir_namespace.namespace_exists(exists_req).await;
5361 assert!(result.is_err(), "Namespace should not exist after drop");
5362 }
5363
5364 #[rstest]
5365 #[case::with_optimization(true)]
5366 #[case::without_optimization(false)]
5367 #[tokio::test]
5368 async fn test_drop_namespace_with_children_fails(#[case] inline_optimization: bool) {
5369 use lance_namespace::models::{CreateNamespaceRequest, DropNamespaceRequest};
5370
5371 let temp_dir = TempStdDir::default();
5372 let temp_path = temp_dir.to_str().unwrap();
5373
5374 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5375 .inline_optimization_enabled(inline_optimization)
5376 .build()
5377 .await
5378 .unwrap();
5379
5380 let mut create_req = CreateNamespaceRequest::new();
5382 create_req.id = Some(vec!["parent".to_string()]);
5383 dir_namespace.create_namespace(create_req).await.unwrap();
5384
5385 let mut create_req = CreateNamespaceRequest::new();
5386 create_req.id = Some(vec!["parent".to_string(), "child".to_string()]);
5387 dir_namespace.create_namespace(create_req).await.unwrap();
5388
5389 let mut drop_req = DropNamespaceRequest::new();
5391 drop_req.id = Some(vec!["parent".to_string()]);
5392 let result = dir_namespace.drop_namespace(drop_req).await;
5393 assert!(result.is_err(), "Should fail when namespace has children");
5394 }
5395
5396 #[rstest]
5397 #[case::with_optimization(true)]
5398 #[case::without_optimization(false)]
5399 #[tokio::test]
5400 async fn test_create_table_in_child_namespace(#[case] inline_optimization: bool) {
5401 use lance_namespace::models::{
5402 CreateNamespaceRequest, CreateTableRequest, ListTablesRequest,
5403 };
5404
5405 let temp_dir = TempStdDir::default();
5406 let temp_path = temp_dir.to_str().unwrap();
5407
5408 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5409 .inline_optimization_enabled(inline_optimization)
5410 .build()
5411 .await
5412 .unwrap();
5413
5414 let mut create_ns_req = CreateNamespaceRequest::new();
5416 create_ns_req.id = Some(vec!["ns1".to_string()]);
5417 dir_namespace.create_namespace(create_ns_req).await.unwrap();
5418
5419 let buffer = create_test_ipc_data();
5421 let mut create_table_req = CreateTableRequest::new();
5422 create_table_req.id = Some(vec!["ns1".to_string(), "table1".to_string()]);
5423 let result = dir_namespace
5424 .create_table(create_table_req, Bytes::from(buffer))
5425 .await;
5426 assert!(
5427 result.is_ok(),
5428 "Failed to create table in child namespace: {:?}",
5429 result.err()
5430 );
5431
5432 let list_req = ListTablesRequest {
5434 id: Some(vec!["ns1".to_string()]),
5435 page_token: None,
5436 limit: None,
5437 ..Default::default()
5438 };
5439 let result = dir_namespace.list_tables(list_req).await;
5440 assert!(result.is_ok());
5441 let tables = result.unwrap();
5442 assert_eq!(tables.tables.len(), 1);
5443 assert_eq!(tables.tables[0], "table1");
5444 }
5445
5446 #[rstest]
5447 #[case::with_optimization(true)]
5448 #[case::without_optimization(false)]
5449 #[tokio::test]
5450 async fn test_describe_child_namespace(#[case] inline_optimization: bool) {
5451 use lance_namespace::models::{CreateNamespaceRequest, DescribeNamespaceRequest};
5452
5453 let temp_dir = TempStdDir::default();
5454 let temp_path = temp_dir.to_str().unwrap();
5455
5456 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5457 .inline_optimization_enabled(inline_optimization)
5458 .build()
5459 .await
5460 .unwrap();
5461
5462 let mut properties = std::collections::HashMap::new();
5464 properties.insert("key1".to_string(), "value1".to_string());
5465
5466 let mut create_req = CreateNamespaceRequest::new();
5467 create_req.id = Some(vec!["ns1".to_string()]);
5468 create_req.properties = Some(properties.clone());
5469 dir_namespace.create_namespace(create_req).await.unwrap();
5470
5471 let describe_req = DescribeNamespaceRequest {
5473 id: Some(vec!["ns1".to_string()]),
5474 ..Default::default()
5475 };
5476 let result = dir_namespace.describe_namespace(describe_req).await;
5477 assert!(
5478 result.is_ok(),
5479 "Failed to describe namespace: {:?}",
5480 result.err()
5481 );
5482 let response = result.unwrap();
5483 assert!(response.properties.is_some());
5484 assert_eq!(
5485 response.properties.unwrap().get("key1"),
5486 Some(&"value1".to_string())
5487 );
5488 }
5489
5490 #[rstest]
5491 #[case::with_optimization(true)]
5492 #[case::without_optimization(false)]
5493 #[tokio::test]
5494 async fn test_concurrent_create_and_drop_single_instance(#[case] inline_optimization: bool) {
5495 use futures::future::join_all;
5496 use std::sync::Arc;
5497
5498 let temp_dir = TempStdDir::default();
5499 let temp_path = temp_dir.to_str().unwrap();
5500
5501 let dir_namespace = Arc::new(
5502 DirectoryNamespaceBuilder::new(temp_path)
5503 .inline_optimization_enabled(inline_optimization)
5504 .build()
5505 .await
5506 .unwrap(),
5507 );
5508
5509 let mut create_ns_request = CreateNamespaceRequest::new();
5512 create_ns_request.id = Some(vec!["test_ns".to_string()]);
5513 dir_namespace
5514 .create_namespace(create_ns_request)
5515 .await
5516 .unwrap();
5517
5518 let num_tables = 10;
5519 let mut handles = Vec::new();
5520
5521 for i in 0..num_tables {
5522 let ns = dir_namespace.clone();
5523 let handle = async move {
5524 let table_name = format!("concurrent_table_{}", i);
5525 let table_id = vec!["test_ns".to_string(), table_name.clone()];
5526 let buffer = create_test_ipc_data();
5527
5528 let mut create_request = CreateTableRequest::new();
5530 create_request.id = Some(table_id.clone());
5531 ns.create_table(create_request, Bytes::from(buffer))
5532 .await
5533 .unwrap_or_else(|e| panic!("Failed to create table {}: {}", table_name, e));
5534
5535 let mut drop_request = DropTableRequest::new();
5537 drop_request.id = Some(table_id);
5538 ns.drop_table(drop_request)
5539 .await
5540 .unwrap_or_else(|e| panic!("Failed to drop table {}: {}", table_name, e));
5541
5542 Ok::<_, lance_core::Error>(())
5543 };
5544 handles.push(handle);
5545 }
5546
5547 let results = join_all(handles).await;
5548 for result in results {
5549 assert!(result.is_ok(), "All concurrent operations should succeed");
5550 }
5551
5552 let mut request = ListTablesRequest::new();
5554 request.id = Some(vec!["test_ns".to_string()]);
5555 let response = dir_namespace.list_tables(request).await.unwrap();
5556 assert_eq!(response.tables.len(), 0, "All tables should be dropped");
5557 }
5558
5559 #[rstest]
5560 #[case::with_optimization(true)]
5561 #[case::without_optimization(false)]
5562 #[tokio::test]
5563 async fn test_concurrent_create_and_drop_multiple_instances(#[case] inline_optimization: bool) {
5564 use futures::future::join_all;
5565
5566 let temp_dir = TempStdDir::default();
5567 let temp_path = temp_dir.to_str().unwrap().to_string();
5568
5569 let init_ns = DirectoryNamespaceBuilder::new(&temp_path)
5572 .inline_optimization_enabled(inline_optimization)
5573 .build()
5574 .await
5575 .unwrap();
5576 let mut create_ns_request = CreateNamespaceRequest::new();
5577 create_ns_request.id = Some(vec!["test_ns".to_string()]);
5578 init_ns.create_namespace(create_ns_request).await.unwrap();
5579
5580 let num_tables = 10;
5581 let mut handles = Vec::new();
5582
5583 for i in 0..num_tables {
5584 let path = temp_path.clone();
5585 let handle = async move {
5586 let ns = DirectoryNamespaceBuilder::new(&path)
5588 .inline_optimization_enabled(inline_optimization)
5589 .build()
5590 .await
5591 .unwrap();
5592
5593 let table_name = format!("multi_ns_table_{}", i);
5594 let table_id = vec!["test_ns".to_string(), table_name.clone()];
5595 let buffer = create_test_ipc_data();
5596
5597 let mut create_request = CreateTableRequest::new();
5599 create_request.id = Some(table_id.clone());
5600 ns.create_table(create_request, Bytes::from(buffer))
5601 .await
5602 .unwrap_or_else(|e| panic!("Failed to create table {}: {}", table_name, e));
5603
5604 let mut drop_request = DropTableRequest::new();
5606 drop_request.id = Some(table_id);
5607 ns.drop_table(drop_request)
5608 .await
5609 .unwrap_or_else(|e| panic!("Failed to drop table {}: {}", table_name, e));
5610
5611 Ok::<_, lance_core::Error>(())
5612 };
5613 handles.push(handle);
5614 }
5615
5616 let results = join_all(handles).await;
5617 for result in results {
5618 assert!(result.is_ok(), "All concurrent operations should succeed");
5619 }
5620
5621 let verify_ns = DirectoryNamespaceBuilder::new(&temp_path)
5623 .inline_optimization_enabled(inline_optimization)
5624 .build()
5625 .await
5626 .unwrap();
5627
5628 let mut request = ListTablesRequest::new();
5629 request.id = Some(vec!["test_ns".to_string()]);
5630 let response = verify_ns.list_tables(request).await.unwrap();
5631 assert_eq!(response.tables.len(), 0, "All tables should be dropped");
5632 }
5633
5634 #[rstest]
5635 #[case::with_optimization(true)]
5636 #[case::without_optimization(false)]
5637 #[tokio::test]
5638 async fn test_concurrent_create_then_drop_from_different_instance(
5639 #[case] inline_optimization: bool,
5640 ) {
5641 use futures::future::join_all;
5642
5643 let temp_dir = TempStdDir::default();
5644 let temp_path = temp_dir.to_str().unwrap().to_string();
5645
5646 let init_ns = DirectoryNamespaceBuilder::new(&temp_path)
5649 .inline_optimization_enabled(inline_optimization)
5650 .build()
5651 .await
5652 .unwrap();
5653 let mut create_ns_request = CreateNamespaceRequest::new();
5654 create_ns_request.id = Some(vec!["test_ns".to_string()]);
5655 init_ns.create_namespace(create_ns_request).await.unwrap();
5656
5657 let num_tables = 10;
5658
5659 let mut create_handles = Vec::new();
5661 for i in 0..num_tables {
5662 let path = temp_path.clone();
5663 let handle = async move {
5664 let ns = DirectoryNamespaceBuilder::new(&path)
5665 .inline_optimization_enabled(inline_optimization)
5666 .build()
5667 .await
5668 .unwrap();
5669
5670 let table_name = format!("cross_instance_table_{}", i);
5671 let table_id = vec!["test_ns".to_string(), table_name.clone()];
5672 let buffer = create_test_ipc_data();
5673
5674 let mut create_request = CreateTableRequest::new();
5675 create_request.id = Some(table_id);
5676 ns.create_table(create_request, Bytes::from(buffer))
5677 .await
5678 .unwrap_or_else(|e| panic!("Failed to create table {}: {}", table_name, e));
5679
5680 Ok::<_, lance_core::Error>(())
5681 };
5682 create_handles.push(handle);
5683 }
5684
5685 let create_results = join_all(create_handles).await;
5686 for result in create_results {
5687 assert!(result.is_ok(), "All create operations should succeed");
5688 }
5689
5690 let mut drop_handles = Vec::new();
5692 for i in 0..num_tables {
5693 let path = temp_path.clone();
5694 let handle = async move {
5695 let ns = DirectoryNamespaceBuilder::new(&path)
5696 .inline_optimization_enabled(inline_optimization)
5697 .build()
5698 .await
5699 .unwrap();
5700
5701 let table_name = format!("cross_instance_table_{}", i);
5702 let table_id = vec!["test_ns".to_string(), table_name.clone()];
5703
5704 let mut drop_request = DropTableRequest::new();
5705 drop_request.id = Some(table_id);
5706 ns.drop_table(drop_request)
5707 .await
5708 .unwrap_or_else(|e| panic!("Failed to drop table {}: {}", table_name, e));
5709
5710 Ok::<_, lance_core::Error>(())
5711 };
5712 drop_handles.push(handle);
5713 }
5714
5715 let drop_results = join_all(drop_handles).await;
5716 for result in drop_results {
5717 assert!(result.is_ok(), "All drop operations should succeed");
5718 }
5719
5720 let verify_ns = DirectoryNamespaceBuilder::new(&temp_path)
5722 .inline_optimization_enabled(inline_optimization)
5723 .build()
5724 .await
5725 .unwrap();
5726
5727 let mut request = ListTablesRequest::new();
5728 request.id = Some(vec!["test_ns".to_string()]);
5729 let response = verify_ns.list_tables(request).await.unwrap();
5730 assert_eq!(response.tables.len(), 0, "All tables should be dropped");
5731 }
5732
5733 #[test]
5734 fn test_construct_full_uri_with_cloud_urls() {
5735 let s3_result =
5737 ManifestNamespace::construct_full_uri("s3://bucket/path/subdir", "table.lance")
5738 .unwrap();
5739 assert_eq!(
5740 s3_result, "s3://bucket/path/subdir/table.lance",
5741 "S3 URL should correctly append table name to nested path"
5742 );
5743
5744 let az_result =
5746 ManifestNamespace::construct_full_uri("az://container/path/subdir", "table.lance")
5747 .unwrap();
5748 assert_eq!(
5749 az_result, "az://container/path/subdir/table.lance",
5750 "Azure URL should correctly append table name to nested path"
5751 );
5752
5753 let gs_result =
5755 ManifestNamespace::construct_full_uri("gs://bucket/path/subdir", "table.lance")
5756 .unwrap();
5757 assert_eq!(
5758 gs_result, "gs://bucket/path/subdir/table.lance",
5759 "GCS URL should correctly append table name to nested path"
5760 );
5761
5762 let deep_result =
5764 ManifestNamespace::construct_full_uri("s3://bucket/a/b/c/d", "my_table.lance").unwrap();
5765 assert_eq!(
5766 deep_result, "s3://bucket/a/b/c/d/my_table.lance",
5767 "Deeply nested path should work correctly"
5768 );
5769
5770 let shallow_result =
5772 ManifestNamespace::construct_full_uri("s3://bucket", "table.lance").unwrap();
5773 assert_eq!(
5774 shallow_result, "s3://bucket/table.lance",
5775 "Single-level nested path should work correctly"
5776 );
5777
5778 let trailing_slash_result =
5780 ManifestNamespace::construct_full_uri("s3://bucket/path/subdir/", "table.lance")
5781 .unwrap();
5782 assert_eq!(
5783 trailing_slash_result, "s3://bucket/path/subdir/table.lance",
5784 "URL with existing trailing slash should still work"
5785 );
5786
5787 let empty_query_result =
5790 ManifestNamespace::construct_full_uri("s3://bucket/path?", "table.lance").unwrap();
5791 assert_eq!(
5792 empty_query_result, "s3://bucket/path/table.lance",
5793 "URL with empty query string should not include trailing '?'"
5794 );
5795
5796 let query_param_result =
5799 ManifestNamespace::construct_full_uri("s3://bucket/path?param=value", "table.lance")
5800 .unwrap();
5801 assert_eq!(
5802 query_param_result, "s3://bucket/path/table.lance",
5803 "URL with query parameters should have them stripped"
5804 );
5805 }
5806
5807 #[test]
5808 fn test_construct_full_uri_with_dollar_sign() {
5809 let result =
5810 ManifestNamespace::construct_full_uri("/tmp/root", "hash_workspace$test_table")
5811 .unwrap();
5812
5813 assert!(
5814 result.ends_with("/tmp/root/hash_workspace$test_table"),
5815 "local file URI should preserve dollar signs without adding empty path segments: {}",
5816 result
5817 );
5818 assert!(
5819 !result.contains("//hash_workspace$test_table"),
5820 "local file URI should not add a double slash before table directory: {}",
5821 result
5822 );
5823 }
5824
5825 #[test]
5826 fn test_construct_full_uri_with_nested_relative_location() {
5827 let result =
5828 ManifestNamespace::construct_full_uri("/tmp/root", "workspace/physical_table.lance")
5829 .unwrap();
5830
5831 assert!(
5832 result.ends_with("/tmp/root/workspace/physical_table.lance"),
5833 "nested relative location should preserve path separators: {}",
5834 result
5835 );
5836 assert!(
5837 !result.contains("%2Fphysical_table.lance"),
5838 "nested relative location should not encode path separators: {}",
5839 result
5840 );
5841 }
5842
5843 #[tokio::test]
5850 async fn test_concurrent_create_table_no_duplicates() {
5851 let temp_dir = TempStdDir::default();
5852 let temp_path = temp_dir.to_str().unwrap();
5853
5854 let ns1 = DirectoryNamespaceBuilder::new(temp_path)
5857 .inline_optimization_enabled(false)
5858 .build()
5859 .await
5860 .unwrap();
5861 let ns2 = DirectoryNamespaceBuilder::new(temp_path)
5862 .inline_optimization_enabled(false)
5863 .build()
5864 .await
5865 .unwrap();
5866
5867 let buffer = create_test_ipc_data();
5868
5869 let mut req1 = CreateTableRequest::new();
5870 req1.id = Some(vec!["race_table".to_string()]);
5871 let mut req2 = CreateTableRequest::new();
5872 req2.id = Some(vec!["race_table".to_string()]);
5873
5874 let (result1, result2) = tokio::join!(
5876 ns1.create_table(req1, Bytes::from(buffer.clone())),
5877 ns2.create_table(req2, Bytes::from(buffer.clone())),
5878 );
5879
5880 let success_count = [&result1, &result2].iter().filter(|r| r.is_ok()).count();
5882 let failure_count = [&result1, &result2].iter().filter(|r| r.is_err()).count();
5883 assert_eq!(
5884 success_count, 1,
5885 "Exactly one create should succeed, got: result1={:?}, result2={:?}",
5886 result1, result2
5887 );
5888 assert_eq!(
5889 failure_count, 1,
5890 "Exactly one create should fail, got: result1={:?}, result2={:?}",
5891 result1, result2
5892 );
5893
5894 let ns_check = DirectoryNamespaceBuilder::new(temp_path)
5896 .inline_optimization_enabled(false)
5897 .build()
5898 .await
5899 .unwrap();
5900 let mut list_request = ListTablesRequest::new();
5901 list_request.id = Some(vec![]);
5902 let response = ns_check.list_tables(list_request).await.unwrap();
5903 assert_eq!(
5904 response.tables.len(),
5905 1,
5906 "Should have exactly 1 table, found: {:?}",
5907 response.tables
5908 );
5909 assert_eq!(response.tables[0], "race_table");
5910
5911 let mut describe_request = DescribeTableRequest::new();
5913 describe_request.id = Some(vec!["race_table".to_string()]);
5914 let describe_result = ns_check.describe_table(describe_request).await;
5915 assert!(
5916 describe_result.is_ok(),
5917 "describe_table should not fail with duplicate entries: {:?}",
5918 describe_result
5919 );
5920 }
5921
5922 fn names(v: &[&str]) -> Vec<String> {
5925 v.iter().map(|s| s.to_string()).collect()
5926 }
5927
5928 #[test]
5929 fn test_apply_pagination_no_token_no_limit() {
5930 let mut n = names(&["b", "a", "c"]);
5931 let next = ManifestNamespace::apply_pagination(&mut n, None, None);
5932 assert_eq!(n, names(&["a", "b", "c"]));
5933 assert_eq!(next, None);
5934 }
5935
5936 #[test]
5937 fn test_apply_pagination_limit_truncates_and_returns_token() {
5938 let mut n = names(&["c", "a", "b"]);
5939 let next = ManifestNamespace::apply_pagination(&mut n, None, Some(2));
5940 assert_eq!(n, names(&["a", "b"]));
5941 assert_eq!(next, Some("b".to_string()));
5942 }
5943
5944 #[test]
5945 fn test_apply_pagination_limit_zero_returns_empty_no_token() {
5946 let mut n = names(&["a", "b", "c"]);
5947 let next = ManifestNamespace::apply_pagination(&mut n, None, Some(0));
5948 assert!(n.is_empty());
5949 assert_eq!(next, None);
5950 }
5951
5952 #[test]
5953 fn test_apply_pagination_page_token_in_list() {
5954 let mut n = names(&["a", "b", "c", "d"]);
5956 let next = ManifestNamespace::apply_pagination(&mut n, Some("b".to_string()), None);
5957 assert_eq!(n, names(&["c", "d"]));
5958 assert_eq!(next, None);
5959 }
5960
5961 #[test]
5962 fn test_apply_pagination_page_token_past_all_items() {
5963 let mut n = names(&["a", "b", "c"]);
5964 let next = ManifestNamespace::apply_pagination(&mut n, Some("z".to_string()), None);
5965 assert!(n.is_empty());
5966 assert_eq!(next, None);
5967 }
5968
5969 #[test]
5970 fn test_apply_pagination_token_and_limit_combined() {
5971 let mut n = names(&["a", "b", "c", "d", "e"]);
5972 let next = ManifestNamespace::apply_pagination(&mut n, Some("b".to_string()), Some(2));
5973 assert_eq!(n, names(&["c", "d"]));
5974 assert_eq!(next, Some("d".to_string()));
5975 }
5976
5977 #[rstest]
5978 #[case::with_optimization(true)]
5979 #[case::without_optimization(false)]
5980 #[tokio::test]
5981 async fn test_alter_table_add_columns(#[case] inline_optimization: bool) {
5982 use lance_namespace::models::{
5983 AddColumnsEntry, AlterTableAddColumnsRequest, DescribeTableRequest,
5984 };
5985
5986 let temp_dir = TempStdDir::default();
5987 let temp_path = temp_dir.to_str().unwrap();
5988
5989 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
5990 .inline_optimization_enabled(inline_optimization)
5991 .build()
5992 .await
5993 .unwrap();
5994
5995 let buffer = create_test_ipc_data();
5997 let mut create_request = CreateTableRequest::new();
5998 create_request.id = Some(vec!["test_table".to_string()]);
5999 dir_namespace
6000 .create_table(create_request, Bytes::from(buffer))
6001 .await
6002 .unwrap();
6003
6004 let mut new_col = AddColumnsEntry::new("doubled_id".to_string());
6006 new_col.expression = Some(Some("id * 2".to_string()));
6007 let mut add_request = AlterTableAddColumnsRequest::new(vec![new_col]);
6008 add_request.id = Some(vec!["test_table".to_string()]);
6009
6010 let response = dir_namespace
6011 .alter_table_add_columns(add_request)
6012 .await
6013 .unwrap();
6014 assert!(response.version > 1);
6016
6017 let mut describe_request = DescribeTableRequest::new();
6019 describe_request.id = Some(vec!["test_table".to_string()]);
6020 describe_request.load_detailed_metadata = Some(true);
6021 let describe_response = dir_namespace
6022 .describe_table(describe_request)
6023 .await
6024 .unwrap();
6025 assert!(describe_response.schema.is_some());
6026
6027 let schema = describe_response.schema.unwrap();
6028 let field_names: Vec<&str> = schema.fields.iter().map(|f| f.name.as_str()).collect();
6029 assert!(
6030 field_names.contains(&"doubled_id"),
6031 "Column 'doubled_id' should exist after add_columns, got: {:?}",
6032 field_names
6033 );
6034 }
6035
6036 #[rstest]
6037 #[case::with_optimization(true)]
6038 #[case::without_optimization(false)]
6039 #[tokio::test]
6040 async fn test_alter_table_add_columns_missing_id(#[case] inline_optimization: bool) {
6041 use lance_namespace::models::{AddColumnsEntry, AlterTableAddColumnsRequest};
6042
6043 let temp_dir = TempStdDir::default();
6044 let temp_path = temp_dir.to_str().unwrap();
6045
6046 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6047 .inline_optimization_enabled(inline_optimization)
6048 .build()
6049 .await
6050 .unwrap();
6051
6052 let new_col = AddColumnsEntry::new("col".to_string());
6054 let request = AlterTableAddColumnsRequest::new(vec![new_col]);
6055 let result = dir_namespace.alter_table_add_columns(request).await;
6056 assert!(result.is_err(), "Should fail when table ID is missing");
6057 }
6058
6059 #[rstest]
6060 #[case::with_optimization(true)]
6061 #[case::without_optimization(false)]
6062 #[tokio::test]
6063 async fn test_alter_table_add_columns_nonexistent_table(#[case] inline_optimization: bool) {
6064 use lance_namespace::models::{AddColumnsEntry, AlterTableAddColumnsRequest};
6065
6066 let temp_dir = TempStdDir::default();
6067 let temp_path = temp_dir.to_str().unwrap();
6068
6069 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6070 .inline_optimization_enabled(inline_optimization)
6071 .build()
6072 .await
6073 .unwrap();
6074
6075 let new_col = AddColumnsEntry::new("col".to_string());
6077 let mut request = AlterTableAddColumnsRequest::new(vec![new_col]);
6078 request.id = Some(vec!["nonexistent".to_string()]);
6079 let result = dir_namespace.alter_table_add_columns(request).await;
6080 assert!(result.is_err(), "Should fail when table does not exist");
6081 }
6082
6083 #[rstest]
6084 #[case::with_optimization(true)]
6085 #[case::without_optimization(false)]
6086 #[tokio::test]
6087 async fn test_alter_table_alter_columns_rename(#[case] inline_optimization: bool) {
6088 use lance_namespace::models::{
6089 AlterColumnsEntry, AlterTableAlterColumnsRequest, DescribeTableRequest,
6090 };
6091
6092 let temp_dir = TempStdDir::default();
6093 let temp_path = temp_dir.to_str().unwrap();
6094
6095 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6096 .inline_optimization_enabled(inline_optimization)
6097 .build()
6098 .await
6099 .unwrap();
6100
6101 let buffer = create_test_ipc_data();
6103 let mut create_request = CreateTableRequest::new();
6104 create_request.id = Some(vec!["test_table".to_string()]);
6105 dir_namespace
6106 .create_table(create_request, Bytes::from(buffer))
6107 .await
6108 .unwrap();
6109
6110 let mut entry = AlterColumnsEntry::new("name".to_string());
6112 entry.rename = Some(Some("full_name".to_string()));
6113 let mut alter_request = AlterTableAlterColumnsRequest::new(vec![entry]);
6114 alter_request.id = Some(vec!["test_table".to_string()]);
6115
6116 let response = dir_namespace
6117 .alter_table_alter_columns(alter_request)
6118 .await
6119 .unwrap();
6120 assert!(response.version > 1);
6121
6122 let mut describe_request = DescribeTableRequest::new();
6124 describe_request.id = Some(vec!["test_table".to_string()]);
6125 describe_request.load_detailed_metadata = Some(true);
6126 let describe_response = dir_namespace
6127 .describe_table(describe_request)
6128 .await
6129 .unwrap();
6130 assert!(describe_response.schema.is_some());
6131
6132 let schema = describe_response.schema.unwrap();
6133 let field_names: Vec<&str> = schema.fields.iter().map(|f| f.name.as_str()).collect();
6134 assert!(
6135 field_names.contains(&"full_name"),
6136 "Column should be renamed to 'full_name', got: {:?}",
6137 field_names
6138 );
6139 assert!(
6140 !field_names.contains(&"name"),
6141 "Old column name 'name' should no longer exist, got: {:?}",
6142 field_names
6143 );
6144 }
6145
6146 #[rstest]
6147 #[case::with_optimization(true)]
6148 #[case::without_optimization(false)]
6149 #[tokio::test]
6150 async fn test_alter_table_alter_columns_missing_id(#[case] inline_optimization: bool) {
6151 use lance_namespace::models::{AlterColumnsEntry, AlterTableAlterColumnsRequest};
6152
6153 let temp_dir = TempStdDir::default();
6154 let temp_path = temp_dir.to_str().unwrap();
6155
6156 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6157 .inline_optimization_enabled(inline_optimization)
6158 .build()
6159 .await
6160 .unwrap();
6161
6162 let entry = AlterColumnsEntry::new("name".to_string());
6163 let request = AlterTableAlterColumnsRequest::new(vec![entry]);
6164 let result = dir_namespace.alter_table_alter_columns(request).await;
6165 assert!(result.is_err(), "Should fail when table ID is missing");
6166 }
6167
6168 #[rstest]
6169 #[case::with_optimization(true)]
6170 #[case::without_optimization(false)]
6171 #[tokio::test]
6172 async fn test_alter_table_drop_columns(#[case] inline_optimization: bool) {
6173 use lance_namespace::models::{AlterTableDropColumnsRequest, DescribeTableRequest};
6174
6175 let temp_dir = TempStdDir::default();
6176 let temp_path = temp_dir.to_str().unwrap();
6177
6178 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6179 .inline_optimization_enabled(inline_optimization)
6180 .build()
6181 .await
6182 .unwrap();
6183
6184 let buffer = create_test_ipc_data();
6186 let mut create_request = CreateTableRequest::new();
6187 create_request.id = Some(vec!["test_table".to_string()]);
6188 dir_namespace
6189 .create_table(create_request, Bytes::from(buffer))
6190 .await
6191 .unwrap();
6192
6193 let mut drop_request = AlterTableDropColumnsRequest::new(vec!["name".to_string()]);
6195 drop_request.id = Some(vec!["test_table".to_string()]);
6196
6197 let response = dir_namespace
6198 .alter_table_drop_columns(drop_request)
6199 .await
6200 .unwrap();
6201 assert!(response.version > 1);
6202
6203 let mut describe_request = DescribeTableRequest::new();
6205 describe_request.id = Some(vec!["test_table".to_string()]);
6206 describe_request.load_detailed_metadata = Some(true);
6207 let describe_response = dir_namespace
6208 .describe_table(describe_request)
6209 .await
6210 .unwrap();
6211 assert!(describe_response.schema.is_some());
6212
6213 let schema = describe_response.schema.unwrap();
6214 let field_names: Vec<&str> = schema.fields.iter().map(|f| f.name.as_str()).collect();
6215 assert!(
6216 !field_names.contains(&"name"),
6217 "Column 'name' should have been dropped, got: {:?}",
6218 field_names
6219 );
6220 assert!(
6221 field_names.contains(&"id"),
6222 "Column 'id' should still exist, got: {:?}",
6223 field_names
6224 );
6225 }
6226
6227 #[rstest]
6228 #[case::with_optimization(true)]
6229 #[case::without_optimization(false)]
6230 #[tokio::test]
6231 async fn test_alter_table_drop_columns_missing_id(#[case] inline_optimization: bool) {
6232 use lance_namespace::models::AlterTableDropColumnsRequest;
6233
6234 let temp_dir = TempStdDir::default();
6235 let temp_path = temp_dir.to_str().unwrap();
6236
6237 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6238 .inline_optimization_enabled(inline_optimization)
6239 .build()
6240 .await
6241 .unwrap();
6242
6243 let request = AlterTableDropColumnsRequest::new(vec!["col".to_string()]);
6244 let result = dir_namespace.alter_table_drop_columns(request).await;
6245 assert!(result.is_err(), "Should fail when table ID is missing");
6246 }
6247
6248 #[rstest]
6249 #[case::with_optimization(true)]
6250 #[case::without_optimization(false)]
6251 #[tokio::test]
6252 async fn test_alter_table_drop_columns_nonexistent_table(#[case] inline_optimization: bool) {
6253 use lance_namespace::models::AlterTableDropColumnsRequest;
6254
6255 let temp_dir = TempStdDir::default();
6256 let temp_path = temp_dir.to_str().unwrap();
6257
6258 let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
6259 .inline_optimization_enabled(inline_optimization)
6260 .build()
6261 .await
6262 .unwrap();
6263
6264 let mut request = AlterTableDropColumnsRequest::new(vec!["col".to_string()]);
6265 request.id = Some(vec!["nonexistent".to_string()]);
6266 let result = dir_namespace.alter_table_drop_columns(request).await;
6267 assert!(result.is_err(), "Should fail when table does not exist");
6268 }
6269}