use crate::{
clients::{offers_client, ClientContext, ContainerClient},
feed::QueryItemIterator,
models::ResourceResponse,
models::{ContainerProperties, DatabaseProperties, ThroughputProperties},
options::{
CreateContainerOptions, DeleteDatabaseOptions, QueryContainersOptions, ReadDatabaseOptions,
ThroughputOptions,
},
Query,
};
use azure_data_cosmos_driver::models::{CosmosOperation, DatabaseReference};
use super::ThroughputPoller;
pub struct DatabaseClient {
database_id: String,
context: ClientContext,
database_ref: DatabaseReference,
}
impl DatabaseClient {
pub(crate) fn new(context: ClientContext, database_id: &str) -> Self {
let database_id = database_id.to_string();
let database_ref =
DatabaseReference::from_name(context.driver.account().clone(), database_id.clone());
Self {
database_id,
context,
database_ref,
}
}
pub async fn container_client(&self, name: &str) -> crate::Result<ContainerClient> {
ContainerClient::new(self.context.clone(), name, &self.database_id).await
}
pub fn id(&self) -> &str {
&self.database_id
}
pub async fn read(
&self,
options: Option<ReadDatabaseOptions>,
) -> crate::Result<ResourceResponse<DatabaseProperties>> {
let options = options.unwrap_or_default();
let operation = CosmosOperation::read_database(self.database_ref.clone());
let driver_response = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
Ok(ResourceResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
))
}
pub async fn query_containers(
&self,
query: impl Into<Query>,
options: Option<QueryContainersOptions>,
) -> crate::Result<QueryItemIterator<ContainerProperties>> {
let options = options.unwrap_or_default();
let query = query.into();
let initial_operation = CosmosOperation::query_containers(self.database_ref.clone())
.with_body(serde_json::to_vec(&query)?);
let operation_options = options.operation;
let plan = self
.context
.driver
.plan_operation(initial_operation, &operation_options, None)
.await?;
Ok(QueryItemIterator::new(
self.context.driver.clone(),
None,
plan,
operation_options,
))
}
#[doc = include_str!("../../docs/control-plane-warning.md")]
#[doc = include_str!("../../docs/control-plane-always-returns-body.md")]
pub async fn create_container(
&self,
properties: ContainerProperties,
options: Option<CreateContainerOptions>,
) -> crate::Result<ResourceResponse<ContainerProperties>> {
let options = options.unwrap_or_default();
let body = serde_json::to_vec(&properties)?;
let mut operation =
CosmosOperation::create_container(self.database_ref.clone()).with_body(body);
if let Some(throughput) = &options.throughput {
let mut headers = azure_data_cosmos_driver::models::CosmosRequestHeaders::new();
throughput.apply_headers(&mut headers);
operation = operation.with_request_headers(headers);
}
let mut operation_options = options.operation;
operation_options.content_response_on_write =
Some(azure_data_cosmos_driver::options::ContentResponseOnWrite::Enabled);
let driver_response = self
.context
.driver
.execute_singleton_operation(operation, operation_options)
.await?;
Ok(ResourceResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
))
}
#[doc = include_str!("../../docs/control-plane-warning.md")]
pub async fn delete(
&self,
options: Option<DeleteDatabaseOptions>,
) -> crate::Result<ResourceResponse<()>> {
let options = options.unwrap_or_default();
let operation = CosmosOperation::delete_database(self.database_ref.clone());
let driver_response = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
Ok(ResourceResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
))
}
pub async fn read_throughput(
&self,
options: Option<ThroughputOptions>,
) -> crate::Result<Option<ThroughputProperties>> {
let options = options.unwrap_or_default();
let db = self.read(None).await?.into_model()?;
let resource_id = resource_id_or_error(db.system_properties.resource_id, "database")?;
offers_client::find_offer(
&self.context.driver,
self.context.driver.account(),
&resource_id,
options.operation,
)
.await
}
#[doc = include_str!("../../docs/control-plane-always-returns-body.md")]
pub async fn begin_replace_throughput(
&self,
throughput: ThroughputProperties,
options: Option<ThroughputOptions>,
) -> crate::Result<ThroughputPoller> {
let options = options.unwrap_or_default();
let db = self.read(None).await?.into_model()?;
let resource_id = resource_id_or_error(db.system_properties.resource_id, "database")?;
offers_client::begin_replace(
self.context.driver.clone(),
self.context.driver.account().clone(),
&resource_id,
throughput,
options.operation,
)
.await
}
}
fn resource_id_or_error(rid: Option<String>, resource_kind: &str) -> crate::Result<String> {
debug_assert!(
rid.is_some(),
"service should always return a '_rid' for a {resource_kind}"
);
rid.ok_or_else(|| {
crate::DriverCosmosError::builder()
.with_status(crate::error::CosmosStatus::SERVICE_RETURNED_OBJECT_WITHOUT_RID)
.with_message(format!(
"service did not return a '_rid' for a {resource_kind}; cannot resolve the throughput offer"
))
.build()
.into()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code, unreachable_code, unused_variables)]
fn _assert_futures_are_send() {
fn assert_send<T: Send>(_: T) {}
let client: &DatabaseClient = todo!();
assert_send(client.container_client(todo!()));
assert_send(client.read(todo!()));
assert_send(client.query_containers(Query::from("SELECT * FROM c"), todo!()));
assert_send(client.create_container(todo!(), todo!()));
assert_send(client.delete(todo!()));
assert_send(client.read_throughput(todo!()));
assert_send(client.begin_replace_throughput(todo!(), todo!()));
}
}