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