Skip to main content

lance_namespace_impls/dir/
manifest.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Manifest-based namespace implementation
5//!
6//! This module provides a namespace implementation that uses a manifest table
7//! to track tables and nested namespaces.
8
9use 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 = "$";
76/// Bounded concurrency for per-table `_versions/` probes when filtering declared tables.
77/// Higher values reduce latency but increase burst load against the object store.
78pub(crate) const DECLARED_FILTER_CONCURRENCY: usize = 16;
79
80// Index names for the __manifest table
81/// BTREE index on the object_id column for fast lookups
82const OBJECT_ID_INDEX_NAME: &str = "object_id_btree";
83/// Bitmap index on the object_type column for filtering by type
84const OBJECT_TYPE_INDEX_NAME: &str = "object_type_bitmap";
85/// LabelList index on the base_objects column for view dependencies
86const BASE_OBJECTS_INDEX_NAME: &str = "base_objects_label_list";
87// Each retry reloads and rewrites the full manifest. Match the regular Lance
88// commit retry budget so multi-process namespace writes can make progress.
89const DEFAULT_MANIFEST_REWRITE_COMMIT_RETRIES: u32 = 20;
90const MANIFEST_INDEX_BATCH_SIZE: usize = 8192;
91
92/// Object types that can be stored in the manifest
93#[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/// Information about a table stored in the manifest
157#[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/// An entry to be inserted into the manifest table.
166///
167/// This struct makes the meaning of each field explicit, replacing the
168/// previous tuple-based API `(String, ObjectType, Option<String>, Option<String>)`.
169#[derive(Debug, Clone)]
170pub struct ManifestEntry {
171    /// The unique object identifier (e.g., table name or version object_id)
172    pub object_id: String,
173    /// The type of the object (Namespace or Table)
174    pub object_type: ObjectType,
175    /// The storage location (e.g., directory name for tables)
176    pub location: Option<String>,
177    /// Additional metadata serialized as JSON
178    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
338/// How to resolve a storage commit conflict (or an ambiguous commit error that did
339/// not land) against the latest catalog state, without re-staging the full rewrite.
340enum ConflictResolution<O> {
341    /// Re-read the latest manifest and re-apply the mutation (upserts, version-range
342    /// deletes). The staged data/index files are discarded and a new rewrite is attempted.
343    Retry,
344    /// Creating these object ids with fail-on-conflict semantics. If any of them now
345    /// exists in the latest manifest, the create lost the race and must fail with a
346    /// concurrent-modification error; otherwise retry the rewrite.
347    FailIfExists(Vec<String>),
348    /// Deleting `object_id`. If it is already absent from the latest manifest the delete
349    /// has effectively happened, so return `output` as success; otherwise retry.
350    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    /// Declares how a storage commit conflict should be resolved against the latest
372    /// committed catalog state. Defaults to re-reading and re-applying.
373    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            // Fail-on-conflict create: a concurrent writer may have created one of these
510            // ids. Re-applying would still fail, so check directly instead of re-staging.
511            WhenMatched::Fail => ConflictResolution::FailIfExists(
512                self.entries.iter().map(|e| e.object_id.clone()).collect(),
513            ),
514            // Metadata upsert is last-writer-wins: re-read and re-apply.
515            _ => 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        // If a concurrent writer already removed the object, the delete is satisfied.
569        ConflictResolution::SucceedIfAbsent {
570            object_id: self.object_id.clone(),
571            output: (),
572        }
573    }
574}
575
576/// Information about a namespace stored in the manifest
577#[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/// A wrapper around a Dataset that provides concurrent access.
585///
586/// This can be cloned cheaply. It supports concurrent reads or exclusive writes.
587/// The manifest dataset uses contiguous attached versions and this module never
588/// runs old-version cleanup on it, allowing reads to check only the immediate
589/// successor manifest before deciding whether a reload is needed.
590#[derive(Debug, Clone)]
591pub struct DatasetConsistencyWrapper(Arc<RwLock<Dataset>>);
592
593impl DatasetConsistencyWrapper {
594    /// Create a new wrapper with the given dataset.
595    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    /// Get an immutable reference to the dataset.
608    /// Always reloads to ensure strong consistency.
609    pub async fn get(&self) -> Result<DatasetReadGuard<'_>> {
610        self.reload().await?;
611        let guard = DatasetReadGuard {
612            guard: self.0.read().await,
613        };
614        // Refuse manifests written with a reader feature flag this build does
615        // not understand instead of misreading them.
616        ensure_readable(guard.metadata())?;
617        Ok(guard)
618    }
619
620    /// Reload the dataset and return a reference.
621    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    /// Get a mutable reference to the dataset.
631    /// Always reloads to ensure strong consistency.
632    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    /// Provide a known latest version of the dataset.
643    ///
644    /// This is usually done after some write operation, which inherently will
645    /// have the latest version.
646    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    /// Reload the dataset to the latest version.
654    async fn reload(&self) -> Result<()> {
655        // First check if we need to reload (with read lock)
656        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        // The directory manifest table uses contiguous attached versions and
665        // does not run old-version cleanup, so the immediate successor probe is
666        // enough to detect changes without resolving or loading the latest
667        // manifest on every namespace read.
668        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 already up-to-date, return early
682        if !has_successor_version {
683            log::debug!("Already up-to-date for uri={}", dataset_uri);
684            return Ok(());
685        }
686
687        // Need to reload, acquire write lock
688        let mut write_guard = self.0.write().await;
689
690        // Double-check after acquiring write lock (someone else might have reloaded)
691        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
739/// Manifest-based namespace implementation
740///
741/// Uses a special `__manifest` Lance table to track tables and nested namespaces.
742pub 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    /// Whether directory listing is enabled in dual mode
750    /// If true, root namespace tables use {table_name}.lance naming
751    /// If false, they use namespace-prefixed names
752    dir_listing_enabled: bool,
753    /// Whether copy-on-write manifest rewrites should build replacement indices.
754    /// Defaults to true.
755    inline_optimization_enabled: bool,
756    /// Number of retries for commit operations on the manifest table.
757    /// If None, defaults to [`lance_table::io::commit::CommitConfig`] default (20).
758    commit_retries: Option<u32>,
759    /// Serialize manifest mutations within a single namespace instance so concurrent
760    /// create/drop calls do not compete with each other on the same in-memory snapshot.
761    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
778/// Convert a Lance commit error to an appropriate namespace error.
779///
780/// Maps lance commit errors to namespace errors:
781/// - `CommitConflict`: version collision retries exhausted -> Throttling (safe to retry)
782/// - `TooMuchWriteContention`: RetryableCommitConflict (semantic conflict) retries exhausted -> ConcurrentModification
783/// - `IncompatibleTransaction`: incompatible concurrent change -> ConcurrentModification
784/// - Errors containing "matched/duplicate/already exists": ConcurrentModification (from WhenMatched::Fail)
785/// - Other errors: IO error with the operation description
786fn convert_lance_commit_error(e: &LanceError, operation: &str, object_id: Option<&str>) -> Error {
787    match e {
788        // CommitConflict: version collision retries exhausted -> Throttling (safe to retry)
789        LanceError::CommitConflict { .. } => NamespaceError::Throttling {
790            message: format!("Too many concurrent writes, please retry later: {:?}", e),
791        }
792        .into(),
793        // TooMuchWriteContention: RetryableCommitConflict (semantic conflict) retries exhausted -> ConcurrentModification
794        // IncompatibleTransaction: incompatible concurrent change -> ConcurrentModification
795        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        // Other errors: check message for semantic conflicts (matched/duplicate from WhenMatched::Fail)
810        _ => {
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    /// Create a new ManifestNamespace from an existing DirectoryNamespace
838    #[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    /// Build object ID from namespace path and name
868    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    /// Parse object ID into namespace path and name
880    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    /// Split an object ID (vec of strings) into namespace and table name
895    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    /// Convert an ID (vec of strings) to an object_id string
907    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    /// Generate a new directory name in format: `<hash>_<object_id>`
916    /// The hash is used to (1) optimize object store throughput,
917    /// (2) have high enough entropy in a short period of time to prevent issues like
918    /// failed table creation, delete and create new table of the same name, etc.
919    /// The object_id is added after the hash to ensure
920    /// dir name uniqueness and make debugging easier.
921    pub fn generate_dir_name(object_id: &str) -> String {
922        // Generate a random number for uniqueness
923        let random_num: u64 = rand::random();
924
925        // Create hash from random number + object_id
926        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 as lowercase hex (8 characters - sufficient entropy for uniqueness)
932        format!("{:08x}_{}", (hash & 0xFFFFFFFF) as u32, object_id)
933    }
934
935    /// Construct a full URI from root and relative location
936    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        // Ensure the base URL has a trailing slash so that path segment mutation
940        // appends rather than replaces the last path segment.
941        // Without this fix, appending "table.lance" to "s3://bucket/path/subdir"
942        // would incorrectly produce "s3://bucket/path/table.lance" (missing subdir).
943        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        // Clear any query string to avoid trailing "?" in the URL.
963        // Use set_query(None) instead of set_query("") because the latter
964        // would still add a trailing '?' to the URL when serialized.
965        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    /// Get the manifest schema
1356    fn manifest_schema() -> Arc<ArrowSchema> {
1357        Arc::new(ArrowSchema::new(vec![
1358            // Set unenforced primary key on object_id for bloom filter conflict detection
1359            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    /// Get a scanner for the manifest dataset
1379    async fn manifest_scanner(&self) -> Result<Scanner> {
1380        let dataset_guard = self.manifest_dataset.get().await?;
1381        Ok(dataset_guard.scan())
1382    }
1383
1384    /// Helper to execute a scanner and collect results into a Vec
1385    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    /// Helper to get a string column from a record batch
1405    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    /// Delete the staged (uncommitted) data files and index directories for a rewrite.
1699    /// Only call this once the rewrite is known *not* to have landed (a put-if-not-exists
1700    /// conflict, or an ambiguous error whose target version does not reference our data
1701    /// file) — otherwise it would orphan files a committed manifest still references.
1702    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    /// Resolve the commit handler for the `__manifest` dataset's storage backend.
1752    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    /// Directly write the rewritten `__manifest` as a new version using the storage
1763    /// backend's atomic put-if-not-exists. The overwrite transaction is embedded inline
1764    /// (no separate transaction file) and the commit handler writes the version hint.
1765    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        // Commit through the dataset's own object store, not `self.object_store`: for
1782        // stores like `memory://` the namespace and the dataset can hold different
1783        // instances, and a commit written to the wrong one is invisible to reads.
1784        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    /// After an ambiguous commit error, determine whether our overwrite actually landed at
1805    /// `target_version`. A network failure can leave the manifest committed even though the
1806    /// client observed an error; in that case the committed version references one of our
1807    /// staged data files, and deleting them would corrupt the catalog.
1808    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    /// Resolve a storage commit conflict against the latest committed catalog state.
1826    /// Returns `Some(output)` when the mutation's intent is already satisfied (no retry
1827    /// needed), `Ok(None)` to retry the rewrite, or an error for a terminal conflict.
1828    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    /// Validate that this build can write the current `__manifest` before a
1859    /// mutating operation performs any side effect (e.g. writing table data), so
1860    /// a refused write leaves nothing orphaned behind. The eventual
1861    /// `rewrite_manifest` commit re-checks `ensure_writable` on each retry, so a
1862    /// concurrent upgrade in between is still caught.
1863    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            // Refuse to mutate a manifest written with a writer feature flag this
1888            // build does not understand.
1889            ensure_writable(dataset.metadata())?;
1890            // Staged files, indices, the commit, and cleanup must all use the dataset's
1891            // own object store (see `commit_manifest_overwrite`).
1892            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            // Pin both limits so the overwrite never splits into multiple fragments: the
1899            // replacement indices map each row to address `(0 << 32) | offset`, valid only
1900            // for a single fragment with id 0. The row count is bounded below u32::MAX by
1901            // `ManifestIndexAccumulator::next_row_id`.
1902            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            // Unique data files this attempt staged. Used to clean up orphans and to
1937            // attribute an ambiguous commit error back to us.
1938            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                    // The put may have landed even though the client saw an error (lost
1996                    // ack). Verify before deleting anything so we never orphan files that a
1997                    // committed manifest still references.
1998                    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    /// Check if the manifest contains an object with the given ID
2043    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        // Project no columns and enable row IDs for count_rows to work
2057        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    /// Query the manifest for a table with the given object ID
2075    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        // `_versions/` should only contain manifest files, so probing the first entry is enough
2173        // to distinguish declared-only tables (empty `_versions/`) from created tables.
2174        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    /// List all table locations in the manifest (for root namespace only)
2205    /// Returns a set of table locations (e.g., "table_name.lance")
2206    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    /// Insert an entry into the manifest table
2237    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    /// Insert one or more entries into the manifest table with metadata and base_objects.
2256    ///
2257    /// This is the unified entry point for both single and batch inserts.
2258    /// If any entry already exists (matching object_id), the entire batch fails.
2259    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    /// Delete an entry from the manifest table
2294    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    /// Register a table in the manifest without creating the physical table (internal helper for migration)
2304    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    /// Validate that all levels of a namespace path exist
2318    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    /// Query the manifest for a namespace with the given object ID
2333    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    /// Create or load the manifest dataset, ensuring it has the latest schema setup.
2402    ///
2403    /// This function will:
2404    /// 1. Try to load an existing manifest table
2405    /// 2. If it exists, check and migrate the schema if needed (e.g., add primary key metadata)
2406    /// 3. If it doesn't exist, create a new manifest table with the current schema
2407    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            // Reject a manifest written with a reader feature flag this build
2435            // does not understand before touching it.
2436            ensure_readable(dataset.metadata())?;
2437
2438            // Check if the object_id field has primary key metadata, migrate if not
2439            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                // This legacy migration writes to the manifest, so confirm this
2450                // build is allowed to write the current format first.
2451                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            // Handle race condition where another process created the manifest concurrently
2499            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                    // Another process created the manifest concurrently, try to load it
2519                    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    /// Sorts names alphabetically and applies pagination using page_token (start_after) and limit.
2560    ///
2561    /// Returns the next page token (last item in this page) if more results exist beyond the limit,
2562    /// or `None` if this is the last page.
2563    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        // Build filter to find tables in this namespace
2614        let filter = if namespace_id.is_empty() {
2615            // Root namespace: find tables without a namespace prefix
2616            "object_type = 'table' AND NOT contains(object_id, '$')".to_string()
2617        } else {
2618            // Namespaced: find tables that start with namespace$ but have no additional $
2619            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                    // `include_declared=false` is an explicit opt-in. We still pay one
2664                    // `_versions/` probe per table so declared-state is derived from actual
2665                    // manifests. This is linear in the total number of listed tables, and we do
2666                    // the probes with bounded concurrency before pagination.
2667                    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        // Extract table name and namespace from table_id
2710        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        // For backwards compatibility, only skip vending credentials when explicitly set to false
2721        let vend_credentials = request.vend_credentials.unwrap_or(true);
2722
2723        match table_info {
2724            Some(info) => {
2725                // Construct full URI from relative location
2726                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 a specific version is requested, checkout that version
2776                        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        // Refuse before writing any table data if this build cannot write the
2862        // manifest, so a refused create leaves no orphaned dataset behind.
2863        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        // Validate that request_data is provided
2930        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        // Write the data using Lance Dataset
2938        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                    // Register in manifest (store dir_name, not full URI)
3029                    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        // Query manifest for table location
3070        let table_info = self.query_manifest_for_table(&object_id).boxed().await?;
3071
3072        match table_info {
3073            Some(info) => {
3074                // Delete from manifest first
3075                self.delete_from_manifest(&object_id).boxed().await?;
3076
3077                // Delete physical data directory using the dir_name from manifest
3078                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                // Remove the table directory
3082                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        // Build filter to find direct child namespaces
3116        let filter = if parent_namespace.is_empty() {
3117            // Root namespace: find all namespaces without a parent
3118            "object_type = 'namespace' AND NOT contains(object_id, '$')".to_string()
3119        } else {
3120            // Non-root: find namespaces that start with parent$ but have no additional $
3121            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        // Root namespace always exists
3176        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        // Check if namespace exists in manifest
3185        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        // Root namespace always exists and cannot be created
3212        if namespace_id.is_empty() {
3213            return Err(NamespaceError::NamespaceAlreadyExists {
3214                message: "root namespace".to_string(),
3215            }
3216            .into());
3217        }
3218
3219        // Validate parent namespaces exist (but not the namespace being created)
3220        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        // Root namespace always exists and cannot be dropped
3261        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        // Check if namespace exists
3271        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        // Check for child namespaces
3279        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        // Root namespace always exists
3320        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        // Check if table already exists in manifest
3353        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        // Create table location path with hash-based naming
3362        // When dir_listing_enabled is true and it's a root table, use directory-style naming: {table_name}.lance
3363        // Otherwise, use hash-based naming: {hash}_{object_id}
3364        let dir_name = if namespace.is_empty() && self.dir_listing_enabled {
3365            // Root table with directory listing enabled: use {table_name}.lance
3366            format!("{}.lance", table_name)
3367        } else {
3368            // Child namespace table or dir listing disabled: use hash-based naming
3369            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        // Validate location if provided
3375        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        // Create the .lance-reserved file to mark the table as existing
3389        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        // Add entry to manifest marking this as a declared table (store dir_name, not full path)
3416        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        // For backwards compatibility, only skip vending credentials when explicitly set to false
3434        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        // Validate that location is a relative path within the root directory
3466        // We don't allow absolute URIs or paths that escape the root
3467        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        // Check for path traversal attempts
3488        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        // Validate that parent namespaces exist (if not root)
3502        if !namespace.is_empty() {
3503            self.validate_namespace_levels_exist(&namespace).await?;
3504        }
3505
3506        // Check if table already exists
3507        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        // Register the table with its location in the manifest
3515        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        // Get table info before deleting
3545        let table_info = self.query_manifest_for_table(&object_id).await?;
3546
3547        let table_uri = match table_info {
3548            Some(info) => {
3549                // Delete from manifest only (leave physical data intact)
3550                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    /// A delete mutation that, during staging, has a concurrent writer delete the same
3699    /// object and commit first, so our own commit hits a conflict while the object is
3700    /// already gone — exercising `ConflictResolution::SucceedIfAbsent`.
3701    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    /// Open the `__manifest` dataset directly and set a table-metadata key,
3826    /// simulating a future Lance client that persisted a feature flag.
3827    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    /// This is a forward-compatibility checker only: it must not set any feature
3849    /// flag, so existing clients keep treating the manifest as compatible.
3850    #[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    /// An unknown reader feature flag must block opening the catalog with a clear
3872    /// "please upgrade" error rather than silently degrading to directory listing.
3873    #[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    /// An unknown writer feature flag must still allow reads but block writes.
3896    #[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        // A refused write must not leave an orphaned table dataset behind.
3917        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        // Mutations that go straight through rewrite_manifest (no early
3935        // create_table check) must also be refused: an insert (create_namespace)
3936        // and a delete (drop_table). This proves the writer check is enforced at
3937        // the single copy-on-write chokepoint, not just on the create_table path.
3938        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        // Regression: the commit must use the same object store the manifest dataset reads
4237        // from. On `memory://` the namespace store and the dataset store can be different
4238        // in-memory instances, so a commit written to the wrong one is invisible to reads
4239        // (manifests as stale version -> endless conflict / "not found").
4240        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        // A second sequential commit must not falsely conflict.
4255        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        // The overwrite transaction is embedded inline in the manifest, never written as a
4297        // separate _transactions/*.txn file.
4298        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        // The committed version references our data file => attributed to us (a lost-ack
4333        // commit must be treated as success, not cleaned up).
4334        assert!(
4335            manifest_ns
4336                .manifest_commit_landed(&dataset, version, &our_files)
4337                .await
4338        );
4339        // A different file set is not attributed to us.
4340        let other = HashSet::from(["missing.lance".to_string()]);
4341        assert!(
4342            !manifest_ns
4343                .manifest_commit_landed(&dataset, version, &other)
4344                .await
4345        );
4346        // A version that does not exist did not land.
4347        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        // A concurrent writer deletes "table" and commits first, so our own delete commit
4375        // conflicts while "table" is already gone. Native resolution treats the goal as
4376        // achieved and succeeds instead of erroring or retrying forever.
4377        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        // Create a DirectoryNamespace with manifest enabled (default)
4403        let dir_namespace = DirectoryNamespaceBuilder::new(temp_path)
4404            .inline_optimization_enabled(inline_optimization)
4405            .build()
4406            .await
4407            .unwrap();
4408
4409        // Verify we can list tables (should be empty)
4410        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        // Create a test table
4416        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        // List tables again - should see our new table
4426        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        // Check non-existent table
4448        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        // Create table
4454        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        // Check existing table
4463        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        // Describe non-existent table
4484        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        // Create table
4490        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        // Describe existing table
4499        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        // Create table
4521        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        // Verify table exists
4530        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        // Drop table
4536        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        // Verify table is gone
4541        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        // Create multiple tables
4593        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        // List all tables
4604        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        // Create a DirectoryNamespace with manifest disabled
4622        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        // Verify we can list tables (should be empty)
4630        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        // Create a test table
4636        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        // Create table - this should use directory-only mode
4641        let _response = dir_namespace
4642            .create_table(create_request, Bytes::from(buffer))
4643            .await
4644            .unwrap();
4645
4646        // List tables - should see our new table
4647        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        // Create a DirectoryNamespace with both manifest and directory enabled
4663        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        // Create tables through manifest
4672        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        // List tables - should see table from both manifest and directory
4681        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        // Create a DirectoryNamespace with only manifest enabled
4697        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        // Create table
4706        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        // List tables - should only use manifest
4715        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        // Try to drop non-existent table
4737        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        // Create table
4758        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        // Try to create table with same name - should fail
4767        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        // Create a child namespace
4794        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        // Verify namespace exists
4804        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        // List child namespaces of root
4812        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        // Create parent namespace
4844        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        // Create nested child namespace
4849        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        // Verify nested namespace exists
4859        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        // List child namespaces of parent
4867        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        // Try to create nested namespace without parent
4897        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        // Create a child namespace
4922        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        // Drop the namespace
4927        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        // Verify namespace no longer exists
4937        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        // Create parent and child namespaces
4962        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        // Try to drop parent namespace - should fail because it has children
4971        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        // Create a child namespace
4996        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        // Create a table in the child namespace
5001        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        // List tables in the namespace
5014        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        // Create a child namespace with properties
5044        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        // Describe the namespace
5053        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        // Initialize namespace first - create parent namespace to ensure __manifest table
5091        // is created before concurrent operations
5092        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                // Create table
5110                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                // Drop table
5117                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        // Verify all tables are dropped
5134        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        // Initialize namespace first with a single instance to ensure __manifest
5151        // table is created and parent namespace exists before concurrent operations
5152        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                // Each task creates its own namespace instance
5168                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                // Create table
5179                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                // Drop table
5186                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        // Verify with a fresh namespace instance
5203        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        // Initialize namespace first with a single instance to ensure __manifest
5228        // table is created and parent namespace exists before concurrent operations
5229        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        // Phase 1: Create all tables concurrently using separate namespace instances
5241        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        // Phase 2: Drop all tables concurrently using NEW namespace instances
5272        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        // Verify all tables are dropped
5302        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        // Test S3-style URL with nested path (no trailing slash)
5317        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        // Test Azure-style URL with nested path (no trailing slash)
5326        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        // Test GCS-style URL with nested path (no trailing slash)
5335        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        // Test with deeper nesting
5344        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        // Test with root-level path (single segment after bucket)
5352        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        // Test that URLs with trailing slash already work (no regression)
5360        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        // Test that URLs with empty query string don't include trailing "?"
5369        // This is important because URL::to_string() can add "?" for empty queries
5370        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        // Test that URLs with actual query parameters have them stripped
5378        // (query parameters are not meaningful for storage paths)
5379        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    /// Test that concurrent create_table calls for the same table name don't
5425    /// create duplicate entries in the manifest. Uses two independent
5426    /// ManifestNamespace instances pointing at the same directory to simulate
5427    /// two separate OS processes racing on table creation. Copy-on-write rewrite
5428    /// retries ensure the second operation detects the duplicate after retrying
5429    /// against the latest data.
5430    #[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        // Two independent namespace instances = two separate "processes"
5436        // sharing the same underlying filesystem directory.
5437        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        // Launch both create_table calls concurrently
5456        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        // Exactly one should succeed and one should fail
5462        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        // Verify only one table entry exists in the manifest
5476        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        // Also verify describe_table works (no "found 2" error)
5493        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    // --- apply_pagination unit tests ---
5504
5505    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        // "b" is in the list; should start from "c" (strict >)
5536        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}