mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-25 13:36:50 +00:00
feat(table-catalog): add REST exists endpoints (#3395)
This commit is contained in:
@@ -43,11 +43,13 @@ const TABLE_CATALOG_ENDPOINTS: &[&str] = &[
|
|||||||
"GET /v1/{prefix}/namespaces",
|
"GET /v1/{prefix}/namespaces",
|
||||||
"POST /v1/{prefix}/namespaces",
|
"POST /v1/{prefix}/namespaces",
|
||||||
"GET /v1/{prefix}/namespaces/{namespace}",
|
"GET /v1/{prefix}/namespaces/{namespace}",
|
||||||
|
"HEAD /v1/{prefix}/namespaces/{namespace}",
|
||||||
"DELETE /v1/{prefix}/namespaces/{namespace}",
|
"DELETE /v1/{prefix}/namespaces/{namespace}",
|
||||||
"GET /v1/{prefix}/namespaces/{namespace}/tables",
|
"GET /v1/{prefix}/namespaces/{namespace}/tables",
|
||||||
"POST /v1/{prefix}/namespaces/{namespace}/tables",
|
"POST /v1/{prefix}/namespaces/{namespace}/tables",
|
||||||
"POST /v1/{prefix}/namespaces/{namespace}/register",
|
"POST /v1/{prefix}/namespaces/{namespace}/register",
|
||||||
"GET /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
"GET /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
||||||
|
"HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
||||||
"POST /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
"POST /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
||||||
"DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
"DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}",
|
||||||
"PUT /buckets/{warehouse}",
|
"PUT /buckets/{warehouse}",
|
||||||
@@ -55,11 +57,13 @@ const TABLE_CATALOG_ENDPOINTS: &[&str] = &[
|
|||||||
"GET /{warehouse}/namespaces",
|
"GET /{warehouse}/namespaces",
|
||||||
"POST /{warehouse}/namespaces",
|
"POST /{warehouse}/namespaces",
|
||||||
"GET /{warehouse}/namespaces/{namespace}",
|
"GET /{warehouse}/namespaces/{namespace}",
|
||||||
|
"HEAD /{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}/tables",
|
||||||
"POST /{warehouse}/namespaces/{namespace}/register",
|
"POST /{warehouse}/namespaces/{namespace}/register",
|
||||||
"GET /{warehouse}/namespaces/{namespace}/tables/{table}",
|
"GET /{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
"HEAD /{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
"POST /{warehouse}/namespaces/{namespace}/tables/{table}",
|
"POST /{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
"DELETE /{warehouse}/namespaces/{namespace}/tables/{table}",
|
"DELETE /{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
"POST /{warehouse}/namespaces/{namespace}/tables/{table}/maintenance/metadata",
|
"POST /{warehouse}/namespaces/{namespace}/tables/{table}/maintenance/metadata",
|
||||||
@@ -80,11 +84,13 @@ static GET_TABLE_BUCKET_HANDLER: GetTableBucketHandler = GetTableBucketHandler {
|
|||||||
static LIST_NAMESPACES_HANDLER: RestListNamespacesHandler = RestListNamespacesHandler {};
|
static LIST_NAMESPACES_HANDLER: RestListNamespacesHandler = RestListNamespacesHandler {};
|
||||||
static CREATE_NAMESPACE_HANDLER: RestCreateNamespaceHandler = RestCreateNamespaceHandler {};
|
static CREATE_NAMESPACE_HANDLER: RestCreateNamespaceHandler = RestCreateNamespaceHandler {};
|
||||||
static GET_NAMESPACE_HANDLER: RestGetNamespaceHandler = RestGetNamespaceHandler {};
|
static GET_NAMESPACE_HANDLER: RestGetNamespaceHandler = RestGetNamespaceHandler {};
|
||||||
|
static NAMESPACE_EXISTS_HANDLER: RestNamespaceExistsHandler = RestNamespaceExistsHandler {};
|
||||||
static DROP_NAMESPACE_HANDLER: RestDropNamespaceHandler = RestDropNamespaceHandler {};
|
static DROP_NAMESPACE_HANDLER: RestDropNamespaceHandler = RestDropNamespaceHandler {};
|
||||||
static LIST_TABLES_HANDLER: RestListTablesHandler = RestListTablesHandler {};
|
static LIST_TABLES_HANDLER: RestListTablesHandler = RestListTablesHandler {};
|
||||||
static CREATE_TABLE_HANDLER: RestCreateTableHandler = RestCreateTableHandler {};
|
static CREATE_TABLE_HANDLER: RestCreateTableHandler = RestCreateTableHandler {};
|
||||||
static REGISTER_TABLE_HANDLER: RestRegisterTableHandler = RestRegisterTableHandler {};
|
static REGISTER_TABLE_HANDLER: RestRegisterTableHandler = RestRegisterTableHandler {};
|
||||||
static LOAD_TABLE_HANDLER: RestLoadTableHandler = RestLoadTableHandler {};
|
static LOAD_TABLE_HANDLER: RestLoadTableHandler = RestLoadTableHandler {};
|
||||||
|
static TABLE_EXISTS_HANDLER: RestTableExistsHandler = RestTableExistsHandler {};
|
||||||
static COMMIT_TABLE_HANDLER: RestCommitTableHandler = RestCommitTableHandler {};
|
static COMMIT_TABLE_HANDLER: RestCommitTableHandler = RestCommitTableHandler {};
|
||||||
static DROP_TABLE_HANDLER: RestDropTableHandler = RestDropTableHandler {};
|
static DROP_TABLE_HANDLER: RestDropTableHandler = RestDropTableHandler {};
|
||||||
static GET_TABLE_METADATA_LOCATION_HANDLER: GetTableMetadataLocationHandler = GetTableMetadataLocationHandler {};
|
static GET_TABLE_METADATA_LOCATION_HANDLER: GetTableMetadataLocationHandler = GetTableMetadataLocationHandler {};
|
||||||
@@ -326,6 +332,11 @@ fn register_table_catalog_prefix_routes(r: &mut S3Router<AdminOperation>, prefix
|
|||||||
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
||||||
AdminOperation(&GET_NAMESPACE_HANDLER),
|
AdminOperation(&GET_NAMESPACE_HANDLER),
|
||||||
)?;
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::HEAD,
|
||||||
|
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
||||||
|
AdminOperation(&NAMESPACE_EXISTS_HANDLER),
|
||||||
|
)?;
|
||||||
r.insert(
|
r.insert(
|
||||||
Method::DELETE,
|
Method::DELETE,
|
||||||
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
||||||
@@ -351,6 +362,11 @@ fn register_table_catalog_prefix_routes(r: &mut S3Router<AdminOperation>, prefix
|
|||||||
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
||||||
AdminOperation(&LOAD_TABLE_HANDLER),
|
AdminOperation(&LOAD_TABLE_HANDLER),
|
||||||
)?;
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::HEAD,
|
||||||
|
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
||||||
|
AdminOperation(&TABLE_EXISTS_HANDLER),
|
||||||
|
)?;
|
||||||
r.insert(
|
r.insert(
|
||||||
Method::POST,
|
Method::POST,
|
||||||
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
format!("{prefix}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
||||||
@@ -434,6 +450,18 @@ fn build_json_response<T: Serialize>(status: StatusCode, body: &T) -> S3Result<S
|
|||||||
Ok(S3Response::with_headers((status, Body::from(data)), headers))
|
Ok(S3Response::with_headers((status, Body::from(data)), headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn empty_response(status: StatusCode) -> S3Response<(StatusCode, Body)> {
|
||||||
|
S3Response::new((status, Body::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists_status(exists: bool) -> StatusCode {
|
||||||
|
if exists {
|
||||||
|
StatusCode::NO_CONTENT
|
||||||
|
} else {
|
||||||
|
StatusCode::NOT_FOUND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn authorize_table_catalog_request(req: &S3Request<Body>, action: AdminAction) -> S3Result<()> {
|
async fn authorize_table_catalog_request(req: &S3Request<Body>, action: AdminAction) -> S3Result<()> {
|
||||||
let Some(input_cred) = &req.credentials else {
|
let Some(input_cred) = &req.credentials else {
|
||||||
return Err(s3_error!(InvalidRequest, "authentication required"));
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
@@ -1651,6 +1679,18 @@ where
|
|||||||
namespace_response_from_entry(entry)
|
namespace_response_from_entry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn namespace_exists_status<S>(store: &S, bucket: &str, namespace: &crate::table_catalog::Namespace) -> S3Result<StatusCode>
|
||||||
|
where
|
||||||
|
S: crate::table_catalog::TableCatalogStore + ?Sized,
|
||||||
|
{
|
||||||
|
let exists = store
|
||||||
|
.get_namespace(bucket, &namespace.public_name())
|
||||||
|
.await
|
||||||
|
.map_err(catalog_store_error)?
|
||||||
|
.is_some();
|
||||||
|
Ok(exists_status(exists))
|
||||||
|
}
|
||||||
|
|
||||||
async fn drop_namespace_in_store<S>(store: &S, bucket: &str, namespace: &str) -> S3Result<()>
|
async fn drop_namespace_in_store<S>(store: &S, bucket: &str, namespace: &str) -> S3Result<()>
|
||||||
where
|
where
|
||||||
S: crate::table_catalog::TableCatalogStore + ?Sized,
|
S: crate::table_catalog::TableCatalogStore + ?Sized,
|
||||||
@@ -1762,6 +1802,23 @@ where
|
|||||||
Ok(load_table_response_from_entry(entry, metadata))
|
Ok(load_table_response_from_entry(entry, metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn table_exists_status<S>(
|
||||||
|
store: &S,
|
||||||
|
bucket: &str,
|
||||||
|
namespace: &crate::table_catalog::Namespace,
|
||||||
|
table: &str,
|
||||||
|
) -> S3Result<StatusCode>
|
||||||
|
where
|
||||||
|
S: crate::table_catalog::TableCatalogStore + ?Sized,
|
||||||
|
{
|
||||||
|
let exists = store
|
||||||
|
.load_table(bucket, &namespace.public_name(), table)
|
||||||
|
.await
|
||||||
|
.map_err(catalog_store_error)?
|
||||||
|
.is_some();
|
||||||
|
Ok(exists_status(exists))
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_table_metadata_location_response<S>(
|
async fn get_table_metadata_location_response<S>(
|
||||||
store: &S,
|
store: &S,
|
||||||
bucket: &str,
|
bucket: &str,
|
||||||
@@ -2125,7 +2182,21 @@ impl Operation for RestDropNamespaceHandler {
|
|||||||
authorize_table_catalog_resource_request(&req, &resource, AdminAction::DeleteTableNamespaceAction).await?;
|
authorize_table_catalog_resource_request(&req, &resource, AdminAction::DeleteTableNamespaceAction).await?;
|
||||||
let store = table_catalog_store()?;
|
let store = table_catalog_store()?;
|
||||||
drop_namespace_in_store(&store, &warehouse, &namespace.public_name()).await?;
|
drop_namespace_in_store(&store, &warehouse, &namespace.public_name()).await?;
|
||||||
Ok(S3Response::new((StatusCode::NO_CONTENT, Body::default())))
|
Ok(empty_response(StatusCode::NO_CONTENT))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RestNamespaceExistsHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for RestNamespaceExistsHandler {
|
||||||
|
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let warehouse = warehouse_from_params(¶ms)?;
|
||||||
|
let namespace = namespace_from_params(¶ms)?;
|
||||||
|
let resource = TableCatalogResource::namespace(&warehouse, &namespace);
|
||||||
|
authorize_table_catalog_resource_request(&req, &resource, AdminAction::GetTableNamespaceAction).await?;
|
||||||
|
let store = table_catalog_store()?;
|
||||||
|
Ok(empty_response(namespace_exists_status(&store, &warehouse, &namespace).await?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2199,6 +2270,21 @@ impl Operation for RestLoadTableHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RestTableExistsHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for RestTableExistsHandler {
|
||||||
|
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let warehouse = warehouse_from_params(¶ms)?;
|
||||||
|
let namespace = namespace_from_params(¶ms)?;
|
||||||
|
let table = table_name_from_params(¶ms)?;
|
||||||
|
let resource = TableCatalogResource::table(&warehouse, &namespace, &table);
|
||||||
|
authorize_table_catalog_resource_request(&req, &resource, AdminAction::GetTableAction).await?;
|
||||||
|
let store = table_catalog_store()?;
|
||||||
|
Ok(empty_response(table_exists_status(&store, &warehouse, &namespace, &table).await?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RestCommitTableHandler {}
|
pub struct RestCommitTableHandler {}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -2229,7 +2315,7 @@ impl Operation for RestDropTableHandler {
|
|||||||
authorize_table_catalog_resource_request(&req, &resource, AdminAction::DeleteTableAction).await?;
|
authorize_table_catalog_resource_request(&req, &resource, AdminAction::DeleteTableAction).await?;
|
||||||
let store = table_catalog_store()?;
|
let store = table_catalog_store()?;
|
||||||
drop_table_in_store(&store, &warehouse, &namespace, &table).await?;
|
drop_table_in_store(&store, &warehouse, &namespace, &table).await?;
|
||||||
Ok(S3Response::new((StatusCode::NO_CONTENT, Body::default())))
|
Ok(empty_response(StatusCode::NO_CONTENT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2447,13 +2533,20 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(response.overrides.is_empty());
|
assert!(response.overrides.is_empty());
|
||||||
assert!(response.endpoints.contains(&"GET /v1/{prefix}/namespaces"));
|
assert!(response.endpoints.contains(&"GET /v1/{prefix}/namespaces"));
|
||||||
|
assert!(response.endpoints.contains(&"HEAD /v1/{prefix}/namespaces/{namespace}"));
|
||||||
assert!(
|
assert!(
|
||||||
response
|
response
|
||||||
.endpoints
|
.endpoints
|
||||||
.contains(&"GET /v1/{prefix}/namespaces/{namespace}/tables/{table}")
|
.contains(&"GET /v1/{prefix}/namespaces/{namespace}/tables/{table}")
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
response
|
||||||
|
.endpoints
|
||||||
|
.contains(&"HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}")
|
||||||
|
);
|
||||||
assert!(response.endpoints.contains(&"GET /{warehouse}/namespaces"));
|
assert!(response.endpoints.contains(&"GET /{warehouse}/namespaces"));
|
||||||
assert!(response.endpoints.contains(&"POST /{warehouse}/namespaces"));
|
assert!(response.endpoints.contains(&"POST /{warehouse}/namespaces"));
|
||||||
|
assert!(response.endpoints.contains(&"HEAD /{warehouse}/namespaces/{namespace}"));
|
||||||
assert!(
|
assert!(
|
||||||
response
|
response
|
||||||
.endpoints
|
.endpoints
|
||||||
@@ -2469,6 +2562,11 @@ mod tests {
|
|||||||
.endpoints
|
.endpoints
|
||||||
.contains(&"GET /{warehouse}/namespaces/{namespace}/tables/{table}")
|
.contains(&"GET /{warehouse}/namespaces/{namespace}/tables/{table}")
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
response
|
||||||
|
.endpoints
|
||||||
|
.contains(&"HEAD /{warehouse}/namespaces/{namespace}/tables/{table}")
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
response
|
response
|
||||||
.endpoints
|
.endpoints
|
||||||
@@ -2495,11 +2593,13 @@ mod tests {
|
|||||||
("RestListNamespacesHandler", "AdminAction::GetTableNamespaceAction"),
|
("RestListNamespacesHandler", "AdminAction::GetTableNamespaceAction"),
|
||||||
("RestCreateNamespaceHandler", "AdminAction::SetTableNamespaceAction"),
|
("RestCreateNamespaceHandler", "AdminAction::SetTableNamespaceAction"),
|
||||||
("RestGetNamespaceHandler", "AdminAction::GetTableNamespaceAction"),
|
("RestGetNamespaceHandler", "AdminAction::GetTableNamespaceAction"),
|
||||||
|
("RestNamespaceExistsHandler", "AdminAction::GetTableNamespaceAction"),
|
||||||
("RestDropNamespaceHandler", "AdminAction::DeleteTableNamespaceAction"),
|
("RestDropNamespaceHandler", "AdminAction::DeleteTableNamespaceAction"),
|
||||||
("RestListTablesHandler", "AdminAction::GetTableAction"),
|
("RestListTablesHandler", "AdminAction::GetTableAction"),
|
||||||
("RestCreateTableHandler", "AdminAction::CreateTableAction"),
|
("RestCreateTableHandler", "AdminAction::CreateTableAction"),
|
||||||
("RestRegisterTableHandler", "AdminAction::RegisterTableAction"),
|
("RestRegisterTableHandler", "AdminAction::RegisterTableAction"),
|
||||||
("RestLoadTableHandler", "AdminAction::GetTableMetadataAction"),
|
("RestLoadTableHandler", "AdminAction::GetTableMetadataAction"),
|
||||||
|
("RestTableExistsHandler", "AdminAction::GetTableAction"),
|
||||||
("RestCommitTableHandler", "AdminAction::CommitTableAction"),
|
("RestCommitTableHandler", "AdminAction::CommitTableAction"),
|
||||||
("RestDropTableHandler", "AdminAction::DeleteTableAction"),
|
("RestDropTableHandler", "AdminAction::DeleteTableAction"),
|
||||||
("GetTableMetadataLocationHandler", "AdminAction::GetTableMetadataLocationAction"),
|
("GetTableMetadataLocationHandler", "AdminAction::GetTableMetadataLocationAction"),
|
||||||
@@ -2530,6 +2630,7 @@ mod tests {
|
|||||||
|
|
||||||
for (handler, action) in [
|
for (handler, action) in [
|
||||||
("RestLoadTableHandler", "AdminAction::GetTableMetadataAction"),
|
("RestLoadTableHandler", "AdminAction::GetTableMetadataAction"),
|
||||||
|
("RestTableExistsHandler", "AdminAction::GetTableAction"),
|
||||||
("RestCommitTableHandler", "AdminAction::CommitTableAction"),
|
("RestCommitTableHandler", "AdminAction::CommitTableAction"),
|
||||||
("RestDropTableHandler", "AdminAction::DeleteTableAction"),
|
("RestDropTableHandler", "AdminAction::DeleteTableAction"),
|
||||||
("GetTableMetadataLocationHandler", "AdminAction::GetTableMetadataLocationAction"),
|
("GetTableMetadataLocationHandler", "AdminAction::GetTableMetadataLocationAction"),
|
||||||
@@ -2594,11 +2695,13 @@ mod tests {
|
|||||||
let _: &RestListNamespacesHandler = &LIST_NAMESPACES_HANDLER;
|
let _: &RestListNamespacesHandler = &LIST_NAMESPACES_HANDLER;
|
||||||
let _: &RestCreateNamespaceHandler = &CREATE_NAMESPACE_HANDLER;
|
let _: &RestCreateNamespaceHandler = &CREATE_NAMESPACE_HANDLER;
|
||||||
let _: &RestGetNamespaceHandler = &GET_NAMESPACE_HANDLER;
|
let _: &RestGetNamespaceHandler = &GET_NAMESPACE_HANDLER;
|
||||||
|
let _: &RestNamespaceExistsHandler = &NAMESPACE_EXISTS_HANDLER;
|
||||||
let _: &RestDropNamespaceHandler = &DROP_NAMESPACE_HANDLER;
|
let _: &RestDropNamespaceHandler = &DROP_NAMESPACE_HANDLER;
|
||||||
let _: &RestListTablesHandler = &LIST_TABLES_HANDLER;
|
let _: &RestListTablesHandler = &LIST_TABLES_HANDLER;
|
||||||
let _: &RestCreateTableHandler = &CREATE_TABLE_HANDLER;
|
let _: &RestCreateTableHandler = &CREATE_TABLE_HANDLER;
|
||||||
let _: &RestRegisterTableHandler = ®ISTER_TABLE_HANDLER;
|
let _: &RestRegisterTableHandler = ®ISTER_TABLE_HANDLER;
|
||||||
let _: &RestLoadTableHandler = &LOAD_TABLE_HANDLER;
|
let _: &RestLoadTableHandler = &LOAD_TABLE_HANDLER;
|
||||||
|
let _: &RestTableExistsHandler = &TABLE_EXISTS_HANDLER;
|
||||||
let _: &RestCommitTableHandler = &COMMIT_TABLE_HANDLER;
|
let _: &RestCommitTableHandler = &COMMIT_TABLE_HANDLER;
|
||||||
let _: &RestDropTableHandler = &DROP_TABLE_HANDLER;
|
let _: &RestDropTableHandler = &DROP_TABLE_HANDLER;
|
||||||
let _: &GetTableMetadataLocationHandler = &GET_TABLE_METADATA_LOCATION_HANDLER;
|
let _: &GetTableMetadataLocationHandler = &GET_TABLE_METADATA_LOCATION_HANDLER;
|
||||||
@@ -2617,11 +2720,13 @@ mod tests {
|
|||||||
assert_operation::<RestListNamespacesHandler>();
|
assert_operation::<RestListNamespacesHandler>();
|
||||||
assert_operation::<RestCreateNamespaceHandler>();
|
assert_operation::<RestCreateNamespaceHandler>();
|
||||||
assert_operation::<RestGetNamespaceHandler>();
|
assert_operation::<RestGetNamespaceHandler>();
|
||||||
|
assert_operation::<RestNamespaceExistsHandler>();
|
||||||
assert_operation::<RestDropNamespaceHandler>();
|
assert_operation::<RestDropNamespaceHandler>();
|
||||||
assert_operation::<RestListTablesHandler>();
|
assert_operation::<RestListTablesHandler>();
|
||||||
assert_operation::<RestCreateTableHandler>();
|
assert_operation::<RestCreateTableHandler>();
|
||||||
assert_operation::<RestRegisterTableHandler>();
|
assert_operation::<RestRegisterTableHandler>();
|
||||||
assert_operation::<RestLoadTableHandler>();
|
assert_operation::<RestLoadTableHandler>();
|
||||||
|
assert_operation::<RestTableExistsHandler>();
|
||||||
assert_operation::<RestCommitTableHandler>();
|
assert_operation::<RestCommitTableHandler>();
|
||||||
assert_operation::<RestDropTableHandler>();
|
assert_operation::<RestDropTableHandler>();
|
||||||
assert_operation::<GetTableMetadataLocationHandler>();
|
assert_operation::<GetTableMetadataLocationHandler>();
|
||||||
@@ -2821,6 +2926,94 @@ mod tests {
|
|||||||
assert_eq!(response.identifiers[0].name, "events");
|
assert_eq!(response.identifiers[0].name, "events");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn namespace_exists_status_uses_head_rest_semantics() {
|
||||||
|
let store = TestTableCatalogStore::default();
|
||||||
|
let namespace = crate::table_catalog::Namespace::parse("analytics").expect("namespace should parse");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
namespace_exists_status(&store, "warehouse", &namespace)
|
||||||
|
.await
|
||||||
|
.expect("missing namespace check should succeed"),
|
||||||
|
StatusCode::NOT_FOUND
|
||||||
|
);
|
||||||
|
|
||||||
|
create_namespace_response(
|
||||||
|
&store,
|
||||||
|
"warehouse",
|
||||||
|
CreateNamespaceRequest {
|
||||||
|
namespace: vec!["analytics".to_string()],
|
||||||
|
properties: BTreeMap::new(),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("namespace should be created");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
namespace_exists_status(&store, "warehouse", &namespace)
|
||||||
|
.await
|
||||||
|
.expect("existing namespace check should succeed"),
|
||||||
|
StatusCode::NO_CONTENT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn table_exists_status_uses_head_rest_semantics() {
|
||||||
|
let store = TestTableCatalogStore::default();
|
||||||
|
let namespace = crate::table_catalog::Namespace::parse("analytics").expect("namespace should parse");
|
||||||
|
|
||||||
|
create_namespace_response(
|
||||||
|
&store,
|
||||||
|
"warehouse",
|
||||||
|
CreateNamespaceRequest {
|
||||||
|
namespace: vec!["analytics".to_string()],
|
||||||
|
properties: BTreeMap::new(),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("namespace should be created");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table_exists_status(&store, "warehouse", &namespace, "events")
|
||||||
|
.await
|
||||||
|
.expect("missing table check should succeed"),
|
||||||
|
StatusCode::NOT_FOUND
|
||||||
|
);
|
||||||
|
|
||||||
|
store
|
||||||
|
.create_table(crate::table_catalog::TableEntry {
|
||||||
|
version: crate::table_catalog::TABLE_CATALOG_ENTRY_VERSION,
|
||||||
|
table_bucket: "warehouse".to_string(),
|
||||||
|
namespace: namespace.public_name(),
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("table should be created");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table_exists_status(&store, "warehouse", &namespace, "events")
|
||||||
|
.await
|
||||||
|
.expect("existing table check should succeed"),
|
||||||
|
StatusCode::NO_CONTENT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn register_table_request_builds_initial_table_entry() {
|
fn register_table_request_builds_initial_table_entry() {
|
||||||
let namespace = crate::table_catalog::Namespace::parse("analytics").expect("namespace should parse");
|
let namespace = crate::table_catalog::Namespace::parse("analytics").expect("namespace should parse");
|
||||||
|
|||||||
@@ -633,6 +633,12 @@ pub const ADMIN_ROUTE_POLICY_SPECS: &[AdminRouteSpec] = &[
|
|||||||
GET_TABLE_NAMESPACE,
|
GET_TABLE_NAMESPACE,
|
||||||
RouteRiskLevel::Sensitive,
|
RouteRiskLevel::Sensitive,
|
||||||
),
|
),
|
||||||
|
admin(
|
||||||
|
HttpMethod::Head,
|
||||||
|
"/iceberg/v1/{warehouse}/namespaces/{namespace}",
|
||||||
|
GET_TABLE_NAMESPACE,
|
||||||
|
RouteRiskLevel::Sensitive,
|
||||||
|
),
|
||||||
admin(
|
admin(
|
||||||
HttpMethod::Delete,
|
HttpMethod::Delete,
|
||||||
"/iceberg/v1/{warehouse}/namespaces/{namespace}",
|
"/iceberg/v1/{warehouse}/namespaces/{namespace}",
|
||||||
@@ -663,6 +669,12 @@ pub const ADMIN_ROUTE_POLICY_SPECS: &[AdminRouteSpec] = &[
|
|||||||
GET_TABLE_METADATA,
|
GET_TABLE_METADATA,
|
||||||
RouteRiskLevel::Sensitive,
|
RouteRiskLevel::Sensitive,
|
||||||
),
|
),
|
||||||
|
admin(
|
||||||
|
HttpMethod::Head,
|
||||||
|
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
GET_TABLE,
|
||||||
|
RouteRiskLevel::Sensitive,
|
||||||
|
),
|
||||||
admin(
|
admin(
|
||||||
HttpMethod::Post,
|
HttpMethod::Post,
|
||||||
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
@@ -766,6 +778,12 @@ pub const ADMIN_ROUTE_POLICY_SPECS: &[AdminRouteSpec] = &[
|
|||||||
GET_TABLE_NAMESPACE,
|
GET_TABLE_NAMESPACE,
|
||||||
RouteRiskLevel::Sensitive,
|
RouteRiskLevel::Sensitive,
|
||||||
),
|
),
|
||||||
|
admin(
|
||||||
|
HttpMethod::Head,
|
||||||
|
"/_iceberg/v1/{warehouse}/namespaces/{namespace}",
|
||||||
|
GET_TABLE_NAMESPACE,
|
||||||
|
RouteRiskLevel::Sensitive,
|
||||||
|
),
|
||||||
admin(
|
admin(
|
||||||
HttpMethod::Delete,
|
HttpMethod::Delete,
|
||||||
"/_iceberg/v1/{warehouse}/namespaces/{namespace}",
|
"/_iceberg/v1/{warehouse}/namespaces/{namespace}",
|
||||||
@@ -796,6 +814,12 @@ pub const ADMIN_ROUTE_POLICY_SPECS: &[AdminRouteSpec] = &[
|
|||||||
GET_TABLE_METADATA,
|
GET_TABLE_METADATA,
|
||||||
RouteRiskLevel::Sensitive,
|
RouteRiskLevel::Sensitive,
|
||||||
),
|
),
|
||||||
|
admin(
|
||||||
|
HttpMethod::Head,
|
||||||
|
"/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
GET_TABLE,
|
||||||
|
RouteRiskLevel::Sensitive,
|
||||||
|
),
|
||||||
admin(
|
admin(
|
||||||
HttpMethod::Post,
|
HttpMethod::Post,
|
||||||
"/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
@@ -1041,11 +1065,13 @@ mod tests {
|
|||||||
let table_specs = ADMIN_ROUTE_POLICY_SPECS
|
let table_specs = ADMIN_ROUTE_POLICY_SPECS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|spec| spec.path().starts_with("/iceberg/v1") || spec.path().starts_with("/_iceberg/v1"));
|
.filter(|spec| spec.path().starts_with("/iceberg/v1") || spec.path().starts_with("/_iceberg/v1"));
|
||||||
assert_eq!(table_specs.count(), 46);
|
assert_eq!(table_specs.count(), 50);
|
||||||
assert_action(HttpMethod::Put, "/iceberg/v1/buckets/{warehouse}", SET_TABLE_BUCKET);
|
assert_action(HttpMethod::Put, "/iceberg/v1/buckets/{warehouse}", SET_TABLE_BUCKET);
|
||||||
assert_action(HttpMethod::Get, "/_iceberg/v1/buckets/{warehouse}", GET_TABLE_BUCKET);
|
assert_action(HttpMethod::Get, "/_iceberg/v1/buckets/{warehouse}", GET_TABLE_BUCKET);
|
||||||
assert_action(HttpMethod::Get, "/iceberg/v1/{warehouse}/namespaces", GET_TABLE_NAMESPACE);
|
assert_action(HttpMethod::Get, "/iceberg/v1/{warehouse}/namespaces", GET_TABLE_NAMESPACE);
|
||||||
assert_action(HttpMethod::Get, "/_iceberg/v1/{warehouse}/namespaces", GET_TABLE_NAMESPACE);
|
assert_action(HttpMethod::Get, "/_iceberg/v1/{warehouse}/namespaces", GET_TABLE_NAMESPACE);
|
||||||
|
assert_action(HttpMethod::Head, "/iceberg/v1/{warehouse}/namespaces/{namespace}", GET_TABLE_NAMESPACE);
|
||||||
|
assert_action(HttpMethod::Head, "/_iceberg/v1/{warehouse}/namespaces/{namespace}", GET_TABLE_NAMESPACE);
|
||||||
assert_action(HttpMethod::Post, "/iceberg/v1/{warehouse}/namespaces/{namespace}/tables", CREATE_TABLE);
|
assert_action(HttpMethod::Post, "/iceberg/v1/{warehouse}/namespaces/{namespace}/tables", CREATE_TABLE);
|
||||||
assert_action(HttpMethod::Post, "/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables", CREATE_TABLE);
|
assert_action(HttpMethod::Post, "/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables", CREATE_TABLE);
|
||||||
assert_action(
|
assert_action(
|
||||||
@@ -1058,6 +1084,16 @@ mod tests {
|
|||||||
"/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
GET_TABLE_METADATA,
|
GET_TABLE_METADATA,
|
||||||
);
|
);
|
||||||
|
assert_action(
|
||||||
|
HttpMethod::Head,
|
||||||
|
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
GET_TABLE,
|
||||||
|
);
|
||||||
|
assert_action(
|
||||||
|
HttpMethod::Head,
|
||||||
|
"/_iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
GET_TABLE,
|
||||||
|
);
|
||||||
assert_action(
|
assert_action(
|
||||||
HttpMethod::Post,
|
HttpMethod::Post,
|
||||||
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ fn expected_admin_route_matrix() -> Vec<RouteMatrixEntry> {
|
|||||||
table_route_sample(Method::GET, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
table_route_sample(Method::GET, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
||||||
table_route_sample(Method::POST, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
table_route_sample(Method::POST, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
||||||
table_route_sample(Method::GET, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
table_route_sample(Method::GET, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||||
|
table_route_sample(Method::HEAD, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||||
table_route_sample(Method::DELETE, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
table_route_sample(Method::DELETE, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||||
table_route_sample(
|
table_route_sample(
|
||||||
Method::GET,
|
Method::GET,
|
||||||
@@ -309,6 +310,11 @@ fn expected_admin_route_matrix() -> Vec<RouteMatrixEntry> {
|
|||||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
"/analytics/namespaces/sales/tables/orders",
|
"/analytics/namespaces/sales/tables/orders",
|
||||||
),
|
),
|
||||||
|
table_route_sample(
|
||||||
|
Method::HEAD,
|
||||||
|
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
"/analytics/namespaces/sales/tables/orders",
|
||||||
|
),
|
||||||
table_route_sample(
|
table_route_sample(
|
||||||
Method::POST,
|
Method::POST,
|
||||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
@@ -375,6 +381,7 @@ fn expected_admin_route_matrix() -> Vec<RouteMatrixEntry> {
|
|||||||
compat_table_route_sample(Method::GET, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
compat_table_route_sample(Method::GET, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
||||||
compat_table_route_sample(Method::POST, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
compat_table_route_sample(Method::POST, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
||||||
compat_table_route_sample(Method::GET, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
compat_table_route_sample(Method::GET, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||||
|
compat_table_route_sample(Method::HEAD, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||||
compat_table_route_sample(Method::DELETE, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
compat_table_route_sample(Method::DELETE, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||||
compat_table_route_sample(
|
compat_table_route_sample(
|
||||||
Method::GET,
|
Method::GET,
|
||||||
@@ -396,6 +403,11 @@ fn expected_admin_route_matrix() -> Vec<RouteMatrixEntry> {
|
|||||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
"/analytics/namespaces/sales/tables/orders",
|
"/analytics/namespaces/sales/tables/orders",
|
||||||
),
|
),
|
||||||
|
compat_table_route_sample(
|
||||||
|
Method::HEAD,
|
||||||
|
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
"/analytics/namespaces/sales/tables/orders",
|
||||||
|
),
|
||||||
compat_table_route_sample(
|
compat_table_route_sample(
|
||||||
Method::POST,
|
Method::POST,
|
||||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||||
|
|||||||
Reference in New Issue
Block a user