1use std::collections::HashMap;
5use std::sync::Arc;
6
7use arrow::array::Float32Array;
8use async_trait::async_trait;
9use bytes::Bytes;
10use lance::dataset::statistics::DatasetStatisticsExt;
11use lance::dataset::{
12 Dataset, MergeInsertBuilder, UpdateBuilder, WhenMatched, WhenNotMatched,
13 WhenNotMatchedBySource, WriteMode,
14};
15use lance::index::DatasetIndexExt;
16use lance_core::{Error, Result, box_error};
17use lance_index::is_system_index;
18use lance_index::scalar::FullTextSearchQuery;
19use lance_linalg::distance::MetricType;
20use lance_namespace::LanceNamespace;
21use lance_namespace::error::NamespaceError;
22use lance_namespace::models::{
23 AlterTableAddColumnsRequest, AlterTableAddColumnsResponse, AlterTableAlterColumnsRequest,
24 AlterTableAlterColumnsResponse, AlterTableDropColumnsRequest, AlterTableDropColumnsResponse,
25 AlterTransactionRequest, AlterTransactionResponse, AnalyzeTableQueryPlanRequest,
26 BatchDeleteTableVersionsRequest, BatchDeleteTableVersionsResponse,
27 BranchContents as ModelBranchContents, CountTableRowsRequest, CreateNamespaceRequest,
28 CreateNamespaceResponse, CreateTableBranchRequest, CreateTableBranchResponse,
29 CreateTableIndexRequest, CreateTableIndexResponse, CreateTableRequest, CreateTableResponse,
30 CreateTableScalarIndexResponse, CreateTableTagRequest, CreateTableTagResponse,
31 CreateTableVersionRequest, CreateTableVersionResponse, DeclareTableRequest,
32 DeclareTableResponse, DeleteFromTableRequest, DeleteFromTableResponse,
33 DeleteTableBranchRequest, DeleteTableBranchResponse, DeleteTableTagRequest,
34 DeleteTableTagResponse, DescribeNamespaceRequest, DescribeNamespaceResponse,
35 DescribeTableIndexStatsRequest, DescribeTableIndexStatsResponse, DescribeTableRequest,
36 DescribeTableResponse, DescribeTableVersionRequest, DescribeTableVersionResponse,
37 DescribeTransactionRequest, DescribeTransactionResponse, DropNamespaceRequest,
38 DropNamespaceResponse, DropTableIndexRequest, DropTableIndexResponse, DropTableRequest,
39 DropTableResponse, ExplainTableQueryPlanRequest, FragmentStats, FragmentSummary,
40 GetTableStatsRequest, GetTableStatsResponse, GetTableTagVersionRequest,
41 GetTableTagVersionResponse, IndexContent, InsertIntoTableRequest, InsertIntoTableResponse,
42 ListNamespacesRequest, ListNamespacesResponse, ListTableBranchesRequest,
43 ListTableBranchesResponse, ListTableIndicesRequest, ListTableIndicesResponse,
44 ListTableTagsRequest, ListTableTagsResponse, ListTableVersionsRequest,
45 ListTableVersionsResponse, ListTablesRequest, ListTablesResponse, MergeInsertIntoTableRequest,
46 MergeInsertIntoTableResponse, NamespaceExistsRequest, QueryTableRequest, RestoreTableRequest,
47 RestoreTableResponse, TableExistsRequest, TagContents as ModelTagContents, UpdateTableRequest,
48 UpdateTableResponse, UpdateTableSchemaMetadataRequest, UpdateTableSchemaMetadataResponse,
49 UpdateTableTagRequest, UpdateTableTagResponse,
50};
51use lance_table::io::commit::ManifestNamingScheme;
52use object_store::path::Path;
53use object_store::{Error as ObjectStoreError, ObjectStoreExt};
54
55use super::marker::{MarkerFileError, put_marker_file_atomic};
56use super::query::{apply_probe_bounds, build_engine_fts_query};
57use super::versions::{ExistingTableVersionResolve, TableDeleteEntry};
58use super::{
59 DirectoryNamespace, build_column_alterations, build_sql_expressions, manifest_feature_flags,
60};
61use crate::merge_insert_on_columns;
62
63#[async_trait]
64impl LanceNamespace for DirectoryNamespace {
65 async fn list_namespaces(
66 &self,
67 request: ListNamespacesRequest,
68 ) -> Result<ListNamespacesResponse> {
69 self.record_op("list_namespaces");
70 self.ensure_read_manifest().await?;
71 if let Some(manifest_ns) = self.manifest_ns_for_read() {
72 return manifest_ns.list_namespaces(request).await;
73 }
74
75 if request.id.as_ref().is_some_and(|id| !id.is_empty()) {
76 return Err(self.child_namespace_requires_manifest_error());
77 }
78 Self::validate_root_namespace_id(&request.id)?;
79 Ok(ListNamespacesResponse::new(vec![]))
80 }
81
82 async fn describe_namespace(
83 &self,
84 request: DescribeNamespaceRequest,
85 ) -> Result<DescribeNamespaceResponse> {
86 self.record_op("describe_namespace");
87 self.ensure_read_manifest().await?;
88 if let Some(manifest_ns) = self.manifest_ns_for_read() {
89 return manifest_ns.describe_namespace(request).await;
90 }
91
92 if request.id.as_ref().is_some_and(|id| !id.is_empty()) {
93 return Err(self.child_namespace_requires_manifest_error());
94 }
95 Self::validate_root_namespace_id(&request.id)?;
96 #[allow(clippy::needless_update)]
97 Ok(DescribeNamespaceResponse {
98 properties: Some(HashMap::new()),
99 ..Default::default()
100 })
101 }
102
103 async fn create_namespace(
104 &self,
105 request: CreateNamespaceRequest,
106 ) -> Result<CreateNamespaceResponse> {
107 self.record_op("create_namespace");
108 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
109 return manifest_ns.create_namespace(request).await;
110 }
111
112 if request.id.is_none() || request.id.as_ref().unwrap().is_empty() {
113 return Err(NamespaceError::NamespaceAlreadyExists {
114 message: "root namespace".to_string(),
115 }
116 .into());
117 }
118
119 Err(NamespaceError::Unsupported {
120 message: "Child namespaces are only supported when manifest mode is enabled"
121 .to_string(),
122 }
123 .into())
124 }
125
126 async fn drop_namespace(&self, request: DropNamespaceRequest) -> Result<DropNamespaceResponse> {
127 self.record_op("drop_namespace");
128 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
129 return manifest_ns.drop_namespace(request).await;
130 }
131
132 if request.id.is_none() || request.id.as_ref().unwrap().is_empty() {
133 return Err(NamespaceError::InvalidInput {
134 message: "Root namespace cannot be dropped".to_string(),
135 }
136 .into());
137 }
138
139 Err(NamespaceError::Unsupported {
140 message: "Child namespaces are only supported when manifest mode is enabled"
141 .to_string(),
142 }
143 .into())
144 }
145
146 async fn namespace_exists(&self, request: NamespaceExistsRequest) -> Result<()> {
147 self.record_op("namespace_exists");
148 self.ensure_read_manifest().await?;
149 if let Some(manifest_ns) = self.manifest_ns_for_read() {
150 return manifest_ns.namespace_exists(request).await;
151 }
152
153 if request.id.is_none() || request.id.as_ref().unwrap().is_empty() {
154 return Ok(());
155 }
156
157 Err(self.child_namespace_requires_manifest_error())
158 }
159
160 async fn list_tables(&self, request: ListTablesRequest) -> Result<ListTablesResponse> {
161 self.record_op("list_tables");
162 let namespace_id = request.id.as_ref().ok_or_else(|| {
164 lance_core::Error::from(NamespaceError::InvalidInput {
165 message: "Namespace ID is required".to_string(),
166 })
167 })?;
168
169 if !namespace_id.is_empty()
175 || !self.dir_listing_enabled
176 || self.dir_listing_to_manifest_migration_enabled
177 {
178 self.ensure_read_manifest().await?;
179 }
180
181 if !namespace_id.is_empty() {
183 if let Some(manifest_ns) = self.manifest_ns_for_read() {
184 return manifest_ns.list_tables(request).await;
185 }
186 return Err(self.child_namespace_requires_manifest_error());
187 }
188
189 if let Some(manifest_ns) = self.manifest_ns_for_read()
191 && !self.dir_listing_enabled
192 {
193 return manifest_ns.list_tables(request).await;
194 }
195 if !self.dir_listing_enabled {
196 return Ok(ListTablesResponse::new(vec![]));
197 }
198
199 let mut tables = if self.manifest_ns_for_read().is_some()
202 && self.dir_listing_enabled
203 && self.dir_listing_to_manifest_migration_enabled
204 {
205 let manifest_locations = if let Some(manifest_ns) = self.manifest_ns_for_read() {
207 manifest_ns.list_manifest_table_locations().await?
208 } else {
209 std::collections::HashSet::new()
210 };
211
212 let mut manifest_request = request.clone();
214 manifest_request.limit = None;
215 manifest_request.page_token = None;
216 let manifest_tables = if let Some(manifest_ns) = self.manifest_ns_for_read() {
217 let manifest_response = manifest_ns.list_tables(manifest_request).await?;
218 manifest_response.tables
219 } else {
220 vec![]
221 };
222
223 let mut all_tables: Vec<String> = manifest_tables;
226 let dir_tables = self.list_directory_tables().await?;
227 for table_name in dir_tables {
228 let full_location = format!("{}/{}.lance", self.root, table_name);
231 let relative_location = format!("{}.lance", table_name);
232 if !manifest_locations.contains(&full_location)
233 && !manifest_locations.contains(&relative_location)
234 {
235 all_tables.push(table_name);
236 }
237 }
238
239 all_tables
240 } else {
241 self.list_directory_tables().await?
242 };
243
244 tables = self
245 .filter_declared_tables(tables, request.include_declared.unwrap_or(true))
246 .await?;
247
248 let next_page_token =
250 Self::apply_pagination(&mut tables, request.page_token, request.limit);
251 let mut response = ListTablesResponse::new(tables);
252 response.page_token = next_page_token;
253 Ok(response)
254 }
255
256 async fn describe_table(&self, request: DescribeTableRequest) -> Result<DescribeTableResponse> {
257 self.record_op("describe_table");
258 self.describe_table_impl(request).await
259 }
260
261 async fn table_exists(&self, request: TableExistsRequest) -> Result<()> {
262 self.record_op("table_exists");
263 let is_root_level = request.id.as_ref().is_some_and(|id| id.len() == 1);
264 let is_child_table = request.id.as_ref().is_some_and(|id| id.len() > 1);
265 let skip_manifest_for_root = self.dir_listing_enabled
266 && is_root_level
267 && !self.dir_listing_to_manifest_migration_enabled;
268 if is_child_table
271 || !self.dir_listing_enabled
272 || self.dir_listing_to_manifest_migration_enabled
273 {
274 self.ensure_read_manifest().await?;
275 }
276 if let Some(manifest_ns) = self.manifest_ns_for_read()
277 && !skip_manifest_for_root
278 {
279 match manifest_ns.table_exists(request.clone()).await {
280 Ok(()) => return Ok(()),
281 Err(e) if manifest_feature_flags::is_incompatible_manifest_error(&e) => {
282 return Err(e);
285 }
286 Err(e) if self.dir_listing_enabled && is_root_level => {
287 if !Self::is_manifest_table_absent_error(&e) {
291 return Err(Self::classify_storage_error(e));
292 }
293 }
294 Err(e) => return Err(e),
295 }
296 }
297 if is_child_table {
298 return Err(self.child_namespace_requires_manifest_error());
299 }
300
301 let table_name = Self::table_name_from_id(&request.id)?;
302 let table_id = Self::format_table_id_from_request(&request.id);
303 if !self.dir_listing_enabled {
304 return Err(NamespaceError::TableNotFound { message: table_id }.into());
305 }
306
307 let status = self.check_table_status(&table_name).await?;
309
310 if !status.exists {
311 return Err(NamespaceError::TableNotFound {
312 message: table_id.clone(),
313 }
314 .into());
315 }
316
317 if status.is_deregistered {
318 return Err(NamespaceError::TableNotFound {
319 message: format!("Table is deregistered: {}", table_id),
320 }
321 .into());
322 }
323
324 Ok(())
325 }
326
327 async fn drop_table(&self, request: DropTableRequest) -> Result<DropTableResponse> {
328 self.record_op("drop_table");
329 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
330 return manifest_ns.drop_table(request).await;
331 }
332
333 let table_name = Self::table_name_from_id(&request.id)?;
334 let table_uri = self.table_full_uri(&table_name);
335 let table_path = self.table_path(&table_name);
336
337 self.object_store
338 .remove_dir_all(table_path)
339 .await
340 .map_err(|e| {
341 lance_core::Error::from(NamespaceError::Internal {
342 message: format!("Failed to drop table {}: {:?}", table_name, e),
343 })
344 })?;
345
346 Ok(DropTableResponse {
347 id: request.id,
348 location: Some(table_uri),
349 ..Default::default()
350 })
351 }
352
353 async fn create_table(
354 &self,
355 request: CreateTableRequest,
356 request_data: Bytes,
357 ) -> Result<CreateTableResponse> {
358 self.record_op("create_table");
359 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
360 return manifest_ns.create_table(request, request_data).await;
361 }
362
363 Self::validate_dir_only_properties(request.properties.as_ref(), "create_table")?;
364
365 let table_name = Self::table_name_from_id(&request.id)?;
366 let table_uri = self.table_full_uri(&table_name);
367 let status = self.check_table_status(&table_name).await?;
368 let (reader, _num_rows) =
369 Self::ipc_reader_from_request_data(&request_data, "create_table")?;
370
371 if status.exists && self.table_has_actual_manifests(&table_name).await? {
372 return Err(NamespaceError::TableAlreadyExists {
373 message: table_name,
374 }
375 .into());
376 }
377
378 let write_result = self
379 .write_reader_to_table(
380 &table_uri,
381 reader,
382 WriteMode::Create,
383 request.storage_options.clone(),
384 )
385 .await;
386 if let Err(err) = write_result {
387 if self.table_uri_has_actual_manifests(&table_uri).await? {
388 return Err(NamespaceError::TableAlreadyExists {
389 message: table_name,
390 }
391 .into());
392 }
393 return Err(err);
394 }
395 Ok(CreateTableResponse {
396 version: Some(1),
397 location: Some(table_uri),
398 storage_options: self.storage_options.clone(),
399 properties: request.properties,
400 ..Default::default()
401 })
402 }
403
404 async fn declare_table(&self, request: DeclareTableRequest) -> Result<DeclareTableResponse> {
405 self.record_op("declare_table");
406 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
407 let mut response = manifest_ns.declare_table(request.clone()).await?;
408 if let Some(ref location) = response.location {
409 let vend = request.vend_credentials.unwrap_or(true);
411 let identity = request.identity.as_deref();
412 response.storage_options = self
413 .get_storage_options_for_table(location, vend, identity)
414 .await?;
415 }
416 if self.table_version_tracking_enabled {
418 response.managed_versioning = Some(true);
419 }
420 return Ok(response);
421 }
422
423 Self::validate_dir_only_properties(request.properties.as_ref(), "declare_table")?;
424
425 let table_name = Self::table_name_from_id(&request.id)?;
426 let table_uri = self.table_full_uri(&table_name);
427
428 if let Some(location) = &request.location {
430 let location = location.trim_end_matches('/');
431 if location != table_uri {
432 return Err(NamespaceError::InvalidInput {
433 message: format!(
434 "Cannot declare table {} at location {}, must be at location {}",
435 table_name, location, table_uri
436 ),
437 }
438 .into());
439 }
440 }
441
442 let status = self.check_table_status(&table_name).await?;
446 if status.exists && !status.has_reserved_file {
447 return Err(NamespaceError::TableAlreadyExists {
449 message: table_name.to_string(),
450 }
451 .into());
452 }
453
454 let reserved_file_path = self.table_reserved_file_path(&table_name);
458
459 put_marker_file_atomic(
460 &self.object_store,
461 &reserved_file_path,
462 &format!("table {}", table_name),
463 )
464 .await
465 .map_err(|e| match e {
466 MarkerFileError::AlreadyExists { .. } => {
467 lance_core::Error::from(NamespaceError::TableAlreadyExists {
468 message: table_name.to_string(),
469 })
470 }
471 MarkerFileError::Other { message } => {
472 lance_core::Error::from(NamespaceError::Internal { message })
473 }
474 })?;
475
476 let vend_credentials = request.vend_credentials.unwrap_or(true);
478 let identity = request.identity.as_deref();
479 let storage_options = self
480 .get_storage_options_for_table(&table_uri, vend_credentials, identity)
481 .await?;
482
483 Ok(DeclareTableResponse {
484 location: Some(table_uri),
485 storage_options,
486 properties: request.properties,
487 managed_versioning: if self.table_version_tracking_enabled {
488 Some(true)
489 } else {
490 None
491 },
492 ..Default::default()
493 })
494 }
495
496 async fn register_table(
497 &self,
498 request: lance_namespace::models::RegisterTableRequest,
499 ) -> Result<lance_namespace::models::RegisterTableResponse> {
500 self.record_op("register_table");
501 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
503 return LanceNamespace::register_table(manifest_ns.as_ref(), request).await;
504 }
505
506 Err(NamespaceError::Unsupported {
508 message: "register_table is only supported when manifest mode is enabled".to_string(),
509 }
510 .into())
511 }
512
513 async fn deregister_table(
514 &self,
515 request: lance_namespace::models::DeregisterTableRequest,
516 ) -> Result<lance_namespace::models::DeregisterTableResponse> {
517 self.record_op("deregister_table");
518 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
520 return LanceNamespace::deregister_table(manifest_ns.as_ref(), request).await;
521 }
522
523 let table_name = Self::table_name_from_id(&request.id)?;
525 let table_uri = self.table_full_uri(&table_name);
526
527 let status = self.check_table_status(&table_name).await?;
530
531 if !status.exists {
532 return Err(NamespaceError::TableNotFound {
533 message: table_name.to_string(),
534 }
535 .into());
536 }
537
538 if status.is_deregistered {
539 return Err(NamespaceError::TableNotFound {
540 message: format!("Table is already deregistered: {}", table_name),
541 }
542 .into());
543 }
544
545 let deregistered_path = self.table_deregistered_file_path(&table_name);
551 put_marker_file_atomic(
552 &self.object_store,
553 &deregistered_path,
554 &format!("deregistration marker for table {}", table_name),
555 )
556 .await
557 .map_err(|e| match e {
558 MarkerFileError::AlreadyExists { .. } => {
559 lance_core::Error::from(NamespaceError::InvalidTableState {
560 message: format!("Table is already deregistered: {}", table_name),
561 })
562 }
563 MarkerFileError::Other { message } => {
564 lance_core::Error::from(NamespaceError::Internal { message })
565 }
566 })?;
567
568 Ok(lance_namespace::models::DeregisterTableResponse {
569 id: request.id,
570 location: Some(table_uri),
571 ..Default::default()
572 })
573 }
574
575 async fn alter_table_add_columns(
576 &self,
577 request: AlterTableAddColumnsRequest,
578 ) -> Result<AlterTableAddColumnsResponse> {
579 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
580 return manifest_ns.alter_table_add_columns(request).await;
581 }
582
583 let table_name = Self::table_name_from_id(&request.id)?;
585 let table_uri = self.table_full_uri(&table_name);
586
587 let status = self.check_table_status(&table_name).await?;
589 if !status.exists {
590 return Err(NamespaceError::TableNotFound {
591 message: table_name,
592 }
593 .into());
594 }
595 if status.is_deregistered {
596 return Err(NamespaceError::TableNotFound {
597 message: format!("Table is deregistered: {}", table_name),
598 }
599 .into());
600 }
601
602 let mut dataset = self
603 .configured_builder(&table_uri)
604 .load()
605 .await
606 .map_err(|e| {
607 Error::io_source(box_error(std::io::Error::other(format!(
608 "Failed to open dataset: {}",
609 e
610 ))))
611 })?;
612
613 let sql_expressions = build_sql_expressions(&request.new_columns)?;
614
615 dataset
616 .add_columns(
617 lance::dataset::NewColumnTransform::SqlExpressions(sql_expressions),
618 None,
619 None,
620 )
621 .await
622 .map_err(|e| {
623 Error::io_source(box_error(std::io::Error::other(format!(
624 "Failed to add columns: {}",
625 e
626 ))))
627 })?;
628
629 let version = dataset.version().version as i64;
630 Ok(AlterTableAddColumnsResponse::new(version))
631 }
632
633 async fn alter_table_alter_columns(
634 &self,
635 request: AlterTableAlterColumnsRequest,
636 ) -> Result<AlterTableAlterColumnsResponse> {
637 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
638 return manifest_ns.alter_table_alter_columns(request).await;
639 }
640
641 let table_name = Self::table_name_from_id(&request.id)?;
642 let table_uri = self.table_full_uri(&table_name);
643
644 let status = self.check_table_status(&table_name).await?;
646 if !status.exists {
647 return Err(NamespaceError::TableNotFound {
648 message: table_name,
649 }
650 .into());
651 }
652 if status.is_deregistered {
653 return Err(NamespaceError::TableNotFound {
654 message: format!("Table is deregistered: {}", table_name),
655 }
656 .into());
657 }
658
659 let mut dataset = self
660 .configured_builder(&table_uri)
661 .load()
662 .await
663 .map_err(|e| {
664 Error::io_source(box_error(std::io::Error::other(format!(
665 "Failed to open dataset: {}",
666 e
667 ))))
668 })?;
669
670 let alterations = build_column_alterations(&request.alterations)?;
671
672 dataset.alter_columns(&alterations).await.map_err(|e| {
673 Error::io_source(box_error(std::io::Error::other(format!(
674 "Failed to alter columns: {}",
675 e
676 ))))
677 })?;
678
679 let version = dataset.version().version as i64;
680 Ok(AlterTableAlterColumnsResponse::new(version))
681 }
682
683 async fn alter_table_drop_columns(
684 &self,
685 request: AlterTableDropColumnsRequest,
686 ) -> Result<AlterTableDropColumnsResponse> {
687 if let Some(manifest_ns) = self.manifest_ns_for_write().await? {
688 return manifest_ns.alter_table_drop_columns(request).await;
689 }
690
691 let table_name = Self::table_name_from_id(&request.id)?;
692 let table_uri = self.table_full_uri(&table_name);
693
694 let status = self.check_table_status(&table_name).await?;
696 if !status.exists {
697 return Err(NamespaceError::TableNotFound {
698 message: table_name,
699 }
700 .into());
701 }
702 if status.is_deregistered {
703 return Err(NamespaceError::TableNotFound {
704 message: format!("Table is deregistered: {}", table_name),
705 }
706 .into());
707 }
708
709 let mut dataset = self
710 .configured_builder(&table_uri)
711 .load()
712 .await
713 .map_err(|e| {
714 Error::io_source(box_error(std::io::Error::other(format!(
715 "Failed to open dataset: {}",
716 e
717 ))))
718 })?;
719
720 let columns: Vec<&str> = request.columns.iter().map(|s| s.as_str()).collect();
721 dataset.drop_columns(&columns).await.map_err(|e| {
722 Error::io_source(box_error(std::io::Error::other(format!(
723 "Failed to drop columns: {}",
724 e
725 ))))
726 })?;
727
728 let version = dataset.version().version as i64;
729 Ok(AlterTableDropColumnsResponse::new(version))
730 }
731
732 async fn list_table_versions(
733 &self,
734 request: ListTableVersionsRequest,
735 ) -> Result<ListTableVersionsResponse> {
736 self.record_op("list_table_versions");
737 let branch = Self::normalized_branch(request.branch.as_deref())?;
738 let table_uri = self.resolve_table_location(&request.id).await?;
739 let table_uri = match branch {
740 Some(b) => self.resolve_branch_location(&table_uri, b).await?,
741 None => table_uri,
742 };
743 let want_descending = request.descending == Some(true);
744 let table_versions = self
745 .list_table_versions_from_storage(&table_uri, want_descending, request.limit)
746 .await?;
747
748 Ok(ListTableVersionsResponse {
749 versions: table_versions,
750 page_token: None,
751 ..Default::default()
752 })
753 }
754
755 async fn create_table_version(
756 &self,
757 request: CreateTableVersionRequest,
758 ) -> Result<CreateTableVersionResponse> {
759 self.record_op("create_table_version");
760 let branch = Self::normalized_branch(request.branch.as_deref())?;
761 let table_uri = self.resolve_table_location(&request.id).await?;
762 let (table_uri, table_path, branch_parent_version) = match branch {
763 Some(b) => self.resolve_branch_for_commit(&table_uri, b).await?,
764 None => {
765 let table_path = self.object_store_path_from_uri(&table_uri)?;
766 (table_uri, table_path, None)
767 }
768 };
769
770 let staging_manifest_path = &request.manifest_path;
771 let version = request.version as u64;
772
773 let naming_scheme = match request.naming_scheme.as_deref() {
775 Some("V1") => ManifestNamingScheme::V1,
776 _ => ManifestNamingScheme::V2,
777 };
778
779 let final_path = naming_scheme.manifest_path(&table_path, version);
781
782 let staging_path = Path::parse(staging_manifest_path).map_err(|e| {
783 lance_core::Error::from(NamespaceError::InvalidInput {
784 message: format!(
785 "Invalid staging manifest path '{}': {}",
786 staging_manifest_path, e
787 ),
788 })
789 })?;
790
791 match self.object_store.inner.head(&final_path).await {
793 Ok(existing_meta) => {
794 return self
795 .resolve_existing_table_version(ExistingTableVersionResolve {
796 staging_path: &staging_path,
797 final_path: &final_path,
798 version,
799 table_uri: &table_uri,
800 final_meta: &existing_meta,
801 request_manifest_size: request.manifest_size,
802 })
803 .await;
804 }
805 Err(ObjectStoreError::NotFound { .. }) => {}
806 Err(e) => {
807 return Err(lance_core::Error::from(NamespaceError::Internal {
808 message: format!(
809 "Failed to stat version {} for table at '{}': {}",
810 version, table_uri, e
811 ),
812 }));
813 }
814 }
815
816 let is_branch = branch.is_some();
819 self.enforce_create_table_version_cas(
820 &table_path,
821 version,
822 &table_uri,
823 is_branch,
824 branch_parent_version,
825 )
826 .await?;
827
828 let copy_result = self
830 .materialize_version_manifest_create(&staging_path, &final_path, staging_manifest_path)
831 .await;
832
833 match copy_result {
834 Ok(()) => {}
835 Err(ObjectStoreError::AlreadyExists { .. })
836 | Err(ObjectStoreError::Precondition { .. }) => {
837 let existing_meta = self.object_store.inner.head(&final_path).await.map_err(|e| {
839 lance_core::Error::from(NamespaceError::Internal {
840 message: format!(
841 "Version {} conflict for table at '{}' but failed to stat winner: {}",
842 version, table_uri, e
843 ),
844 })
845 })?;
846 return self
847 .resolve_existing_table_version(ExistingTableVersionResolve {
848 staging_path: &staging_path,
849 final_path: &final_path,
850 version,
851 table_uri: &table_uri,
852 final_meta: &existing_meta,
853 request_manifest_size: request.manifest_size,
854 })
855 .await;
856 }
857 Err(ObjectStoreError::NotFound { .. }) => {
858 return Err(lance_core::Error::from(NamespaceError::InvalidInput {
859 message: format!(
860 "Staging manifest not found at '{}' for version {} of table at '{}'",
861 staging_manifest_path, version, table_uri
862 ),
863 }));
864 }
865 Err(e) => {
866 return Err(lance_core::Error::from(NamespaceError::Internal {
867 message: format!(
868 "Failed to create version {} for table at '{}': {}",
869 version, table_uri, e
870 ),
871 }));
872 }
873 }
874
875 let final_meta = self
876 .object_store
877 .inner
878 .head(&final_path)
879 .await
880 .map_err(|e| {
881 lance_core::Error::from(NamespaceError::Internal {
882 message: format!(
883 "Failed to stat created version {} for table at '{}': {}",
884 version, table_uri, e
885 ),
886 })
887 })?;
888
889 if let Err(e) = self.object_store.inner.delete(&staging_path).await {
891 log::warn!(
892 "Failed to delete staging manifest at '{}': {:?}",
893 staging_path,
894 e
895 );
896 }
897
898 Ok(Self::create_table_version_response(
899 version,
900 &final_path,
901 &final_meta,
902 ))
903 }
904
905 async fn describe_table_version(
906 &self,
907 request: DescribeTableVersionRequest,
908 ) -> Result<DescribeTableVersionResponse> {
909 self.record_op("describe_table_version");
910 let branch = Self::normalized_branch(request.branch.as_deref())?;
911 let table_uri = self.resolve_table_location(&request.id).await?;
912 let table_uri = match branch {
913 Some(b) => self.resolve_branch_location(&table_uri, b).await?,
914 None => table_uri,
915 };
916 let versions = self
917 .list_table_versions_from_storage(&table_uri, true, None)
918 .await?;
919 let table_version = if let Some(requested_version) = request.version {
920 versions
921 .into_iter()
922 .find(|version| version.version == requested_version)
923 .ok_or_else(|| {
924 lance_core::Error::from(NamespaceError::TableVersionNotFound {
925 message: format!(
926 "version {} for table {}",
927 requested_version,
928 Self::format_table_id_from_request(&request.id)
929 ),
930 })
931 })?
932 } else {
933 versions.into_iter().next().ok_or_else(|| {
934 lance_core::Error::from(NamespaceError::TableVersionNotFound {
935 message: format!(
936 "latest version for table {}",
937 Self::format_table_id_from_request(&request.id)
938 ),
939 })
940 })?
941 };
942
943 Ok(DescribeTableVersionResponse {
944 version: Box::new(table_version),
945 ..Default::default()
946 })
947 }
948
949 async fn batch_delete_table_versions(
950 &self,
951 request: BatchDeleteTableVersionsRequest,
952 ) -> Result<BatchDeleteTableVersionsResponse> {
953 self.record_op("batch_delete_table_versions");
954 let branch = Self::normalized_branch(request.branch.as_deref())?;
955 let ranges: Vec<(i64, i64)> = request
958 .ranges
959 .iter()
960 .map(|r| (r.start_version, r.end_version))
961 .collect();
962
963 const MAX_VERSIONS_PER_REQUEST: i128 = 1_000_000;
967 let requested: i128 = ranges
968 .iter()
969 .map(|(s, e)| {
970 if *e < 0 {
971 0
972 } else {
973 (*e as i128 - *s as i128).max(0)
974 }
975 })
976 .sum();
977 if requested > MAX_VERSIONS_PER_REQUEST {
978 return Err(NamespaceError::InvalidInput {
979 message: format!(
980 "batch_delete requested {} versions; limit is {}",
981 requested, MAX_VERSIONS_PER_REQUEST
982 ),
983 }
984 .into());
985 }
986
987 let table_entries = vec![TableDeleteEntry {
988 table_id: request.id.clone(),
989 ranges,
990 }];
991
992 let total_deleted_count = self
993 .delete_physical_version_files(&table_entries, branch)
994 .await?;
995
996 Ok(BatchDeleteTableVersionsResponse {
997 deleted_count: Some(total_deleted_count),
998 transaction_id: None,
999 ..Default::default()
1000 })
1001 }
1002
1003 async fn create_table_index(
1004 &self,
1005 request: CreateTableIndexRequest,
1006 ) -> Result<CreateTableIndexResponse> {
1007 self.record_op("create_table_index");
1008 let table_uri = self.resolve_table_location(&request.id).await?;
1009 let mut dataset = self
1010 .load_dataset(&table_uri, None, "create_table_index")
1011 .await?;
1012 let index_request = Self::build_index_params(&request)?;
1013
1014 dataset
1015 .create_index(
1016 &[request.column.as_str()],
1017 index_request.index_type(),
1018 request.name.clone(),
1019 index_request.params(),
1020 false,
1021 )
1022 .await
1023 .map_err(|e| {
1024 let err_msg = format!("{}", e);
1025 let ns_err = if err_msg.contains("already exists") {
1026 NamespaceError::TableIndexAlreadyExists {
1027 message: format!(
1028 "Index '{}' already exists on table '{}': {:?}",
1029 request.name.as_deref().unwrap_or("<auto-generated>"),
1030 table_uri,
1031 e
1032 ),
1033 }
1034 } else if err_msg.contains("not found") || err_msg.contains("does not exist") {
1035 NamespaceError::TableColumnNotFound {
1036 message: format!(
1037 "Column '{}' not found for table '{}': {:?}",
1038 request.column, table_uri, e
1039 ),
1040 }
1041 } else {
1042 NamespaceError::Internal {
1043 message: format!(
1044 "Failed to create {} index '{}' on column '{}' for table '{}': {:?}",
1045 request.index_type,
1046 request.name.as_deref().unwrap_or("<auto-generated>"),
1047 request.column,
1048 table_uri,
1049 e
1050 ),
1051 }
1052 };
1053 lance_core::Error::from(ns_err)
1054 })?;
1055
1056 let transaction_id = dataset
1057 .read_transaction()
1058 .await
1059 .map_err(|e| {
1060 lance_core::Error::from(NamespaceError::Internal {
1061 message: format!(
1062 "Failed to read committed transaction after creating index on '{}': {}",
1063 table_uri, e
1064 ),
1065 })
1066 })?
1067 .map(|transaction| transaction.uuid);
1068
1069 Ok(CreateTableIndexResponse {
1070 transaction_id,
1071 ..Default::default()
1072 })
1073 }
1074
1075 async fn list_table_indices(
1076 &self,
1077 request: ListTableIndicesRequest,
1078 ) -> Result<ListTableIndicesResponse> {
1079 self.record_op("list_table_indices");
1080 let table_uri = self.resolve_table_location(&request.id).await?;
1081 let dataset = self
1082 .load_dataset(&table_uri, request.version, "list_table_indices")
1083 .await?;
1084 let total_rows = dataset.count_rows(None).await.map_err(|e| {
1085 lance_core::Error::from(NamespaceError::Internal {
1086 message: format!("Failed to count rows for table '{}': {:?}", table_uri, e),
1087 })
1088 })? as u64;
1089 let mut indices = dataset
1090 .describe_indices(None)
1091 .await
1092 .map_err(|e| {
1093 lance_core::Error::from(NamespaceError::Internal {
1094 message: format!("Failed to describe table indices for '{}': {:?}", table_uri, e),
1095 })
1096 })?
1097 .into_iter()
1098 .filter(|description| {
1099 description
1100 .metadata()
1101 .first()
1102 .map(|metadata| !is_system_index(metadata))
1103 .unwrap_or(false)
1104 })
1105 .map(|description| {
1106 let columns = description
1107 .field_ids()
1108 .iter()
1109 .map(|field_id| {
1110 dataset
1111 .schema()
1112 .field_path_minimal(i32::try_from(*field_id).map_err(|e| {
1113 lance_core::Error::from(NamespaceError::Internal {
1114 message: format!(
1115 "Field id {} does not fit in i32 for table '{}': {}",
1116 field_id, table_uri, e
1117 ),
1118 })
1119 })?)
1120 .map_err(|e| {
1121 lance_core::Error::from(NamespaceError::Internal {
1122 message: format!(
1123 "Failed to resolve field path for field_id {} in table '{}': {}",
1124 field_id, table_uri, e
1125 ),
1126 })
1127 })
1128 })
1129 .collect::<Result<Vec<_>>>()?;
1130
1131 let segments = description.segments();
1132 let created_at = segments
1133 .iter()
1134 .filter_map(|segment| segment.created_at)
1135 .min()
1136 .map(|ts| ts.to_rfc3339());
1137
1138 #[allow(clippy::needless_update)]
1141 let content = IndexContent {
1142 index_name: description.name().to_string(),
1143 index_uuid: description.metadata()[0].uuid.to_string(),
1144 columns,
1145 status: "SUCCEEDED".to_string(),
1146 index_type: Some(description.index_type().to_string()),
1147 type_url: Some(description.type_url().to_string()),
1148 num_indexed_rows: Some(description.rows_indexed() as i64),
1149 num_unindexed_rows: Some(
1150 total_rows.saturating_sub(description.rows_indexed()) as i64,
1151 ),
1152 size_bytes: description.total_size_bytes().map(|size| size as i64),
1153 num_segments: Some(segments.len() as i32),
1154 created_at,
1155 index_version: segments.first().map(|segment| segment.index_version),
1156 index_details: description.details().ok(),
1157 ..Default::default()
1158 };
1159 Ok(content)
1160 })
1161 .collect::<Result<Vec<_>>>()?;
1162
1163 let page_token = Self::paginate_indices(&mut indices, request.page_token, request.limit);
1164 Ok(ListTableIndicesResponse {
1165 indexes: indices,
1166 page_token,
1167 ..Default::default()
1168 })
1169 }
1170
1171 async fn describe_table_index_stats(
1172 &self,
1173 request: DescribeTableIndexStatsRequest,
1174 ) -> Result<DescribeTableIndexStatsResponse> {
1175 self.record_op("describe_table_index_stats");
1176 let table_uri = self.resolve_table_location(&request.id).await?;
1177 let dataset = self
1178 .load_dataset(&table_uri, request.version, "describe_table_index_stats")
1179 .await?;
1180 let index_name = request.index_name.as_deref().ok_or_else(|| {
1181 lance_core::Error::from(NamespaceError::InvalidInput {
1182 message: "Index name is required for describe_table_index_stats".to_string(),
1183 })
1184 })?;
1185 let metadatas = dataset
1186 .load_indices_by_name(index_name)
1187 .await
1188 .map_err(|e| {
1189 lance_core::Error::from(NamespaceError::TableIndexNotFound {
1190 message: format!(
1191 "Failed to load index '{}' metadata for table '{}': {}",
1192 index_name, table_uri, e
1193 ),
1194 })
1195 })?;
1196 if metadatas.first().is_some_and(is_system_index) {
1197 return Err(NamespaceError::Unsupported {
1198 message: format!("System index '{}' is not exposed by this API", index_name),
1199 }
1200 .into());
1201 }
1202
1203 let stats = <Dataset as DatasetIndexExt>::index_statistics(&dataset, index_name)
1204 .await
1205 .map_err(|e| {
1206 lance_core::Error::from(NamespaceError::TableIndexNotFound {
1207 message: format!(
1208 "Failed to describe index statistics for '{}' on table '{}': {}",
1209 index_name, table_uri, e
1210 ),
1211 })
1212 })?;
1213 let stats: serde_json::Value = serde_json::from_str(&stats).map_err(|e| {
1214 lance_core::Error::from(NamespaceError::Internal {
1215 message: format!(
1216 "Failed to parse index statistics for '{}' on table '{}': {}",
1217 index_name, table_uri, e
1218 ),
1219 })
1220 })?;
1221
1222 Ok(Self::describe_table_index_stats_response(&stats))
1223 }
1224
1225 async fn describe_transaction(
1226 &self,
1227 request: DescribeTransactionRequest,
1228 ) -> Result<DescribeTransactionResponse> {
1229 self.record_op("describe_transaction");
1230 let mut request_id = request.id.ok_or_else(|| {
1231 lance_core::Error::from(NamespaceError::InvalidInput {
1232 message: "Transaction id must include table id and transaction identifier"
1233 .to_string(),
1234 })
1235 })?;
1236 if request_id.len() < 2 {
1237 return Err(NamespaceError::InvalidInput {
1238 message: format!(
1239 "Transaction request id must include table id and transaction identifier, got {:?}",
1240 request_id
1241 ),
1242 }
1243 .into());
1244 }
1245
1246 let id = request_id.pop().expect("request_id len checked above");
1247 let table_id = Some(request_id);
1248 let table_uri = self.resolve_table_location(&table_id).await?;
1249 let dataset = self
1250 .load_dataset(&table_uri, None, "describe_transaction")
1251 .await?;
1252 let (version, transaction) = self.find_transaction(&dataset, &id).await?;
1253
1254 let sidecar = self
1257 .load_transaction_alteration(&table_uri, &transaction.uuid)
1258 .await?;
1259
1260 Ok(Self::transaction_response(version, &transaction, sidecar))
1261 }
1262
1263 async fn alter_transaction(
1264 &self,
1265 request: AlterTransactionRequest,
1266 ) -> Result<AlterTransactionResponse> {
1267 self.record_op("alter_transaction");
1268
1269 let mut request_id = request.id.ok_or_else(|| {
1271 lance_core::Error::from(NamespaceError::InvalidInput {
1272 message: "Transaction id must include table id and transaction identifier"
1273 .to_string(),
1274 })
1275 })?;
1276 if request_id.len() < 2 {
1277 return Err(NamespaceError::InvalidInput {
1278 message: format!(
1279 "Transaction request id must include table id and transaction identifier, got {:?}",
1280 request_id
1281 ),
1282 }
1283 .into());
1284 }
1285
1286 let txn_id = request_id.pop().expect("request_id len checked above");
1287 let table_id = Some(request_id);
1288 let table_uri = self.resolve_table_location(&table_id).await?;
1289 let dataset = self
1290 .load_dataset(&table_uri, None, "alter_transaction")
1291 .await?;
1292 let (version, transaction) = self.find_transaction(&dataset, &txn_id).await?;
1293
1294 const RESERVED_KEYS: &[&str] = &["uuid", "version", "read_version", "operation", "tag"];
1298 let is_reserved = |key: &str| RESERVED_KEYS.contains(&key);
1299
1300 let mut sidecar = self
1303 .load_transaction_alteration(&table_uri, &transaction.uuid)
1304 .await?
1305 .unwrap_or_default();
1306
1307 for action in &request.actions {
1308 if let Some(ref set_status) = action.set_status_action
1309 && let Some(ref status) = set_status.status
1310 {
1311 let normalized = status.to_lowercase().replace('_', "");
1313 match normalized.as_str() {
1314 "queued" | "running" | "succeeded" | "failed" | "canceled" => {
1315 sidecar.status = Some(status.clone());
1316 }
1317 _ => {
1318 return Err(NamespaceError::InvalidInput {
1319 message: format!(
1320 "Invalid transaction status '{}'. Valid values are: Queued, Running, Succeeded, Failed, Canceled",
1321 status
1322 ),
1323 }
1324 .into());
1325 }
1326 }
1327 }
1328
1329 if let Some(ref set_property) = action.set_property_action
1330 && let (Some(key), Some(value)) = (&set_property.key, &set_property.value)
1331 {
1332 if is_reserved(key) {
1333 return Err(NamespaceError::InvalidInput {
1334 message: format!("Property '{}' is reserved and cannot be modified", key),
1335 }
1336 .into());
1337 }
1338 let mode = set_property
1339 .mode
1340 .as_deref()
1341 .unwrap_or("Overwrite")
1342 .to_lowercase();
1343 match mode.as_str() {
1344 "overwrite" => {
1345 sidecar.properties.insert(key.clone(), value.clone());
1346 }
1347 "fail" => {
1348 let exists = sidecar.properties.contains_key(key)
1351 || transaction
1352 .transaction_properties
1353 .as_ref()
1354 .is_some_and(|props| props.contains_key(key));
1355 if exists {
1356 return Err(NamespaceError::ConcurrentModification {
1357 message: format!(
1358 "Property '{}' already exists and mode is 'Fail'",
1359 key
1360 ),
1361 }
1362 .into());
1363 }
1364 sidecar.properties.insert(key.clone(), value.clone());
1365 }
1366 "skip" => {
1367 let exists = sidecar.properties.contains_key(key)
1368 || transaction
1369 .transaction_properties
1370 .as_ref()
1371 .is_some_and(|props| props.contains_key(key));
1372 if !exists {
1373 sidecar.properties.insert(key.clone(), value.clone());
1374 }
1375 }
1376 _ => {
1377 return Err(NamespaceError::InvalidInput {
1378 message: format!(
1379 "Invalid set_property mode '{}'. Valid values are: Overwrite, Fail, Skip",
1380 mode
1381 ),
1382 }
1383 .into());
1384 }
1385 }
1386 }
1387
1388 if let Some(ref unset_property) = action.unset_property_action
1389 && let Some(ref key) = unset_property.key
1390 {
1391 if is_reserved(key) {
1392 return Err(NamespaceError::InvalidInput {
1393 message: format!("Property '{}' is reserved and cannot be modified", key),
1394 }
1395 .into());
1396 }
1397 let mode = unset_property
1398 .mode
1399 .as_deref()
1400 .unwrap_or("Skip")
1401 .to_lowercase();
1402 let exists_in_transaction = transaction
1403 .transaction_properties
1404 .as_ref()
1405 .is_some_and(|props| props.contains_key(key));
1406 match mode.as_str() {
1407 "skip" => {
1408 sidecar.properties.remove(key);
1409 if exists_in_transaction {
1410 sidecar.removed_properties.insert(key.clone());
1413 }
1414 }
1415 "fail" => {
1416 if !sidecar.properties.contains_key(key) && !exists_in_transaction {
1417 return Err(NamespaceError::InvalidInput {
1418 message: format!(
1419 "Property '{}' does not exist and mode is 'Fail'",
1420 key
1421 ),
1422 }
1423 .into());
1424 }
1425 sidecar.properties.remove(key);
1426 if exists_in_transaction {
1427 sidecar.removed_properties.insert(key.clone());
1428 }
1429 }
1430 _ => {
1431 return Err(NamespaceError::InvalidInput {
1432 message: format!(
1433 "Invalid unset_property mode '{}'. Valid values are: Skip, Fail",
1434 mode
1435 ),
1436 }
1437 .into());
1438 }
1439 }
1440 }
1441 }
1442
1443 self.save_transaction_alteration(&table_uri, &transaction.uuid, &sidecar)
1447 .await?;
1448
1449 let final_status = sidecar
1452 .status
1453 .clone()
1454 .unwrap_or_else(|| "SUCCEEDED".to_string());
1455 let response = Self::transaction_response(version, &transaction, Some(sidecar));
1456 Ok(AlterTransactionResponse {
1457 status: final_status,
1458 properties: response.properties,
1459 ..Default::default()
1460 })
1461 }
1462
1463 async fn create_table_scalar_index(
1464 &self,
1465 request: CreateTableIndexRequest,
1466 ) -> Result<CreateTableScalarIndexResponse> {
1467 self.record_op("create_table_scalar_index");
1468 let index_type = Self::parse_index_type(&request.index_type)?;
1469 if !index_type.is_scalar() {
1470 return Err(NamespaceError::InvalidInput {
1471 message: format!(
1472 "create_table_scalar_index only supports scalar index types, got {}",
1473 request.index_type
1474 ),
1475 }
1476 .into());
1477 }
1478
1479 let response = self.create_table_index(request).await?;
1480 Ok(CreateTableScalarIndexResponse {
1481 transaction_id: response.transaction_id,
1482 ..Default::default()
1483 })
1484 }
1485
1486 async fn drop_table_index(
1487 &self,
1488 request: DropTableIndexRequest,
1489 ) -> Result<DropTableIndexResponse> {
1490 self.record_op("drop_table_index");
1491 let table_uri = self.resolve_table_location(&request.id).await?;
1492 let index_name = request.index_name.as_deref().ok_or_else(|| {
1493 lance_core::Error::from(NamespaceError::InvalidInput {
1494 message: "Index name is required for drop_table_index".to_string(),
1495 })
1496 })?;
1497 let mut dataset = self
1498 .load_dataset(&table_uri, None, "drop_table_index")
1499 .await?;
1500 let metadatas = dataset
1501 .load_indices_by_name(index_name)
1502 .await
1503 .map_err(|e| {
1504 lance_core::Error::from(NamespaceError::TableIndexNotFound {
1505 message: format!(
1506 "Failed to load index '{}' before dropping it from table '{}': {}",
1507 index_name, table_uri, e
1508 ),
1509 })
1510 })?;
1511 if metadatas.first().is_some_and(is_system_index) {
1512 return Err(NamespaceError::Unsupported {
1513 message: format!(
1514 "System index '{}' cannot be dropped via this API",
1515 index_name
1516 ),
1517 }
1518 .into());
1519 }
1520
1521 dataset.drop_index(index_name).await.map_err(|e| {
1522 lance_core::Error::from(NamespaceError::TableIndexNotFound {
1523 message: format!(
1524 "Failed to drop index '{}' from table '{}': {}",
1525 index_name, table_uri, e
1526 ),
1527 })
1528 })?;
1529
1530 let transaction_id = dataset
1531 .read_transaction()
1532 .await
1533 .map_err(|e| {
1534 lance_core::Error::from(NamespaceError::Internal {
1535 message: format!(
1536 "Failed to read committed transaction after dropping index '{}' from '{}': {}",
1537 index_name, table_uri, e
1538 ),
1539 })
1540 })?
1541 .map(|transaction| transaction.uuid);
1542
1543 Ok(DropTableIndexResponse {
1544 transaction_id,
1545 ..Default::default()
1546 })
1547 }
1548
1549 async fn list_all_tables(&self, request: ListTablesRequest) -> Result<ListTablesResponse> {
1550 let mut tables = self.list_directory_tables().await?;
1553 tables = self
1554 .filter_declared_tables(tables, request.include_declared.unwrap_or(true))
1555 .await?;
1556 Self::apply_pagination(&mut tables, request.page_token, request.limit);
1557 Ok(ListTablesResponse::new(tables))
1558 }
1559
1560 async fn restore_table(&self, request: RestoreTableRequest) -> Result<RestoreTableResponse> {
1561 let version = request.version;
1562 if version < 0 {
1563 return Err(Error::invalid_input_source(
1564 format!(
1565 "Table version for restore_table must be non-negative, got {}",
1566 version
1567 )
1568 .into(),
1569 ));
1570 }
1571
1572 let branch = Self::normalized_branch(request.branch.as_deref())?;
1573 let table_uri = self.resolve_table_location(&request.id).await?;
1574 let mut dataset = match branch {
1575 Some(branch) => self.open_validated_branch(&table_uri, branch).await?,
1576 None => self.load_dataset(&table_uri, None, "restore_table").await?,
1577 };
1578
1579 dataset = dataset
1580 .checkout_version(version as u64)
1581 .await
1582 .map_err(|e| {
1583 Error::namespace_source(
1584 format!(
1585 "Failed to checkout version {} for restore at '{}': {}",
1586 version, table_uri, e
1587 )
1588 .into(),
1589 )
1590 })?;
1591
1592 dataset.restore().await.map_err(|e| {
1593 Error::namespace_source(
1594 format!(
1595 "Failed to restore table at '{}' to version {}: {}",
1596 table_uri, version, e
1597 )
1598 .into(),
1599 )
1600 })?;
1601
1602 let transaction_id = dataset
1603 .read_transaction()
1604 .await
1605 .map_err(|e| {
1606 Error::namespace_source(
1607 format!(
1608 "Failed to read transaction after restoring '{}': {}",
1609 table_uri, e
1610 )
1611 .into(),
1612 )
1613 })?
1614 .map(|t| t.uuid);
1615
1616 Ok(RestoreTableResponse {
1617 transaction_id,
1618 ..Default::default()
1619 })
1620 }
1621
1622 async fn update_table_schema_metadata(
1623 &self,
1624 request: UpdateTableSchemaMetadataRequest,
1625 ) -> Result<UpdateTableSchemaMetadataResponse> {
1626 let table_uri = self.resolve_table_location(&request.id).await?;
1627 let mut dataset = self
1628 .load_dataset(&table_uri, None, "update_table_schema_metadata")
1629 .await?;
1630
1631 let new_metadata = request.metadata.unwrap_or_default();
1632 let updated_metadata = dataset
1633 .update_schema_metadata(new_metadata.iter().map(|(k, v)| (k.as_str(), v.as_str())))
1634 .await
1635 .map_err(|e| {
1636 Error::namespace_source(
1637 format!(
1638 "Failed to update schema metadata for table at '{}': {}",
1639 table_uri, e
1640 )
1641 .into(),
1642 )
1643 })?;
1644
1645 let transaction_id = dataset
1646 .read_transaction()
1647 .await
1648 .map_err(|e| {
1649 Error::namespace_source(
1650 format!(
1651 "Failed to read transaction after updating metadata for '{}': {}",
1652 table_uri, e
1653 )
1654 .into(),
1655 )
1656 })?
1657 .map(|t| t.uuid);
1658
1659 Ok(UpdateTableSchemaMetadataResponse {
1660 metadata: Some(updated_metadata),
1661 transaction_id,
1662 ..Default::default()
1663 })
1664 }
1665
1666 async fn get_table_stats(
1667 &self,
1668 request: GetTableStatsRequest,
1669 ) -> Result<GetTableStatsResponse> {
1670 let table_uri = self.resolve_table_location(&request.id).await?;
1671 let branch = request.branch.as_deref();
1672 let dataset = Arc::new(
1673 self.load_branch_dataset(&table_uri, branch, None, "get_table_stats")
1674 .await?,
1675 );
1676
1677 let data_stats = dataset.calculate_data_stats().await.map_err(|e| {
1679 Error::namespace_source(
1680 format!(
1681 "Failed to calculate data statistics for table at '{}': {}",
1682 table_uri, e
1683 )
1684 .into(),
1685 )
1686 })?;
1687 let total_bytes: i64 = data_stats
1688 .fields
1689 .iter()
1690 .map(|f| f.bytes_on_disk as i64)
1691 .sum();
1692
1693 let fragment_row_futures: Vec<_> = dataset
1695 .get_fragments()
1696 .into_iter()
1697 .map(|f| async move { f.physical_rows().await })
1698 .collect();
1699 let fragment_row_results = futures::future::join_all(fragment_row_futures).await;
1700 let mut fragment_row_counts: Vec<i64> = fragment_row_results
1701 .into_iter()
1702 .filter_map(|r| r.ok())
1703 .map(|r| r as i64)
1704 .collect();
1705
1706 let num_fragments = fragment_row_counts.len() as i64;
1707 let num_rows: i64 = fragment_row_counts.iter().sum();
1708
1709 const SMALL_FRAGMENT_THRESHOLD: i64 = 1024 * 1024;
1712 let num_small_fragments = fragment_row_counts
1713 .iter()
1714 .filter(|&&r| r < SMALL_FRAGMENT_THRESHOLD)
1715 .count() as i64;
1716
1717 fragment_row_counts.sort_unstable();
1719 let lengths = if fragment_row_counts.is_empty() {
1720 FragmentSummary::new(0, 0, 0, 0, 0, 0, 0)
1721 } else {
1722 let len = fragment_row_counts.len();
1723 let min = fragment_row_counts[0];
1724 let max = fragment_row_counts[len - 1];
1725 let mean = num_rows / num_fragments;
1726 let pct = |p: f64| fragment_row_counts[((len - 1) as f64 * p) as usize];
1727 FragmentSummary::new(min, max, mean, pct(0.25), pct(0.50), pct(0.75), pct(0.99))
1728 };
1729
1730 let indices = dataset.load_indices().await.map_err(|e| {
1732 Error::namespace_source(
1733 format!("Failed to load indices for table at '{}': {}", table_uri, e).into(),
1734 )
1735 })?;
1736 let num_indices = indices.iter().filter(|m| !is_system_index(m)).count() as i64;
1737
1738 let fragment_stats = FragmentStats::new(num_fragments, num_small_fragments, lengths);
1739 Ok(GetTableStatsResponse::new(
1740 total_bytes,
1741 num_rows,
1742 num_indices,
1743 fragment_stats,
1744 ))
1745 }
1746
1747 async fn explain_table_query_plan(
1748 &self,
1749 request: ExplainTableQueryPlanRequest,
1750 ) -> Result<String> {
1751 let table_uri = self.resolve_table_location(&request.id).await?;
1752 let dataset = self
1753 .load_dataset(
1754 &table_uri,
1755 request.query.version,
1756 "explain_table_query_plan",
1757 )
1758 .await?;
1759 let verbose = request.verbose.unwrap_or(false);
1760
1761 let mut scanner = dataset.scan();
1762 Self::apply_query_params_to_scanner(
1763 &mut scanner,
1764 request.query.filter.as_deref(),
1765 request.query.columns.as_deref(),
1766 request.query.vector_column.as_deref(),
1767 &request.query.vector,
1768 request.query.k,
1769 request.query.offset,
1770 request.query.prefilter,
1771 request.query.bypass_vector_index,
1772 request.query.nprobes,
1773 request.query.minimum_nprobes,
1774 request.query.maximum_nprobes,
1775 request.query.ef,
1776 request.query.refine_factor,
1777 request.query.distance_type.as_deref(),
1778 request.query.fast_search,
1779 request.query.with_row_id,
1780 request.query.lower_bound,
1781 request.query.upper_bound,
1782 "explain_table_query_plan",
1783 )?;
1784
1785 scanner.explain_plan(verbose).await.map_err(|e| {
1786 Error::namespace_source(
1787 format!(
1788 "Failed to explain query plan for table at '{}': {}",
1789 table_uri, e
1790 )
1791 .into(),
1792 )
1793 })
1794 }
1795
1796 async fn analyze_table_query_plan(
1797 &self,
1798 request: AnalyzeTableQueryPlanRequest,
1799 ) -> Result<String> {
1800 let table_uri = self.resolve_table_location(&request.id).await?;
1801 let dataset = self
1802 .load_dataset(&table_uri, request.version, "analyze_table_query_plan")
1803 .await?;
1804
1805 let mut scanner = dataset.scan();
1806 Self::apply_query_params_to_scanner(
1807 &mut scanner,
1808 request.filter.as_deref(),
1809 request.columns.as_deref(),
1810 request.vector_column.as_deref(),
1811 &request.vector,
1812 request.k,
1813 request.offset,
1814 request.prefilter,
1815 request.bypass_vector_index,
1816 request.nprobes,
1817 request.minimum_nprobes,
1818 request.maximum_nprobes,
1819 request.ef,
1820 request.refine_factor,
1821 request.distance_type.as_deref(),
1822 request.fast_search,
1823 request.with_row_id,
1824 request.lower_bound,
1825 request.upper_bound,
1826 "analyze_table_query_plan",
1827 )?;
1828
1829 scanner.analyze_plan().await.map_err(|e| {
1830 Error::namespace_source(
1831 format!(
1832 "Failed to analyze query plan for table at '{}': {}",
1833 table_uri, e
1834 )
1835 .into(),
1836 )
1837 })
1838 }
1839
1840 async fn count_table_rows(&self, request: CountTableRowsRequest) -> Result<i64> {
1841 self.record_op("count_table_rows");
1842 let table_uri = self.resolve_table_location(&request.id).await?;
1843 let branch = request.branch.as_deref();
1844 let dataset = self
1845 .load_branch_dataset(&table_uri, branch, request.version, "count_table_rows")
1846 .await?;
1847
1848 let count =
1849 dataset
1850 .count_rows(request.predicate)
1851 .await
1852 .map_err(|e| NamespaceError::Internal {
1853 message: format!("Failed to count rows for table at '{}': {:?}", table_uri, e),
1854 })?;
1855
1856 Ok(count as i64)
1857 }
1858
1859 async fn insert_into_table(
1860 &self,
1861 request: InsertIntoTableRequest,
1862 request_data: Bytes,
1863 ) -> Result<InsertIntoTableResponse> {
1864 self.record_op("insert_into_table");
1865 let table_uri = self.resolve_table_location(&request.id).await?;
1866 let (reader, num_rows) =
1867 Self::ipc_reader_from_request_data(&request_data, "insert_into_table")?;
1868
1869 let mode = match request.mode.as_deref() {
1870 Some(m) if m.eq_ignore_ascii_case("overwrite") => WriteMode::Overwrite,
1871 Some(m) if m.eq_ignore_ascii_case("append") => WriteMode::Append,
1872 None => WriteMode::Append,
1873 Some(m) => {
1874 return Err(lance_namespace::error::NamespaceError::InvalidInput {
1875 message: format!(
1876 "Unsupported write mode '{}'. Supported modes are: 'append', 'overwrite'",
1877 m
1878 ),
1879 }
1880 .into());
1881 }
1882 };
1883
1884 let dataset = if !self.table_uri_has_actual_manifests(&table_uri).await? {
1885 self.write_reader_to_table(&table_uri, reader, WriteMode::Create, None)
1886 .await?
1887 } else {
1888 self.write_reader_to_table(&table_uri, reader, mode, None)
1889 .await?
1890 };
1891
1892 Ok(InsertIntoTableResponse {
1893 transaction_id: None,
1894 num_inserted_rows: Some(num_rows as i64),
1895 version: Some(dataset.version().version as i64),
1896 ..Default::default()
1897 })
1898 }
1899
1900 async fn merge_insert_into_table(
1901 &self,
1902 request: MergeInsertIntoTableRequest,
1903 request_data: Bytes,
1904 ) -> Result<MergeInsertIntoTableResponse> {
1905 self.record_op("merge_insert_into_table");
1906 let table_uri = self.resolve_table_location(&request.id).await?;
1907 let on = merge_insert_on_columns(request.on.as_deref(), "merge_insert_into_table")?;
1908
1909 let table_has_manifests = self.table_uri_has_actual_manifests(&table_uri).await?;
1910 let (reader, num_rows) =
1911 Self::ipc_reader_from_request_data(&request_data, "merge_insert_into_table")?;
1912
1913 if !table_has_manifests {
1914 let dataset = self
1915 .write_reader_to_table(&table_uri, reader, WriteMode::Create, None)
1916 .await?;
1917 let version = dataset.version().version as i64;
1918 return Ok(MergeInsertIntoTableResponse {
1919 transaction_id: None,
1920 num_updated_rows: Some(0),
1921 num_inserted_rows: Some(num_rows as i64),
1922 num_deleted_rows: Some(0),
1923 version: Some(version),
1924 ..Default::default()
1925 });
1926 }
1927
1928 let dataset = Arc::new(
1929 self.load_dataset(&table_uri, None, "merge_insert_into_table")
1930 .await?,
1931 );
1932
1933 let mut merge_builder =
1934 MergeInsertBuilder::try_new(dataset.clone(), on.to_vec()).map_err(|e| {
1935 lance_core::Error::from(NamespaceError::InvalidInput {
1936 message: format!("Failed to create merge_insert_into_table builder: {}", e),
1937 })
1938 })?;
1939
1940 if let Some(filter) = request.when_matched_update_all_filt.as_deref() {
1941 let behavior = WhenMatched::update_if(dataset.as_ref(), filter).map_err(|e| {
1942 lance_core::Error::from(NamespaceError::InvalidInput {
1943 message: format!(
1944 "Invalid when_matched_update_all_filt for merge_insert_into_table: {}",
1945 e
1946 ),
1947 })
1948 })?;
1949 merge_builder.when_matched(behavior);
1950 } else if request.when_matched_update_all.unwrap_or(false) {
1951 merge_builder.when_matched(WhenMatched::UpdateAll);
1952 }
1953
1954 if matches!(request.when_not_matched_insert_all, Some(false)) {
1955 merge_builder.when_not_matched(WhenNotMatched::DoNothing);
1956 } else {
1957 merge_builder.when_not_matched(WhenNotMatched::InsertAll);
1958 }
1959
1960 if let Some(filter) = request.when_not_matched_by_source_delete_filt.as_deref() {
1961 let behavior = WhenNotMatchedBySource::delete_if(dataset.as_ref(), filter).map_err(|e| {
1962 lance_core::Error::from(NamespaceError::InvalidInput {
1963 message: format!(
1964 "Invalid when_not_matched_by_source_delete_filt for merge_insert_into_table: {}",
1965 e
1966 ),
1967 })
1968 })?;
1969 merge_builder.when_not_matched_by_source(behavior);
1970 } else if request.when_not_matched_by_source_delete.unwrap_or(false) {
1971 merge_builder.when_not_matched_by_source(WhenNotMatchedBySource::Delete);
1972 }
1973
1974 if let Some(use_index) = request.use_index {
1975 merge_builder.use_index(use_index);
1976 }
1977
1978 let (dataset, stats) = merge_builder
1979 .try_build()
1980 .map_err(|e| {
1981 lance_core::Error::from(NamespaceError::InvalidInput {
1982 message: format!("Failed to build merge_insert_into_table job: {}", e),
1983 })
1984 })?
1985 .execute_reader(reader)
1986 .await
1987 .map_err(|e| Self::map_mutation_error(e, "merge_insert_into_table", &table_uri))?;
1988
1989 Ok(MergeInsertIntoTableResponse {
1990 transaction_id: None,
1991 num_updated_rows: Some(stats.num_updated_rows as i64),
1992 num_inserted_rows: Some(stats.num_inserted_rows as i64),
1993 num_deleted_rows: Some(stats.num_deleted_rows as i64),
1994 version: Some(dataset.version().version as i64),
1995 ..Default::default()
1996 })
1997 }
1998
1999 async fn update_table(&self, request: UpdateTableRequest) -> Result<UpdateTableResponse> {
2000 self.record_op("update_table");
2001
2002 if request.updates.is_empty() {
2003 return Err(NamespaceError::InvalidInput {
2004 message: "update_table requires at least one [column, expression] pair".to_string(),
2005 }
2006 .into());
2007 }
2008
2009 let mut seen_columns: HashMap<String, ()> = HashMap::with_capacity(request.updates.len());
2012 for (idx, pair) in request.updates.iter().enumerate() {
2013 if pair.len() != 2 {
2014 return Err(NamespaceError::InvalidInput {
2015 message: format!(
2016 "update_table updates[{}] must be a [column, expression] pair, got {} elements",
2017 idx,
2018 pair.len()
2019 ),
2020 }
2021 .into());
2022 }
2023 let column = &pair[0];
2024 if column.trim().is_empty() {
2025 return Err(NamespaceError::InvalidInput {
2026 message: format!("update_table updates[{}] has an empty column name", idx),
2027 }
2028 .into());
2029 }
2030 if seen_columns.insert(column.clone(), ()).is_some() {
2031 return Err(NamespaceError::InvalidInput {
2032 message: format!(
2033 "update_table cannot update column '{}' more than once",
2034 column
2035 ),
2036 }
2037 .into());
2038 }
2039 }
2040
2041 let table_uri = self.resolve_table_location(&request.id).await?;
2042 let dataset = Arc::new(self.load_dataset(&table_uri, None, "update_table").await?);
2043
2044 let mut builder = UpdateBuilder::new(dataset);
2045 for pair in &request.updates {
2046 builder = builder.set(&pair[0], &pair[1]).map_err(|e| {
2048 lance_core::Error::from(NamespaceError::InvalidInput {
2049 message: format!("Invalid update expression for column '{}': {}", pair[0], e),
2050 })
2051 })?;
2052 }
2053 if let Some(predicate) = request.predicate.as_deref()
2054 && !predicate.trim().is_empty()
2055 {
2056 builder = builder.update_where(predicate).map_err(|e| {
2057 lance_core::Error::from(NamespaceError::InvalidInput {
2058 message: format!("Invalid update_table predicate '{}': {}", predicate, e),
2059 })
2060 })?;
2061 }
2062
2063 let job = builder.build().map_err(|e| {
2064 lance_core::Error::from(NamespaceError::InvalidInput {
2065 message: format!("Failed to build update_table job: {}", e),
2066 })
2067 })?;
2068
2069 let result = job
2070 .execute()
2071 .await
2072 .map_err(|e| Self::map_mutation_error(e, "update_table", &table_uri))?;
2073
2074 let version = result.new_dataset.version().version as i64;
2075 Ok(UpdateTableResponse {
2076 transaction_id: None,
2077 updated_rows: result.rows_updated as i64,
2078 version,
2079 properties: None,
2080 ..Default::default()
2081 })
2082 }
2083
2084 async fn delete_from_table(
2085 &self,
2086 request: DeleteFromTableRequest,
2087 ) -> Result<DeleteFromTableResponse> {
2088 self.record_op("delete_from_table");
2089
2090 if request.predicate.trim().is_empty() {
2091 return Err(NamespaceError::InvalidInput {
2092 message: "delete_from_table requires a non-empty predicate".to_string(),
2093 }
2094 .into());
2095 }
2096
2097 let table_uri = self.resolve_table_location(&request.id).await?;
2098 let mut dataset = self
2099 .load_dataset(&table_uri, None, "delete_from_table")
2100 .await?;
2101
2102 let result = dataset
2103 .delete(&request.predicate)
2104 .await
2105 .map_err(|e| Self::map_mutation_error(e, "delete_from_table", &table_uri))?;
2106
2107 Ok(DeleteFromTableResponse {
2108 transaction_id: None,
2109 version: Some(result.new_dataset.version().version as i64),
2110 ..Default::default()
2111 })
2112 }
2113
2114 async fn query_table(&self, request: QueryTableRequest) -> Result<Bytes> {
2115 use arrow::ipc::writer::FileWriter;
2116
2117 self.record_op("query_table");
2118 let table_uri = self.resolve_table_location(&request.id).await?;
2119 let dataset = self
2120 .load_dataset(&table_uri, request.version, "query_table")
2121 .await?;
2122
2123 let mut scanner = dataset.scan();
2125
2126 let has_vector_query = request
2129 .vector
2130 .single_vector
2131 .as_ref()
2132 .map(|sv| !sv.is_empty())
2133 .unwrap_or(false)
2134 || request
2135 .vector
2136 .multi_vector
2137 .as_ref()
2138 .map(|mv| !mv.is_empty())
2139 .unwrap_or(false);
2140
2141 if let Some(prefilter) = request.prefilter {
2143 scanner.prefilter(prefilter);
2144 }
2145
2146 if has_vector_query {
2148 let vector_column = request.vector_column.as_deref().unwrap_or("vector");
2149
2150 let query_vector: Vec<f32> = request
2152 .vector
2153 .single_vector
2154 .clone()
2155 .or_else(|| {
2156 request
2157 .vector
2158 .multi_vector
2159 .as_ref()
2160 .and_then(|mv| mv.first().cloned())
2161 })
2162 .unwrap_or_default();
2163
2164 if !query_vector.is_empty() {
2165 let k = if request.k > 0 {
2166 request.k as usize
2167 } else {
2168 10
2169 };
2170 let query_array = Float32Array::from(query_vector);
2171 scanner
2172 .nearest(vector_column, &query_array, k)
2173 .map_err(|e| NamespaceError::InvalidInput {
2174 message: format!("Invalid vector search: {:?}", e),
2175 })?;
2176
2177 if let Some(ref distance_type) = request.distance_type {
2179 let metric = match distance_type.to_lowercase().as_str() {
2180 "l2" | "euclidean" => MetricType::L2,
2181 "cosine" => MetricType::Cosine,
2182 "dot" | "inner_product" => MetricType::Dot,
2183 "hamming" => MetricType::Hamming,
2184 _ => {
2185 return Err(NamespaceError::InvalidInput {
2186 message: format!("Unknown distance type: {}", distance_type),
2187 }
2188 .into());
2189 }
2190 };
2191 scanner.distance_metric(metric);
2192 }
2193
2194 apply_probe_bounds(
2195 &mut scanner,
2196 request.nprobes,
2197 request.minimum_nprobes,
2198 request.maximum_nprobes,
2199 )?;
2200
2201 if let Some(ef) = request.ef {
2203 scanner.ef(ef as usize);
2204 }
2205
2206 if let Some(refine_factor) = request.refine_factor {
2208 scanner.refine(refine_factor as u32);
2209 }
2210
2211 if request.lower_bound.is_some() || request.upper_bound.is_some() {
2213 scanner.distance_range(request.lower_bound, request.upper_bound);
2214 }
2215
2216 if let Some(bypass) = request.bypass_vector_index {
2218 scanner.use_index(!bypass);
2219 }
2220
2221 if request.fast_search == Some(true) {
2223 scanner.fast_search();
2224 }
2225 }
2226 }
2227
2228 if let Some(ref fts_query) = request.full_text_query {
2230 if let Some(ref string_query) = fts_query.string_query {
2232 let mut fts = FullTextSearchQuery::new(string_query.query.clone());
2233
2234 if let Some(ref columns) = string_query.columns
2236 && !columns.is_empty()
2237 {
2238 fts = fts
2239 .with_columns(columns)
2240 .map_err(|e| NamespaceError::InvalidInput {
2241 message: format!("Invalid FTS columns: {:?}", e),
2242 })?;
2243 }
2244
2245 scanner
2246 .full_text_search(fts)
2247 .map_err(|e| NamespaceError::InvalidInput {
2248 message: format!("Invalid full text search: {:?}", e),
2249 })?;
2250 } else if let Some(ref structured_query) = fts_query.structured_query {
2251 let engine_query = build_engine_fts_query(&structured_query.query)?;
2253 let fts = FullTextSearchQuery::new_query(engine_query);
2254 scanner
2255 .full_text_search(fts)
2256 .map_err(|e| NamespaceError::InvalidInput {
2257 message: format!("Invalid full text search: {:?}", e),
2258 })?;
2259 }
2260 }
2261
2262 if let Some(ref columns) = request.columns {
2264 if let Some(ref column_names) = columns.column_names
2265 && !column_names.is_empty()
2266 {
2267 scanner
2268 .project(column_names)
2269 .map_err(|e| NamespaceError::InvalidInput {
2270 message: format!("Invalid column projection: {:?}", e),
2271 })?;
2272 } else if let Some(ref column_aliases) = columns.column_aliases
2273 && !column_aliases.is_empty()
2274 {
2275 let transform_pairs: Vec<(String, String)> = column_aliases
2277 .iter()
2278 .map(|(alias, sql)| (alias.clone(), sql.clone()))
2279 .collect();
2280 scanner
2281 .project_with_transform(
2282 &transform_pairs
2283 .iter()
2284 .map(|(a, s)| (a.as_str(), s.as_str()))
2285 .collect::<Vec<_>>(),
2286 )
2287 .map_err(|e| NamespaceError::InvalidInput {
2288 message: format!("Invalid column alias expression: {:?}", e),
2289 })?;
2290 }
2291 }
2292
2293 if let Some(ref filter) = request.filter
2295 && !filter.is_empty()
2296 {
2297 scanner
2298 .filter(filter)
2299 .map_err(|e| NamespaceError::InvalidInput {
2300 message: format!("Invalid filter expression: {:?}", e),
2301 })?;
2302 }
2303
2304 if request.with_row_id == Some(true) {
2306 scanner.with_row_id();
2307 }
2308
2309 if !has_vector_query && request.k > 0 {
2313 let offset = request.offset.map(|o| o as i64);
2314 scanner.limit(Some(request.k as i64), offset).map_err(|e| {
2315 NamespaceError::InvalidInput {
2316 message: format!("Invalid limit/offset: {:?}", e),
2317 }
2318 })?;
2319 } else if has_vector_query && request.offset.is_some() {
2320 let offset = request.offset.map(|o| o as i64);
2322 scanner
2323 .limit(None, offset)
2324 .map_err(|e| NamespaceError::InvalidInput {
2325 message: format!("Invalid offset: {:?}", e),
2326 })?;
2327 }
2328
2329 let batch = scanner
2331 .try_into_batch()
2332 .await
2333 .map_err(|e| NamespaceError::Internal {
2334 message: format!("Failed to execute query: {:?}", e),
2335 })?;
2336
2337 let schema = batch.schema();
2339 let mut buffer = Vec::new();
2340 {
2341 let mut writer = FileWriter::try_new(&mut buffer, &schema).map_err(|e| {
2342 NamespaceError::Internal {
2343 message: format!("Failed to create IPC writer: {:?}", e),
2344 }
2345 })?;
2346 writer.write(&batch).map_err(|e| NamespaceError::Internal {
2347 message: format!("Failed to write batch to IPC: {:?}", e),
2348 })?;
2349 writer.finish().map_err(|e| NamespaceError::Internal {
2350 message: format!("Failed to finish IPC writer: {:?}", e),
2351 })?;
2352 }
2353
2354 Ok(Bytes::from(buffer))
2355 }
2356
2357 async fn list_table_tags(
2358 &self,
2359 request: ListTableTagsRequest,
2360 ) -> Result<ListTableTagsResponse> {
2361 self.record_op("list_table_tags");
2362 let table_uri = self.resolve_table_location(&request.id).await?;
2363 let dataset = self
2364 .load_dataset(&table_uri, None, "list_table_tags")
2365 .await?;
2366
2367 let raw_tags = dataset.tags().list().await.map_err(|e| {
2368 lance_core::Error::from(NamespaceError::Internal {
2369 message: format!("Failed to list tags for table at '{}': {}", table_uri, e),
2370 })
2371 })?;
2372
2373 let tags = raw_tags
2374 .into_iter()
2375 .map(|(name, contents)| {
2376 let mut tag_model =
2377 ModelTagContents::new(contents.version as i64, contents.manifest_size as i64);
2378 tag_model.branch = contents.branch;
2379 (name, tag_model)
2380 })
2381 .collect();
2382
2383 Ok(ListTableTagsResponse {
2384 tags,
2385 page_token: None,
2386 ..Default::default()
2387 })
2388 }
2389
2390 async fn get_table_tag_version(
2391 &self,
2392 request: GetTableTagVersionRequest,
2393 ) -> Result<GetTableTagVersionResponse> {
2394 self.record_op("get_table_tag_version");
2395 if request.tag.is_empty() {
2396 return Err(NamespaceError::InvalidInput {
2397 message: "tag name must not be empty for get_table_tag_version".to_string(),
2398 }
2399 .into());
2400 }
2401
2402 let table_uri = self.resolve_table_location(&request.id).await?;
2403 let dataset = self
2404 .load_dataset(&table_uri, None, "get_table_tag_version")
2405 .await?;
2406
2407 let contents = dataset
2408 .tags()
2409 .get(&request.tag)
2410 .await
2411 .map_err(|e| Self::map_tag_error(e, &request.tag, &table_uri))?;
2412
2413 Ok(GetTableTagVersionResponse {
2414 version: contents.version as i64,
2415 branch: contents.branch,
2416 ..Default::default()
2417 })
2418 }
2419
2420 async fn create_table_tag(
2421 &self,
2422 request: CreateTableTagRequest,
2423 ) -> Result<CreateTableTagResponse> {
2424 self.record_op("create_table_tag");
2425 if request.tag.is_empty() {
2426 return Err(NamespaceError::InvalidInput {
2427 message: "tag name must not be empty for create_table_tag".to_string(),
2428 }
2429 .into());
2430 }
2431 if request.version <= 0 {
2432 return Err(NamespaceError::InvalidInput {
2433 message: format!(
2434 "tag version must be a positive integer, got {} for create_table_tag",
2435 request.version
2436 ),
2437 }
2438 .into());
2439 }
2440
2441 let table_uri = self.resolve_table_location(&request.id).await?;
2442 let dataset = self
2443 .load_dataset(&table_uri, None, "create_table_tag")
2444 .await?;
2445
2446 dataset
2447 .tags()
2448 .create(&request.tag, request.version as u64)
2449 .await
2450 .map_err(|e| Self::map_tag_error(e, &request.tag, &table_uri))?;
2451
2452 Ok(CreateTableTagResponse {
2453 transaction_id: None,
2454 ..Default::default()
2455 })
2456 }
2457
2458 async fn delete_table_tag(
2459 &self,
2460 request: DeleteTableTagRequest,
2461 ) -> Result<DeleteTableTagResponse> {
2462 self.record_op("delete_table_tag");
2463 if request.tag.is_empty() {
2464 return Err(NamespaceError::InvalidInput {
2465 message: "tag name must not be empty for delete_table_tag".to_string(),
2466 }
2467 .into());
2468 }
2469
2470 let table_uri = self.resolve_table_location(&request.id).await?;
2471 let dataset = self
2472 .load_dataset(&table_uri, None, "delete_table_tag")
2473 .await?;
2474
2475 dataset
2476 .tags()
2477 .delete(&request.tag)
2478 .await
2479 .map_err(|e| Self::map_tag_error(e, &request.tag, &table_uri))?;
2480
2481 Ok(DeleteTableTagResponse {
2482 transaction_id: None,
2483 ..Default::default()
2484 })
2485 }
2486
2487 async fn update_table_tag(
2488 &self,
2489 request: UpdateTableTagRequest,
2490 ) -> Result<UpdateTableTagResponse> {
2491 self.record_op("update_table_tag");
2492 if request.tag.is_empty() {
2493 return Err(NamespaceError::InvalidInput {
2494 message: "tag name must not be empty for update_table_tag".to_string(),
2495 }
2496 .into());
2497 }
2498 if request.version <= 0 {
2499 return Err(NamespaceError::InvalidInput {
2500 message: format!(
2501 "tag version must be a positive integer, got {} for update_table_tag",
2502 request.version
2503 ),
2504 }
2505 .into());
2506 }
2507
2508 let table_uri = self.resolve_table_location(&request.id).await?;
2509 let dataset = self
2510 .load_dataset(&table_uri, None, "update_table_tag")
2511 .await?;
2512
2513 dataset
2514 .tags()
2515 .update(&request.tag, request.version as u64)
2516 .await
2517 .map_err(|e| Self::map_tag_error(e, &request.tag, &table_uri))?;
2518
2519 Ok(UpdateTableTagResponse {
2520 transaction_id: None,
2521 ..Default::default()
2522 })
2523 }
2524
2525 async fn create_table_branch(
2526 &self,
2527 request: CreateTableBranchRequest,
2528 ) -> Result<CreateTableBranchResponse> {
2529 self.record_op("create_table_branch");
2530 if request.name.is_empty() {
2531 return Err(NamespaceError::InvalidInput {
2532 message: "branch name must not be empty for create_table_branch".to_string(),
2533 }
2534 .into());
2535 }
2536 let from_version = match request.from_version {
2537 Some(v) if v <= 0 => {
2538 return Err(NamespaceError::InvalidInput {
2539 message: format!(
2540 "from_version must be a positive integer, got {} for create_table_branch",
2541 v
2542 ),
2543 }
2544 .into());
2545 }
2546 Some(v) => Some(v as u64),
2547 None => None,
2548 };
2549
2550 let table_uri = self.resolve_table_location(&request.id).await?;
2551 let mut dataset = self
2552 .load_dataset(&table_uri, None, "create_table_branch")
2553 .await?;
2554
2555 if dataset.branches().get(&request.name).await.is_ok() {
2560 return Err(NamespaceError::TableBranchAlreadyExists {
2561 message: format!("branch '{}' for table at '{}'", request.name, table_uri),
2562 }
2563 .into());
2564 }
2565
2566 dataset
2567 .create_branch(
2568 &request.name,
2569 (request.from_branch.as_deref(), from_version),
2570 None,
2571 )
2572 .await
2573 .map_err(|e| {
2574 if matches!(e, lance_core::Error::DatasetNotFound { .. }) {
2577 NamespaceError::InvalidInput {
2578 message: format!(
2579 "from_branch/from_version for branch '{}' refers to a source that does not exist: {}",
2580 request.name, e
2581 ),
2582 }
2583 .into()
2584 } else {
2585 Self::map_branch_error(e, &request.name, &table_uri)
2586 }
2587 })?;
2588
2589 Ok(CreateTableBranchResponse {
2590 transaction_id: None,
2591 ..Default::default()
2592 })
2593 }
2594
2595 async fn list_table_branches(
2596 &self,
2597 request: ListTableBranchesRequest,
2598 ) -> Result<ListTableBranchesResponse> {
2599 self.record_op("list_table_branches");
2600 let table_uri = self.resolve_table_location(&request.id).await?;
2601 let dataset = self
2602 .load_dataset(&table_uri, None, "list_table_branches")
2603 .await?;
2604
2605 let raw_branches = dataset.list_branches().await.map_err(|e| {
2606 lance_core::Error::from(NamespaceError::Internal {
2607 message: format!(
2608 "Failed to list branches for table at '{}': {}",
2609 table_uri, e
2610 ),
2611 })
2612 })?;
2613
2614 let branches = raw_branches
2615 .into_iter()
2616 .map(|(name, contents)| {
2617 let mut branch_model = ModelBranchContents::new(
2620 contents.parent_version as i64,
2621 contents.create_at as i64,
2622 contents.manifest_size as i64,
2623 );
2624 branch_model.parent_branch = contents.parent_branch;
2625 branch_model.metadata = if contents.metadata.is_empty() {
2626 None
2627 } else {
2628 Some(contents.metadata)
2629 };
2630 (name, branch_model)
2631 })
2632 .collect();
2633
2634 Ok(ListTableBranchesResponse {
2635 branches,
2636 page_token: None,
2637 ..Default::default()
2638 })
2639 }
2640
2641 async fn delete_table_branch(
2642 &self,
2643 request: DeleteTableBranchRequest,
2644 ) -> Result<DeleteTableBranchResponse> {
2645 self.record_op("delete_table_branch");
2646 if request.name.is_empty() {
2647 return Err(NamespaceError::InvalidInput {
2648 message: "branch name must not be empty for delete_table_branch".to_string(),
2649 }
2650 .into());
2651 }
2652
2653 let table_uri = self.resolve_table_location(&request.id).await?;
2654 let mut dataset = self
2655 .load_dataset(&table_uri, None, "delete_table_branch")
2656 .await?;
2657
2658 dataset
2659 .delete_branch(&request.name)
2660 .await
2661 .map_err(|e| match e {
2662 lance_core::Error::RefConflict { message } => NamespaceError::InvalidInput {
2663 message: format!(
2664 "branch '{}' for table at '{}': {}",
2665 request.name, table_uri, message
2666 ),
2667 }
2668 .into(),
2669 other => Self::map_branch_error(other, &request.name, &table_uri),
2670 })?;
2671
2672 Ok(DeleteTableBranchResponse {
2673 transaction_id: None,
2674 ..Default::default()
2675 })
2676 }
2677
2678 fn namespace_id(&self) -> String {
2679 format!("DirectoryNamespace {{ root: {:?} }}", self.root)
2680 }
2681}