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