diff --git a/crates/catalog/rest/src/catalog.rs b/crates/catalog/rest/src/catalog.rs index 6d5fecb63..6984def45 100644 --- a/crates/catalog/rest/src/catalog.rs +++ b/crates/catalog/rest/src/catalog.rs @@ -37,7 +37,8 @@ use self::_serde::{ NamespaceSerde, RenameTableRequest, NO_CONTENT, OK, }; -const ICEBERG_REST_SPEC_VERSION: &str = "0.14.1"; +const ICEBERG_REST_SPEC_VERSION: &str = "1.14"; +const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); const PATH_V1: &str = "v1"; /// Rest catalog configuration. @@ -93,19 +94,20 @@ impl RestCatalogConfig { fn try_create_rest_client(&self) -> Result { //TODO: We will add oauth, ssl config, sigv4 later - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - HeaderValue::from_static("application/json"), - ); - headers.insert( - HeaderName::from_static("x-client-version"), - HeaderValue::from_static(ICEBERG_REST_SPEC_VERSION), - ); - headers.insert( - header::USER_AGENT, - HeaderValue::from_str(&format!("iceberg-rs/{}", env!("CARGO_PKG_VERSION"))).unwrap(), - ); + let headers = HeaderMap::from_iter([ + ( + header::CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ), + ( + HeaderName::from_static("x-client-version"), + HeaderValue::from_static(ICEBERG_REST_SPEC_VERSION), + ), + ( + header::USER_AGENT, + HeaderValue::from_str(&format!("iceberg-rs/{}", CARGO_PKG_VERSION)).unwrap(), + ), + ]); Ok(HttpClient( Client::builder().default_headers(headers).build()?, @@ -257,6 +259,19 @@ impl Catalog for RestCatalog { )) } + async fn namespace_exists(&self, ns: &NamespaceIdent) -> Result { + let request = self + .client + .0 + .head(self.config.namespace_endpoint(ns)) + .build()?; + + self.client + .execute::(request) + .await + .map(|_| true) + } + /// Drop a namespace from the catalog. async fn drop_namespace(&self, namespace: &NamespaceIdent) -> Result<()> { let request = self @@ -688,6 +703,31 @@ mod tests { get_ns_mock.assert_async().await; } + #[tokio::test] + async fn check_namespace_exists() { + let mut server = Server::new_async().await; + + let config_mock = create_config_mock(&mut server).await; + + let get_ns_mock = server + .mock("HEAD", "/v1/namespaces/ns1") + .with_status(204) + .create_async() + .await; + + let catalog = RestCatalog::new(RestCatalogConfig::builder().uri(server.url()).build()) + .await + .unwrap(); + + assert!(catalog + .namespace_exists(&NamespaceIdent::new("ns1".to_string())) + .await + .unwrap()); + + config_mock.assert_async().await; + get_ns_mock.assert_async().await; + } + #[tokio::test] async fn test_drop_namespace() { let mut server = Server::new_async().await; diff --git a/crates/iceberg/src/catalog/mod.rs b/crates/iceberg/src/catalog/mod.rs index 4f391b2fe..d58eaa2bd 100644 --- a/crates/iceberg/src/catalog/mod.rs +++ b/crates/iceberg/src/catalog/mod.rs @@ -44,6 +44,9 @@ pub trait Catalog { /// Get a namespace information from the catalog. async fn get_namespace(&self, namespace: &NamespaceIdent) -> Result; + /// Check if namespace exists in catalog. + async fn namespace_exists(&self, namesace: &NamespaceIdent) -> Result; + /// Update a namespace inside the catalog. /// /// # Behavior