use std::{collections::HashMap, fmt::Debug, sync::Arc};
use uclient::ClientExt;
use log::trace;
use maybe_async::maybe_async;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::value::Value;
use url::Url;
use crate::graph::{GraphCollection, GraphResponse, GHARIAL_API_PATH};
use crate::index::INDEX_API_PATH;
use crate::transaction::TRANSACTION_HEADER;
use crate::{
analyzer::{AnalyzerDescription, AnalyzerInfo},
aql::{AqlQuery, Cursor},
collection::{
options::{CreateOptions, CreateParameters},
response::{Info, Properties},
Collection, CollectionType,
},
connection::Version,
graph::Graph,
index::{DeleteIndexResponse, Index, IndexCollection},
response::{deserialize_response, ArangoResult},
transaction::ArangoTransaction,
transaction::Transaction,
transaction::TransactionList,
transaction::TransactionSettings,
transaction::TransactionState,
view::ArangoSearchViewProperties,
view::ArangoSearchViewPropertiesOptions,
view::ViewDescription,
view::{View, ViewOptions},
ClientError,
};
#[derive(Debug, Clone)]
pub struct Database<C: ClientExt> {
name: String,
base_url: Url,
session: Arc<C>,
}
impl<'a, C: ClientExt> Database<C> {
pub(crate) fn new<T: Into<String>>(name: T, arango_url: &Url, session: Arc<C>) -> Database<C> {
let name = name.into();
let path = format!("/_db/{}/", name.as_str());
let url = arango_url.join(path.as_str()).unwrap();
Database {
name,
session,
base_url: url,
}
}
#[maybe_async]
pub async fn accessible_collections(&self) -> Result<Vec<Info>, ClientError> {
let url = self.base_url.join("_api/collection").unwrap();
trace!(
"Retrieving collections from {:?}: {}",
self.name,
url.as_str()
);
let resp = self.session.get(url, "").await?;
let result: ArangoResult<Vec<Info>> = deserialize_response(resp.body())?;
trace!("Collections retrieved");
Ok(result.unwrap())
}
pub fn url(&self) -> &Url {
&self.base_url
}
pub fn name(&self) -> &str {
&self.name
}
pub fn session(&self) -> Arc<C> {
Arc::clone(&self.session)
}
#[maybe_async]
pub async fn collection(&self, name: &str) -> Result<Collection<C>, ClientError> {
let url = self
.base_url
.join(&format!("_api/collection/{}", name))
.unwrap();
let resp: Info = deserialize_response(self.session.get(url, "").await?.body())?;
Ok(Collection::from_response(self, &resp))
}
#[maybe_async]
pub async fn create_collection_with_options<'f>(
&self,
options: CreateOptions<'f>,
parameters: CreateParameters,
) -> Result<Collection<C>, ClientError> {
let mut url = self.base_url.join("_api/collection").unwrap();
let query = serde_qs::to_string(¶meters).unwrap();
url.set_query(Some(query.as_str()));
let resp = self
.session
.post(url, &serde_json::to_string(&options)?)
.await?;
let result: Properties = deserialize_response(resp.body())?;
self.collection(&result.info.name).await
}
#[maybe_async]
pub async fn create_collection(&self, name: &str) -> Result<Collection<C>, ClientError> {
self.create_collection_with_options(
CreateOptions::builder().name(name).build(),
Default::default(),
)
.await
}
#[maybe_async]
pub async fn create_edge_collection(&self, name: &str) -> Result<Collection<C>, ClientError> {
self.create_collection_with_options(
CreateOptions::builder()
.name(name)
.collection_type(CollectionType::Edge)
.build(),
Default::default(),
)
.await
}
#[maybe_async]
pub async fn drop_collection(&self, name: &str) -> Result<String, ClientError> {
let url_path = format!("_api/collection/{}", name);
let url = self.base_url.join(&url_path).unwrap();
#[derive(Debug, Deserialize)]
struct DropCollectionResponse {
id: String,
}
let resp: DropCollectionResponse =
deserialize_response(self.session.delete(url, "").await?.body())?;
Ok(resp.id)
}
#[maybe_async]
pub async fn arango_version(&self) -> Result<Version, ClientError> {
let url = self.base_url.join("_api/version").unwrap();
let resp = self.session.get(url, "").await?;
let version: Version = serde_json::from_str(resp.body())?;
Ok(version)
}
#[maybe_async]
pub async fn info(&self) -> Result<DatabaseDetails, ClientError> {
let url = self.base_url.join("_api/database/current").unwrap();
let resp = self.session.get(url, "").await?;
let res: ArangoResult<DatabaseDetails> = deserialize_response(resp.body())?;
Ok(res.unwrap())
}
#[maybe_async]
pub async fn aql_query_batch<R>(&self, aql: AqlQuery<'_>) -> Result<Cursor<R>, ClientError>
where
R: DeserializeOwned,
{
let url = self.base_url.join("_api/cursor").unwrap();
let resp = self
.session
.post(url, &serde_json::to_string(&aql)?)
.await?;
deserialize_response(resp.body())
}
#[maybe_async]
pub async fn aql_next_batch<R>(&self, cursor_id: &str) -> Result<Cursor<R>, ClientError>
where
R: DeserializeOwned,
{
let url = self
.base_url
.join(&format!("_api/cursor/{}", cursor_id))
.unwrap();
let resp = self.session.put(url, "").await?;
deserialize_response(resp.body())
}
#[maybe_async]
async fn aql_fetch_all<R>(&self, response: Cursor<R>) -> Result<Vec<R>, ClientError>
where
R: DeserializeOwned,
{
let mut response_cursor = response;
let mut results: Vec<R> = Vec::new();
loop {
results.extend(response_cursor.result.into_iter());
if response_cursor.more {
let id = response_cursor.id.unwrap().clone();
response_cursor = self.aql_next_batch(id.as_str()).await?;
} else {
break;
}
}
Ok(results)
}
#[maybe_async]
pub async fn aql_query<R>(&self, aql: AqlQuery<'_>) -> Result<Vec<R>, ClientError>
where
R: DeserializeOwned,
{
let response = self.aql_query_batch(aql).await?;
if response.more {
self.aql_fetch_all(response).await
} else {
Ok(response.result)
}
}
#[maybe_async]
pub async fn aql_str<R>(&self, query: &str) -> Result<Vec<R>, ClientError>
where
R: DeserializeOwned,
{
let aql = AqlQuery::builder().query(query).build();
self.aql_query(aql).await
}
#[maybe_async]
pub async fn aql_bind_vars<R>(
&self,
query: &str,
bind_vars: HashMap<&str, Value>,
) -> Result<Vec<R>, ClientError>
where
R: DeserializeOwned,
{
let aql = AqlQuery::builder()
.query(query)
.bind_vars(bind_vars)
.build();
self.aql_query(aql).await
}
#[maybe_async]
pub async fn create_index(
&self,
collection: &str,
index: &Index,
) -> Result<Index, ClientError> {
let mut url = self.base_url.join(INDEX_API_PATH).unwrap();
url.set_query(Some(&format!("collection={}", collection)));
let resp = self
.session
.post(url, &serde_json::to_string(&index)?)
.await?;
let result: Index = deserialize_response::<Index>(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn index(&self, id: &str) -> Result<Index, ClientError> {
let url = self
.base_url
.join(&format!("{}/{}", INDEX_API_PATH, id))
.unwrap();
let resp = self.session.get(url, "").await?;
let result: Index = deserialize_response::<Index>(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn indexes(&self, collection: &str) -> Result<IndexCollection, ClientError> {
let mut url = self.base_url.join(INDEX_API_PATH).unwrap();
url.set_query(Some(&format!("collection={}", collection)));
let resp = self.session.get(url, "").await?;
let result: IndexCollection = deserialize_response::<IndexCollection>(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn delete_index(&self, id: &str) -> Result<DeleteIndexResponse, ClientError> {
let url = self
.base_url
.join(&format!("{}/{}", INDEX_API_PATH, id))
.unwrap();
let resp = self.session.delete(url, "").await?;
let result: DeleteIndexResponse = deserialize_response::<DeleteIndexResponse>(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn create_graph(
&self,
graph: Graph,
wait_for_sync: bool,
) -> Result<Graph, ClientError> {
let mut url = self.base_url.join(GHARIAL_API_PATH).unwrap();
url.set_query(Some(&format!("waitForSync={}", wait_for_sync)));
let resp = self
.session
.post(url, &serde_json::to_string(&graph)?)
.await?;
let result: GraphResponse = deserialize_response::<GraphResponse>(resp.body())?;
Ok(result.graph)
}
#[maybe_async]
pub async fn graph(&self, name: &str) -> Result<Graph, ClientError> {
let url = self
.base_url
.join(&format!("{}/{}", GHARIAL_API_PATH, name))
.unwrap();
let resp = self.session.get(url, "").await?;
let result: GraphResponse = deserialize_response::<GraphResponse>(resp.body())?;
Ok(result.graph)
}
#[maybe_async]
pub async fn graphs(&self) -> Result<GraphCollection, ClientError> {
let url = self.base_url.join(GHARIAL_API_PATH).unwrap();
let resp = self.session.get(url, "").await?;
let result: GraphCollection = deserialize_response::<GraphCollection>(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn drop_graph(&self, name: &str, drop_collections: bool) -> Result<(), ClientError> {
let mut url = self
.base_url
.join(&format!("{}/{}", GHARIAL_API_PATH, name))
.unwrap();
url.set_query(Some(&format!("dropCollections={}", drop_collections)));
self.session.delete(url, "").await?;
Ok(())
}
#[maybe_async]
pub async fn list_transactions(&self) -> Result<Vec<TransactionState>, ClientError> {
let url = self.base_url.join("_api/transaction").unwrap();
let resp = self.session.get(url, "").await?;
let result: TransactionList = deserialize_response(resp.body())?;
Ok(result.transactions)
}
#[maybe_async]
pub async fn begin_transaction(
&self,
transaction_settings: TransactionSettings,
) -> Result<Transaction<C>, ClientError> {
let url = self.base_url.join("_api/transaction/begin").unwrap();
let resp = self
.session
.post(url, &serde_json::to_string(&transaction_settings)?)
.await?;
let result: ArangoResult<ArangoTransaction> = deserialize_response(resp.body())?;
let transaction = result.unwrap();
let tx_id = transaction.id.clone();
let mut session = (*self.session).clone();
session
.headers()
.insert(TRANSACTION_HEADER, tx_id.parse().unwrap());
Ok(Transaction::<C>::new(
transaction,
Arc::new(session),
self.base_url.clone(),
))
}
#[maybe_async]
pub async fn list_views(&self) -> Result<Vec<ViewDescription>, ClientError> {
let url = self.base_url.join("_api/view").unwrap();
let resp = self.session.get(url, "").await?;
let result: ArangoResult<Vec<ViewDescription>> = deserialize_response(resp.body())?;
Ok(result.unwrap())
}
#[maybe_async]
pub async fn create_view(&self, view_options: ViewOptions) -> Result<View, ClientError> {
let url = self.base_url.join("_api/view").unwrap();
let resp = self
.session
.post(url, &serde_json::to_string(&view_options)?)
.await?;
let result: View = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn view(&self, view_name: &str) -> Result<ViewDescription, ClientError> {
let url = self
.base_url
.join(&format!("_api/view/{}", view_name))
.unwrap();
let resp = self.session.get(url, "").await?;
let result: ViewDescription = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn view_properties(
&self,
view_name: &str,
) -> Result<ArangoSearchViewProperties, ClientError> {
let url = self
.base_url
.join(&format!("_api/view/{}/properties", view_name))
.unwrap();
let resp = self.session.get(url, "").await?;
let result: ArangoSearchViewProperties = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn replace_view_properties(
&self,
view_name: &str,
properties: ArangoSearchViewPropertiesOptions,
) -> Result<View, ClientError> {
let url = self
.base_url
.join(&format!("_api/view/{}/properties", view_name))
.unwrap();
let resp = self
.session
.put(url, &serde_json::to_string(&properties)?)
.await?;
let result: View = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn update_view_properties(
&self,
view_name: &str,
properties: ArangoSearchViewPropertiesOptions,
) -> Result<View, ClientError> {
let url = self
.base_url
.join(&format!("_api/view/{}/properties", view_name))
.unwrap();
let resp = self
.session
.patch(url, &serde_json::to_string(&properties)?)
.await?;
let result: View = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn drop_view(&self, view_name: &str) -> Result<bool, ClientError> {
let url = self
.base_url
.join(&format!("_api/view/{}", view_name))
.unwrap();
let resp = self.session.delete(url, "").await?;
let result: ArangoResult<bool> = deserialize_response(resp.body())?;
Ok(result.unwrap())
}
#[maybe_async]
pub async fn list_analyzers(&self) -> Result<Vec<AnalyzerInfo>, ClientError> {
let url = self.base_url.join("_api/analyzer").unwrap();
let resp = self.session.get(url, "").await?;
let result: ArangoResult<Vec<AnalyzerInfo>> = deserialize_response(resp.body())?;
Ok(result.unwrap())
}
#[maybe_async]
pub async fn create_analyzer(
&self,
analyzer: AnalyzerInfo,
) -> Result<AnalyzerInfo, ClientError> {
let url = self.base_url.join("_api/analyzer").unwrap();
let resp = self
.session
.post(url, &serde_json::to_string(&analyzer)?)
.await?;
let result: AnalyzerInfo = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn analyzer(&self, analyzer_name: &str) -> Result<AnalyzerInfo, ClientError> {
let url = self
.base_url
.join(&format!("_api/analyzer/{}", analyzer_name))
.unwrap();
let resp = self.session.get(url, "").await?;
let result: AnalyzerInfo = deserialize_response(resp.body())?;
Ok(result)
}
#[maybe_async]
pub async fn drop_analyzer(
&self,
analyzer_name: &str,
) -> Result<AnalyzerDescription, ClientError> {
let url = self
.base_url
.join(&format!("_api/analyzer/{}", analyzer_name))
.unwrap();
let resp = self.session.delete(url, "").await?;
let result: AnalyzerDescription = deserialize_response(resp.body())?;
Ok(result)
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DatabaseDetails {
pub name: String,
pub id: String,
pub path: String,
pub is_system: bool,
}