mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-22 04:16:38 +00:00
feat(table-catalog): tighten REST load/register compatibility (#3245)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com> Co-authored-by: cxymds <Cxymds@qq.com>
This commit is contained in:
@@ -38,7 +38,6 @@ const TABLE_CATALOG_ENDPOINTS: &[&str] = &[
|
|||||||
"GET /{warehouse}/namespaces/{namespace}",
|
"GET /{warehouse}/namespaces/{namespace}",
|
||||||
"DELETE /{warehouse}/namespaces/{namespace}",
|
"DELETE /{warehouse}/namespaces/{namespace}",
|
||||||
"GET /{warehouse}/namespaces/{namespace}/tables",
|
"GET /{warehouse}/namespaces/{namespace}/tables",
|
||||||
"POST /{warehouse}/namespaces/{namespace}/tables",
|
|
||||||
"POST /{warehouse}/namespaces/{namespace}/register",
|
"POST /{warehouse}/namespaces/{namespace}/register",
|
||||||
"GET /{warehouse}/namespaces/{namespace}/tables/{table}",
|
"GET /{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
"DELETE /{warehouse}/namespaces/{namespace}/tables/{table}",
|
"DELETE /{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
@@ -78,7 +77,7 @@ struct RegisterTableRequest {
|
|||||||
#[serde(rename = "metadata-location")]
|
#[serde(rename = "metadata-location")]
|
||||||
metadata_location: String,
|
metadata_location: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
properties: BTreeMap<String, String>,
|
overwrite: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -277,11 +276,14 @@ fn table_name_from_params(params: &Params<'_, '_>) -> S3Result<String> {
|
|||||||
Ok(table.to_string())
|
Ok(table.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_catalog_store() -> S3Result<crate::table_catalog::EcStoreTableCatalogStore<ECStore>> {
|
fn table_catalog_backend() -> S3Result<crate::table_catalog::EcStoreTableCatalogObjectBackend<ECStore>> {
|
||||||
let store = new_object_layer_fn().ok_or_else(|| s3_error!(InternalError, "object store not initialized"))?;
|
let store = new_object_layer_fn().ok_or_else(|| s3_error!(InternalError, "object store not initialized"))?;
|
||||||
Ok(crate::table_catalog::ObjectTableCatalogStore::new(
|
Ok(crate::table_catalog::EcStoreTableCatalogObjectBackend::new(store))
|
||||||
crate::table_catalog::EcStoreTableCatalogObjectBackend::new(store),
|
}
|
||||||
))
|
|
||||||
|
fn table_catalog_store() -> S3Result<crate::table_catalog::EcStoreTableCatalogStore<ECStore>> {
|
||||||
|
let backend = table_catalog_backend()?;
|
||||||
|
Ok(crate::table_catalog::ObjectTableCatalogStore::new(backend))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn table_bucket_enabled_from_metadata(bucket: &str) -> S3Result<bool> {
|
async fn table_bucket_enabled_from_metadata(bucket: &str) -> S3Result<bool> {
|
||||||
@@ -375,10 +377,10 @@ fn list_tables_response_from_entries(entries: Vec<crate::table_catalog::TableEnt
|
|||||||
Ok(RestListTablesResponse { identifiers })
|
Ok(RestListTablesResponse { identifiers })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_table_response_from_entry(entry: crate::table_catalog::TableEntry) -> RestLoadTableResponse {
|
fn load_table_response_from_entry(entry: crate::table_catalog::TableEntry, metadata: serde_json::Value) -> RestLoadTableResponse {
|
||||||
RestLoadTableResponse {
|
RestLoadTableResponse {
|
||||||
metadata_location: entry.metadata_location,
|
metadata_location: entry.metadata_location,
|
||||||
metadata: serde_json::Value::Null,
|
metadata,
|
||||||
config: BTreeMap::from([("warehouse-location".to_string(), entry.warehouse_location)]),
|
config: BTreeMap::from([("warehouse-location".to_string(), entry.warehouse_location)]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,6 +420,9 @@ fn table_entry_from_register_request(
|
|||||||
namespace: &crate::table_catalog::Namespace,
|
namespace: &crate::table_catalog::Namespace,
|
||||||
request: RegisterTableRequest,
|
request: RegisterTableRequest,
|
||||||
) -> S3Result<crate::table_catalog::TableEntry> {
|
) -> S3Result<crate::table_catalog::TableEntry> {
|
||||||
|
if request.overwrite {
|
||||||
|
return Err(s3_error!(NotImplemented, "register table overwrite is not supported"));
|
||||||
|
}
|
||||||
let table = crate::table_catalog::IdentifierSegment::parse(request.name)
|
let table = crate::table_catalog::IdentifierSegment::parse(request.name)
|
||||||
.map_err(|err| s3_error!(InvalidRequest, "invalid table name: {}", err))?;
|
.map_err(|err| s3_error!(InvalidRequest, "invalid table name: {}", err))?;
|
||||||
if !crate::table_catalog::is_valid_table_metadata_location(namespace, &table, &request.metadata_location) {
|
if !crate::table_catalog::is_valid_table_metadata_location(namespace, &table, &request.metadata_location) {
|
||||||
@@ -439,7 +444,7 @@ fn table_entry_from_register_request(
|
|||||||
version_token: format!("token-{}", Uuid::new_v4()),
|
version_token: format!("token-{}", Uuid::new_v4()),
|
||||||
generation: 1,
|
generation: 1,
|
||||||
state: crate::table_catalog::TableCatalogEntryState::Active,
|
state: crate::table_catalog::TableCatalogEntryState::Active,
|
||||||
properties: request.properties,
|
properties: BTreeMap::new(),
|
||||||
created_at: None,
|
created_at: None,
|
||||||
updated_at: None,
|
updated_at: None,
|
||||||
})
|
})
|
||||||
@@ -450,15 +455,18 @@ fn table_entry_from_create_table_request(
|
|||||||
namespace: &crate::table_catalog::Namespace,
|
namespace: &crate::table_catalog::Namespace,
|
||||||
request: CreateTableRequest,
|
request: CreateTableRequest,
|
||||||
) -> S3Result<crate::table_catalog::TableEntry> {
|
) -> S3Result<crate::table_catalog::TableEntry> {
|
||||||
table_entry_from_register_request(
|
let properties = request.properties;
|
||||||
|
let mut entry = table_entry_from_register_request(
|
||||||
bucket,
|
bucket,
|
||||||
namespace,
|
namespace,
|
||||||
RegisterTableRequest {
|
RegisterTableRequest {
|
||||||
name: request.name,
|
name: request.name,
|
||||||
metadata_location: request.metadata_location,
|
metadata_location: request.metadata_location,
|
||||||
properties: request.properties,
|
overwrite: false,
|
||||||
},
|
},
|
||||||
)
|
)?;
|
||||||
|
entry.properties = properties;
|
||||||
|
Ok(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn namespace_entry_from_create_request(
|
fn namespace_entry_from_create_request(
|
||||||
@@ -545,6 +553,7 @@ where
|
|||||||
|
|
||||||
async fn register_table_response<S>(
|
async fn register_table_response<S>(
|
||||||
store: &S,
|
store: &S,
|
||||||
|
metadata_backend: &impl crate::table_catalog::TableCatalogObjectBackend,
|
||||||
bucket: &str,
|
bucket: &str,
|
||||||
namespace: &crate::table_catalog::Namespace,
|
namespace: &crate::table_catalog::Namespace,
|
||||||
request: RegisterTableRequest,
|
request: RegisterTableRequest,
|
||||||
@@ -555,12 +564,14 @@ where
|
|||||||
{
|
{
|
||||||
let entry = table_entry_from_register_request(bucket, namespace, request)?;
|
let entry = table_entry_from_register_request(bucket, namespace, request)?;
|
||||||
ensure_table_bucket_entry(store, bucket, table_bucket_enabled).await?;
|
ensure_table_bucket_entry(store, bucket, table_bucket_enabled).await?;
|
||||||
|
let metadata = read_table_metadata_json(metadata_backend, bucket, &entry.metadata_location).await?;
|
||||||
store.register_table(entry.clone()).await.map_err(catalog_store_error)?;
|
store.register_table(entry.clone()).await.map_err(catalog_store_error)?;
|
||||||
Ok(load_table_response_from_entry(entry))
|
Ok(load_table_response_from_entry(entry, metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_table_response<S>(
|
async fn create_table_response<S>(
|
||||||
store: &S,
|
store: &S,
|
||||||
|
metadata_backend: &impl crate::table_catalog::TableCatalogObjectBackend,
|
||||||
bucket: &str,
|
bucket: &str,
|
||||||
namespace: &crate::table_catalog::Namespace,
|
namespace: &crate::table_catalog::Namespace,
|
||||||
request: CreateTableRequest,
|
request: CreateTableRequest,
|
||||||
@@ -571,8 +582,29 @@ where
|
|||||||
{
|
{
|
||||||
let entry = table_entry_from_create_table_request(bucket, namespace, request)?;
|
let entry = table_entry_from_create_table_request(bucket, namespace, request)?;
|
||||||
ensure_table_bucket_entry(store, bucket, table_bucket_enabled).await?;
|
ensure_table_bucket_entry(store, bucket, table_bucket_enabled).await?;
|
||||||
|
let metadata = read_table_metadata_json(metadata_backend, bucket, &entry.metadata_location).await?;
|
||||||
store.create_table(entry.clone()).await.map_err(catalog_store_error)?;
|
store.create_table(entry.clone()).await.map_err(catalog_store_error)?;
|
||||||
Ok(load_table_response_from_entry(entry))
|
Ok(load_table_response_from_entry(entry, metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_table_metadata_json(
|
||||||
|
metadata_backend: &impl crate::table_catalog::TableCatalogObjectBackend,
|
||||||
|
bucket: &str,
|
||||||
|
metadata_location: &str,
|
||||||
|
) -> S3Result<serde_json::Value> {
|
||||||
|
let Some(object) = metadata_backend
|
||||||
|
.read_object(bucket, metadata_location)
|
||||||
|
.await
|
||||||
|
.map_err(catalog_store_error)?
|
||||||
|
else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "table metadata object not found: {metadata_location}"));
|
||||||
|
};
|
||||||
|
let metadata = serde_json::from_slice::<serde_json::Value>(&object.data)
|
||||||
|
.map_err(|err| s3_error!(InvalidRequest, "failed to parse table metadata JSON: {}", err))?;
|
||||||
|
if !metadata.is_object() {
|
||||||
|
return Err(s3_error!(InvalidRequest, "table metadata JSON must be an object"));
|
||||||
|
}
|
||||||
|
Ok(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_tables_response<S>(
|
async fn list_tables_response<S>(
|
||||||
@@ -592,6 +624,7 @@ where
|
|||||||
|
|
||||||
async fn load_table_response<S>(
|
async fn load_table_response<S>(
|
||||||
store: &S,
|
store: &S,
|
||||||
|
metadata_backend: &impl crate::table_catalog::TableCatalogObjectBackend,
|
||||||
bucket: &str,
|
bucket: &str,
|
||||||
namespace: &crate::table_catalog::Namespace,
|
namespace: &crate::table_catalog::Namespace,
|
||||||
table: &str,
|
table: &str,
|
||||||
@@ -606,7 +639,8 @@ where
|
|||||||
else {
|
else {
|
||||||
return Err(s3_error!(InvalidRequest, "table not found"));
|
return Err(s3_error!(InvalidRequest, "table not found"));
|
||||||
};
|
};
|
||||||
Ok(load_table_response_from_entry(entry))
|
let metadata = read_table_metadata_json(metadata_backend, bucket, &entry.metadata_location).await?;
|
||||||
|
Ok(load_table_response_from_entry(entry, metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit_table_response<S>(
|
async fn commit_table_response<S>(
|
||||||
@@ -723,9 +757,11 @@ impl Operation for RestCreateTableHandler {
|
|||||||
let warehouse = warehouse_from_params(¶ms)?;
|
let warehouse = warehouse_from_params(¶ms)?;
|
||||||
let namespace = namespace_from_params(¶ms)?;
|
let namespace = namespace_from_params(¶ms)?;
|
||||||
let request = read_json_body::<CreateTableRequest>(req.input).await?;
|
let request = read_json_body::<CreateTableRequest>(req.input).await?;
|
||||||
let store = table_catalog_store()?;
|
let metadata_backend = table_catalog_backend()?;
|
||||||
|
let store = crate::table_catalog::ObjectTableCatalogStore::new(metadata_backend.clone());
|
||||||
let table_bucket_enabled = table_bucket_enabled_from_metadata(&warehouse).await?;
|
let table_bucket_enabled = table_bucket_enabled_from_metadata(&warehouse).await?;
|
||||||
let response = create_table_response(&store, &warehouse, &namespace, request, table_bucket_enabled).await?;
|
let response =
|
||||||
|
create_table_response(&store, &metadata_backend, &warehouse, &namespace, request, table_bucket_enabled).await?;
|
||||||
build_json_response(StatusCode::OK, &response)
|
build_json_response(StatusCode::OK, &response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -739,9 +775,11 @@ impl Operation for RestRegisterTableHandler {
|
|||||||
let warehouse = warehouse_from_params(¶ms)?;
|
let warehouse = warehouse_from_params(¶ms)?;
|
||||||
let namespace = namespace_from_params(¶ms)?;
|
let namespace = namespace_from_params(¶ms)?;
|
||||||
let request = read_json_body::<RegisterTableRequest>(req.input).await?;
|
let request = read_json_body::<RegisterTableRequest>(req.input).await?;
|
||||||
let store = table_catalog_store()?;
|
let metadata_backend = table_catalog_backend()?;
|
||||||
|
let store = crate::table_catalog::ObjectTableCatalogStore::new(metadata_backend.clone());
|
||||||
let table_bucket_enabled = table_bucket_enabled_from_metadata(&warehouse).await?;
|
let table_bucket_enabled = table_bucket_enabled_from_metadata(&warehouse).await?;
|
||||||
let response = register_table_response(&store, &warehouse, &namespace, request, table_bucket_enabled).await?;
|
let response =
|
||||||
|
register_table_response(&store, &metadata_backend, &warehouse, &namespace, request, table_bucket_enabled).await?;
|
||||||
build_json_response(StatusCode::OK, &response)
|
build_json_response(StatusCode::OK, &response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,8 +793,9 @@ impl Operation for RestLoadTableHandler {
|
|||||||
let warehouse = warehouse_from_params(¶ms)?;
|
let warehouse = warehouse_from_params(¶ms)?;
|
||||||
let namespace = namespace_from_params(¶ms)?;
|
let namespace = namespace_from_params(¶ms)?;
|
||||||
let table = table_name_from_params(¶ms)?;
|
let table = table_name_from_params(¶ms)?;
|
||||||
let store = table_catalog_store()?;
|
let metadata_backend = table_catalog_backend()?;
|
||||||
let response = load_table_response(&store, &warehouse, &namespace, &table).await?;
|
let store = crate::table_catalog::ObjectTableCatalogStore::new(metadata_backend.clone());
|
||||||
|
let response = load_table_response(&store, &metadata_backend, &warehouse, &namespace, &table).await?;
|
||||||
build_json_response(StatusCode::OK, &response)
|
build_json_response(StatusCode::OK, &response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -796,6 +835,7 @@ impl Operation for RestDropTableHandler {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::table_catalog::TableCatalogStore;
|
use crate::table_catalog::TableCatalogStore;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn catalog_config_response_lists_mvp_rest_endpoints() {
|
fn catalog_config_response_lists_mvp_rest_endpoints() {
|
||||||
@@ -810,6 +850,11 @@ mod tests {
|
|||||||
.endpoints
|
.endpoints
|
||||||
.contains(&"POST /{warehouse}/namespaces/{namespace}/register")
|
.contains(&"POST /{warehouse}/namespaces/{namespace}/register")
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
!response
|
||||||
|
.endpoints
|
||||||
|
.contains(&"POST /{warehouse}/namespaces/{namespace}/tables")
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
response
|
response
|
||||||
.endpoints
|
.endpoints
|
||||||
@@ -928,9 +973,7 @@ mod tests {
|
|||||||
let request: RegisterTableRequest = serde_json::from_value(serde_json::json!({
|
let request: RegisterTableRequest = serde_json::from_value(serde_json::json!({
|
||||||
"name": "events",
|
"name": "events",
|
||||||
"metadata-location": ".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json",
|
"metadata-location": ".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json",
|
||||||
"properties": {
|
"overwrite": false
|
||||||
"write.format.default": "parquet"
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
.expect("request should parse");
|
.expect("request should parse");
|
||||||
|
|
||||||
@@ -943,11 +986,44 @@ mod tests {
|
|||||||
entry.metadata_location,
|
entry.metadata_location,
|
||||||
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json"
|
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json"
|
||||||
);
|
);
|
||||||
assert_eq!(entry.properties.get("write.format.default").map(String::as_str), Some("parquet"));
|
assert!(entry.properties.is_empty());
|
||||||
assert_eq!(entry.generation, 1);
|
assert_eq!(entry.generation, 1);
|
||||||
assert!(!entry.version_token.is_empty());
|
assert!(!entry.version_token.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_table_response_includes_rest_metadata_payload() {
|
||||||
|
let metadata = serde_json::json!({
|
||||||
|
"format-version": 2,
|
||||||
|
"table-uuid": "table-uuid",
|
||||||
|
"location": "s3://warehouse/tables/table-id"
|
||||||
|
});
|
||||||
|
let response = load_table_response_from_entry(
|
||||||
|
crate::table_catalog::TableEntry {
|
||||||
|
version: crate::table_catalog::TABLE_CATALOG_ENTRY_VERSION,
|
||||||
|
table_bucket: "warehouse".to_string(),
|
||||||
|
namespace: "analytics".to_string(),
|
||||||
|
table: "events".to_string(),
|
||||||
|
table_id: "table-id".to_string(),
|
||||||
|
table_uuid: "table-uuid".to_string(),
|
||||||
|
format: "ICEBERG".to_string(),
|
||||||
|
format_version: 2,
|
||||||
|
warehouse_location: "s3://warehouse/tables/table-id".to_string(),
|
||||||
|
metadata_location:
|
||||||
|
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json".to_string(),
|
||||||
|
version_token: "token-v1".to_string(),
|
||||||
|
generation: 1,
|
||||||
|
state: crate::table_catalog::TableCatalogEntryState::Active,
|
||||||
|
properties: BTreeMap::new(),
|
||||||
|
created_at: None,
|
||||||
|
updated_at: None,
|
||||||
|
},
|
||||||
|
metadata.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(response.metadata, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn commit_table_request_uses_rest_commit_fields() {
|
fn commit_table_request_uses_rest_commit_fields() {
|
||||||
let request: RestCommitTableRequest = serde_json::from_value(serde_json::json!({
|
let request: RestCommitTableRequest = serde_json::from_value(serde_json::json!({
|
||||||
@@ -987,6 +1063,89 @@ mod tests {
|
|||||||
commits: tokio::sync::Mutex<Vec<crate::table_catalog::CommitLogEntry>>,
|
commits: tokio::sync::Mutex<Vec<crate::table_catalog::CommitLogEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct TestTableCatalogObjectBackend {
|
||||||
|
objects: Arc<tokio::sync::Mutex<BTreeMap<(String, String), crate::table_catalog::TableCatalogObject>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestTableCatalogObjectBackend {
|
||||||
|
async fn put_json(&self, bucket: &str, object: &str, value: serde_json::Value) {
|
||||||
|
let data = serde_json::to_vec(&value).expect("metadata JSON should serialize");
|
||||||
|
self.objects.lock().await.insert(
|
||||||
|
(bucket.to_string(), object.to_string()),
|
||||||
|
crate::table_catalog::TableCatalogObject {
|
||||||
|
data,
|
||||||
|
etag: Some("etag".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl crate::table_catalog::TableCatalogObjectBackend for TestTableCatalogObjectBackend {
|
||||||
|
async fn read_object(
|
||||||
|
&self,
|
||||||
|
bucket: &str,
|
||||||
|
object: &str,
|
||||||
|
) -> crate::table_catalog::TableCatalogStoreResult<Option<crate::table_catalog::TableCatalogObject>> {
|
||||||
|
Ok(self
|
||||||
|
.objects
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get(&(bucket.to_string(), object.to_string()))
|
||||||
|
.cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn object_exists(&self, bucket: &str, object: &str) -> crate::table_catalog::TableCatalogStoreResult<bool> {
|
||||||
|
Ok(self
|
||||||
|
.objects
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.contains_key(&(bucket.to_string(), object.to_string())))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_object(
|
||||||
|
&self,
|
||||||
|
bucket: &str,
|
||||||
|
object: &str,
|
||||||
|
data: Vec<u8>,
|
||||||
|
_precondition: crate::table_catalog::TableCatalogPutPrecondition,
|
||||||
|
) -> crate::table_catalog::TableCatalogStoreResult<()> {
|
||||||
|
self.objects.lock().await.insert(
|
||||||
|
(bucket.to_string(), object.to_string()),
|
||||||
|
crate::table_catalog::TableCatalogObject {
|
||||||
|
data,
|
||||||
|
etag: Some("etag".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_object(&self, bucket: &str, object: &str) -> crate::table_catalog::TableCatalogStoreResult<()> {
|
||||||
|
self.objects.lock().await.remove(&(bucket.to_string(), object.to_string()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_objects(&self, bucket: &str, prefix: &str) -> crate::table_catalog::TableCatalogStoreResult<Vec<String>> {
|
||||||
|
Ok(self
|
||||||
|
.objects
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.keys()
|
||||||
|
.filter(|(object_bucket, object)| object_bucket == bucket && object.starts_with(prefix))
|
||||||
|
.map(|(_, object)| object.clone())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn acquire_write_lock(
|
||||||
|
&self,
|
||||||
|
_bucket: &str,
|
||||||
|
_object: &str,
|
||||||
|
) -> crate::table_catalog::TableCatalogStoreResult<Box<dyn Send>> {
|
||||||
|
Ok(Box::new(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl crate::table_catalog::TableCatalogStore for TestTableCatalogStore {
|
impl crate::table_catalog::TableCatalogStore for TestTableCatalogStore {
|
||||||
async fn get_table_bucket(
|
async fn get_table_bucket(
|
||||||
@@ -1288,6 +1447,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn table_helpers_call_catalog_store() {
|
async fn table_helpers_call_catalog_store() {
|
||||||
let store = TestTableCatalogStore::default();
|
let store = TestTableCatalogStore::default();
|
||||||
|
let metadata_backend = TestTableCatalogObjectBackend::default();
|
||||||
let namespace = crate::table_catalog::Namespace::parse("analytics").expect("namespace should parse");
|
let namespace = crate::table_catalog::Namespace::parse("analytics").expect("namespace should parse");
|
||||||
ensure_table_bucket_entry(&store, "warehouse", true)
|
ensure_table_bucket_entry(&store, "warehouse", true)
|
||||||
.await
|
.await
|
||||||
@@ -1306,14 +1466,26 @@ mod tests {
|
|||||||
|
|
||||||
let metadata_location =
|
let metadata_location =
|
||||||
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json";
|
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00001.metadata.json";
|
||||||
|
metadata_backend
|
||||||
|
.put_json(
|
||||||
|
"warehouse",
|
||||||
|
metadata_location,
|
||||||
|
serde_json::json!({
|
||||||
|
"format-version": 2,
|
||||||
|
"table-uuid": "table-uuid",
|
||||||
|
"location": "s3://warehouse/tables/table-id"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let register = register_table_response(
|
let register = register_table_response(
|
||||||
&store,
|
&store,
|
||||||
|
&metadata_backend,
|
||||||
"warehouse",
|
"warehouse",
|
||||||
&namespace,
|
&namespace,
|
||||||
RegisterTableRequest {
|
RegisterTableRequest {
|
||||||
name: "events".to_string(),
|
name: "events".to_string(),
|
||||||
metadata_location: metadata_location.to_string(),
|
metadata_location: metadata_location.to_string(),
|
||||||
properties: BTreeMap::new(),
|
overwrite: false,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
@@ -1321,16 +1493,18 @@ mod tests {
|
|||||||
.expect("table should register");
|
.expect("table should register");
|
||||||
|
|
||||||
assert_eq!(register.metadata_location, metadata_location);
|
assert_eq!(register.metadata_location, metadata_location);
|
||||||
|
assert_eq!(register.metadata["format-version"], 2);
|
||||||
|
|
||||||
let list = list_tables_response(&store, "warehouse", &namespace)
|
let list = list_tables_response(&store, "warehouse", &namespace)
|
||||||
.await
|
.await
|
||||||
.expect("table list should load");
|
.expect("table list should load");
|
||||||
assert_eq!(list.identifiers[0].name, "events");
|
assert_eq!(list.identifiers[0].name, "events");
|
||||||
|
|
||||||
let load = load_table_response(&store, "warehouse", &namespace, "events")
|
let load = load_table_response(&store, &metadata_backend, "warehouse", &namespace, "events")
|
||||||
.await
|
.await
|
||||||
.expect("table should load");
|
.expect("table should load");
|
||||||
assert_eq!(load.metadata_location, metadata_location);
|
assert_eq!(load.metadata_location, metadata_location);
|
||||||
|
assert_eq!(load.metadata["table-uuid"], "table-uuid");
|
||||||
|
|
||||||
let current = store
|
let current = store
|
||||||
.load_table("warehouse", "analytics", "events")
|
.load_table("warehouse", "analytics", "events")
|
||||||
@@ -1339,6 +1513,18 @@ mod tests {
|
|||||||
.expect("table should exist");
|
.expect("table should exist");
|
||||||
let next_metadata_location =
|
let next_metadata_location =
|
||||||
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00002.metadata.json";
|
".rustfs-table/warehouses/default/namespaces/analytics/tables/events/metadata/00002.metadata.json";
|
||||||
|
metadata_backend
|
||||||
|
.put_json(
|
||||||
|
"warehouse",
|
||||||
|
next_metadata_location,
|
||||||
|
serde_json::json!({
|
||||||
|
"format-version": 2,
|
||||||
|
"table-uuid": "table-uuid",
|
||||||
|
"location": "s3://warehouse/tables/table-id",
|
||||||
|
"last-sequence-number": 2
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let commit = commit_table_response(
|
let commit = commit_table_response(
|
||||||
&store,
|
&store,
|
||||||
"warehouse",
|
"warehouse",
|
||||||
@@ -1372,6 +1558,10 @@ mod tests {
|
|||||||
drop_table_in_store(&store, "warehouse", &namespace, "events")
|
drop_table_in_store(&store, "warehouse", &namespace, "events")
|
||||||
.await
|
.await
|
||||||
.expect("table should drop");
|
.expect("table should drop");
|
||||||
assert!(load_table_response(&store, "warehouse", &namespace, "events").await.is_err());
|
assert!(
|
||||||
|
load_table_response(&store, &metadata_backend, "warehouse", &namespace, "events")
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user