use super::*;
use std::io::Cursor;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use arrow::record_batch::RecordBatchIterator;
use arrow_ipc::reader::{FileReader, StreamReader};
use async_trait::async_trait;
use bytes::Bytes;
use futures::StreamExt;
use lance::dataset::{Dataset, WriteMode, WriteParams};
use lance::index::DatasetIndexExt;
use lance::index::vector::{StageParams, VectorIndexParams};
use lance_core::utils::tempfile::{TempStdDir, TempStrDir};
use lance_core::utils::testing::CountingObjectStore;
use lance_index::IndexType;
use lance_index::scalar::inverted::query::{FtsQuery, Operator};
use lance_io::object_store::providers::local::FileStoreProvider;
use lance_io::object_store::{ObjectStoreParams, ObjectStoreRegistry, uri_to_url};
use lance_linalg::distance::MetricType;
use lance_namespace::error::{ErrorCode, NamespaceError};
use lance_namespace::models::{
BatchDeleteTableVersionsRequest, BatchDeleteTableVersionsResponse, CountTableRowsRequest,
CreateNamespaceRequest, CreateNamespaceResponse, CreateTableBranchRequest,
CreateTableIndexRequest, CreateTableRequest, CreateTableResponse, CreateTableTagRequest,
CreateTableVersionRequest, CreateTableVersionResponse, DeclareTableRequest,
DeclareTableResponse, DeleteFromTableRequest, DeleteTableBranchRequest, DeleteTableTagRequest,
DescribeNamespaceRequest, DescribeNamespaceResponse, DescribeTableRequest,
DescribeTableResponse, DescribeTableVersionRequest, DescribeTableVersionResponse,
DropNamespaceRequest, DropNamespaceResponse, DropTableRequest, DropTableResponse,
GetTableTagVersionRequest, InsertIntoTableRequest, JsonArrowDataType, JsonArrowField,
JsonArrowSchema, ListNamespacesRequest, ListNamespacesResponse, ListTableBranchesRequest,
ListTableTagsRequest, ListTableVersionsRequest, ListTableVersionsResponse, ListTablesRequest,
ListTablesResponse, NamespaceExistsRequest, QueryTableRequest, QueryTableRequestColumns,
TableExistsRequest, TableVersion, UpdateTableRequest, UpdateTableTagRequest,
};
use lance_namespace::schema::convert_json_arrow_schema;
use lance_table::io::commit::{ManifestNamingScheme, VERSIONS_DIR};
use object_store::{
Error as ObjectStoreError, ObjectStore as OSObjectStore, ObjectStoreExt, PutOptions,
};
use rstest::rstest;
use url::Url;
use super::index_params::DirectoryIndexParams;
use super::query::build_engine_fts_query;
mod branches;
mod e2e_table_version_tracking;
mod namespaces;
mod register_declare;
mod storage;
mod table_versions;
mod tables;
mod unit;
fn assert_plan_contains_all(plan: &str, expected_fragments: &[&str], context: &str) {
for expected_fragment in expected_fragments {
assert!(
plan.contains(expected_fragment),
"{}. Missing fragment: '{}'. Plan:\n{}",
context,
expected_fragment,
plan
);
}
}
fn mutation_error_code(err: lance_core::Error) -> ErrorCode {
match err {
lance_core::Error::Namespace { source, .. } => source
.downcast_ref::<NamespaceError>()
.expect("mutation error should wrap a NamespaceError")
.code(),
other => panic!("expected Namespace error, got: {other:?}"),
}
}
async fn create_test_namespace() -> (DirectoryNamespace, TempStdDir) {
let temp_dir = TempStdDir::default();
let namespace = DirectoryNamespaceBuilder::new(temp_dir.to_str().unwrap())
.build()
.await
.unwrap();
(namespace, temp_dir)
}
fn create_test_ipc_data(schema: &JsonArrowSchema) -> Vec<u8> {
use arrow::ipc::writer::StreamWriter;
let arrow_schema = convert_json_arrow_schema(schema).unwrap();
let arrow_schema = Arc::new(arrow_schema);
let batch = arrow::record_batch::RecordBatch::new_empty(arrow_schema.clone());
let mut buffer = Vec::new();
{
let mut writer = StreamWriter::try_new(&mut buffer, &arrow_schema).unwrap();
writer.write(&batch).unwrap();
writer.finish().unwrap();
}
buffer
}
fn create_ipc_data_from_batches(
schema: Arc<arrow_schema::Schema>,
batches: Vec<arrow::record_batch::RecordBatch>,
) -> Vec<u8> {
use arrow::ipc::writer::StreamWriter;
let mut buffer = Vec::new();
{
let mut writer = StreamWriter::try_new(&mut buffer, &schema).unwrap();
for batch in &batches {
writer.write(batch).unwrap();
}
writer.finish().unwrap();
}
buffer
}
fn create_non_empty_test_ipc_data() -> Vec<u8> {
use arrow::array::{Int32Array, StringArray};
use arrow::record_batch::RecordBatch;
let schema = Arc::new(convert_json_arrow_schema(&create_test_schema()).unwrap());
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(Int32Array::from(vec![1, 2])),
Arc::new(StringArray::from(vec![Some("alice"), Some("bob")])),
],
)
.unwrap();
create_ipc_data_from_batches(schema, vec![batch])
}
fn create_single_row_test_ipc_data() -> Vec<u8> {
use arrow::array::{Int32Array, StringArray};
use arrow::record_batch::RecordBatch;
let schema = Arc::new(convert_json_arrow_schema(&create_test_schema()).unwrap());
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(Int32Array::from(vec![10])),
Arc::new(StringArray::from(vec![Some("carol")])),
],
)
.unwrap();
create_ipc_data_from_batches(schema, vec![batch])
}
fn create_test_schema() -> JsonArrowSchema {
let int_type = JsonArrowDataType::new("int32".to_string());
let string_type = JsonArrowDataType::new("utf8".to_string());
let id_field = JsonArrowField {
name: "id".to_string(),
r#type: Box::new(int_type),
nullable: false,
metadata: None,
};
let name_field = JsonArrowField {
name: "name".to_string(),
r#type: Box::new(string_type),
nullable: true,
metadata: None,
};
JsonArrowSchema {
fields: vec![id_field, name_field],
metadata: None,
}
}
fn create_scalar_table_ipc_data() -> Vec<u8> {
use arrow::array::{Int32Array, StringArray};
use arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
let schema = Arc::new(ArrowSchema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new("name", DataType::Utf8, true),
]));
let batch = arrow::record_batch::RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(Int32Array::from(vec![1, 2, 3])),
Arc::new(StringArray::from(vec!["alice", "bob", "cory"])),
],
)
.unwrap();
create_ipc_data_from_batches(schema, vec![batch])
}
async fn create_legacy_manifest_without_primary_key_metadata(root: &str) {
use arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
use arrow::record_batch::{RecordBatch, RecordBatchIterator};
let schema = Arc::new(ArrowSchema::new(vec![
Field::new("object_id", DataType::Utf8, false),
Field::new("object_type", DataType::Utf8, false),
Field::new("location", DataType::Utf8, true),
Field::new("metadata", DataType::Utf8, true),
Field::new(
"base_objects",
DataType::List(Arc::new(Field::new("object_id", DataType::Utf8, true))),
true,
),
]));
let batch = RecordBatch::new_empty(schema.clone());
let reader = RecordBatchIterator::new(vec![Ok(batch)], schema);
Dataset::write(Box::new(reader), &format!("{}/__manifest", root), None)
.await
.unwrap();
}
async fn manifest_has_primary_key_metadata(root: &str) -> bool {
let dataset = Dataset::open(&format!("{}/__manifest", root))
.await
.unwrap();
dataset
.schema()
.field("object_id")
.map(|field| {
field
.metadata
.contains_key(lance_core::datatypes::LANCE_UNENFORCED_PRIMARY_KEY_POSITION)
})
.unwrap_or(false)
}
fn create_vector_table_ipc_data() -> Vec<u8> {
use arrow::array::{FixedSizeListArray, Float32Array, Int32Array};
use arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
let schema = Arc::new(ArrowSchema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new(
"vector",
DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Float32, true)), 2),
true,
),
]));
let vector_field = Arc::new(Field::new("item", DataType::Float32, true));
let vectors = FixedSizeListArray::try_new(
vector_field,
2,
Arc::new(Float32Array::from(vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6])),
None,
)
.unwrap();
let batch = arrow::record_batch::RecordBatch::try_new(
schema.clone(),
vec![Arc::new(Int32Array::from(vec![1, 2, 3])), Arc::new(vectors)],
)
.unwrap();
create_ipc_data_from_batches(schema, vec![batch])
}
async fn create_scalar_table(namespace: &DirectoryNamespace, table_name: &str) {
let mut create_table_request = CreateTableRequest::new();
create_table_request.id = Some(vec![table_name.to_string()]);
namespace
.create_table(
create_table_request,
Bytes::from(create_scalar_table_ipc_data()),
)
.await
.unwrap();
}
async fn create_vector_table(namespace: &DirectoryNamespace, table_name: &str) {
let mut create_table_request = CreateTableRequest::new();
create_table_request.id = Some(vec![table_name.to_string()]);
namespace
.create_table(
create_table_request,
Bytes::from(create_vector_table_ipc_data()),
)
.await
.unwrap();
}
async fn open_dataset(namespace: &DirectoryNamespace, table_name: &str) -> Dataset {
let mut describe_request = DescribeTableRequest::new();
describe_request.id = Some(vec![table_name.to_string()]);
let table_uri = namespace
.describe_table(describe_request)
.await
.unwrap()
.location
.expect("table location should exist");
Dataset::open(&table_uri).await.unwrap()
}
async fn create_scalar_index(
namespace: &DirectoryNamespace,
table_name: &str,
index_name: &str,
) -> Option<String> {
use lance_namespace::models::CreateTableIndexRequest;
let mut create_index_request =
CreateTableIndexRequest::new("id".to_string(), "BTREE".to_string());
create_index_request.id = Some(vec![table_name.to_string()]);
create_index_request.name = Some(index_name.to_string());
namespace
.create_table_scalar_index(create_index_request)
.await
.unwrap()
.transaction_id
}
async fn create_branch_with_commits(
namespace: &DirectoryNamespace,
table_name: &str,
branch_name: &str,
extra_versions: usize,
) -> String {
let mut main = open_dataset(namespace, table_name).await;
let fork_version = main.version().version;
let branch = main
.create_branch(branch_name, fork_version, None)
.await
.unwrap();
let branch_uri = branch.uri().to_string();
for i in 0..extra_versions {
append_scalar_version(&branch_uri, (i as i32 + 1) * 100).await;
}
branch_uri
}
async fn append_scalar_version(uri: &str, seed: i32) {
use arrow::array::{Int32Array, StringArray};
use arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
let schema = Arc::new(ArrowSchema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new("name", DataType::Utf8, true),
]));
let batch = arrow::record_batch::RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(Int32Array::from(vec![seed, seed + 1])),
Arc::new(StringArray::from(vec![Some("x"), Some("y")])),
],
)
.unwrap();
let reader = RecordBatchIterator::new(vec![Ok(batch)], schema.clone());
Dataset::write(
reader,
uri,
Some(WriteParams {
mode: WriteMode::Append,
..Default::default()
}),
)
.await
.unwrap();
}
async fn list_versions(
namespace: &DirectoryNamespace,
table_name: &str,
branch: Option<&str>,
) -> Result<Vec<TableVersion>> {
let req = ListTableVersionsRequest {
id: Some(vec![table_name.to_string()]),
branch: branch.map(|b| b.to_string()),
..Default::default()
};
namespace.list_table_versions(req).await.map(|r| r.versions)
}