use std::sync::Arc;
use tokio::runtime::Runtime;
use ytsaurus_api::{
Error as ApiError, LookupOptions, MaybeRow, Result as ApiResult, Row, SelectOptions,
TableClient, TableTransaction, Transport,
};
use crate::client::{Client as AsyncClient, StartTransactionOptions, Transaction, TransactionType};
use crate::wire;
mod convert;
pub use convert::{row_from_wire, row_to_wire};
pub struct Client {
runtime: Arc<Runtime>,
inner: Arc<AsyncClient>,
}
impl Client {
pub fn connect(address: &str) -> ApiResult<Self> {
Self::builder(address).connect()
}
pub fn builder(address: &str) -> ClientBuilder {
ClientBuilder {
address: address.to_owned(),
token: None,
timeout: None,
}
}
}
pub struct ClientBuilder {
address: String,
token: Option<String>,
timeout: Option<std::time::Duration>,
}
impl ClientBuilder {
pub fn token(mut self, token: impl Into<String>) -> Self {
self.token = Some(token.into());
self
}
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn connect(self) -> ApiResult<Client> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|error| ApiError::transport_from("starting a runtime", error))?;
let mut builder = AsyncClient::builder(&self.address);
if let Some(token) = self.token {
builder = builder.token(token);
}
if let Some(timeout) = self.timeout {
builder = builder.timeout(timeout);
}
let inner = runtime
.block_on(builder.connect())
.map_err(|error| ApiError::transport_from("connecting", error))?;
Ok(Client {
runtime: Arc::new(runtime),
inner: Arc::new(inner),
})
}
}
fn map_error(operation: &str, error: crate::Error) -> ApiError {
match &error {
crate::Error::Response {
error: reported, ..
} => {
let code = Some(reported.code);
ApiError::cluster_from(operation, code, error)
}
crate::Error::Timeout { .. } => ApiError::Timeout {
operation: operation.to_owned(),
},
_ => ApiError::transport_from(operation, error),
}
}
fn column_names(rows: &[Row]) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
for row in rows {
for name in row.names() {
if !names.iter().any(|known| known == name) {
names.push(name.to_owned());
}
}
}
names
}
fn to_wire_rows(rows: &[Row], columns: &[String]) -> ApiResult<Vec<wire::Row>> {
rows.iter().map(|row| row_to_wire(row, columns)).collect()
}
fn from_wire_rows(rows: Vec<MaybeRowWire>, columns: &[String]) -> ApiResult<Vec<MaybeRow>> {
rows.into_iter()
.map(|row| match row {
Some(row) => row_from_wire(&row, columns).map(Some),
None => Ok(None),
})
.collect()
}
type MaybeRowWire = Option<wire::Row>;
fn rpc_select_options(options: &SelectOptions) -> crate::client::SelectOptions {
crate::client::SelectOptions {
timestamp: options.timestamp,
output_row_limit: options.limit,
}
}
impl TableClient for Client {
fn transport(&self) -> Transport {
Transport::Rpc
}
fn lookup_rows(
&self,
path: &str,
keys: &[Row],
options: &LookupOptions,
) -> ApiResult<Vec<MaybeRow>> {
let key_columns = column_names(keys);
let wire_keys = to_wire_rows(keys, &key_columns)?;
let borrowed: Vec<&str> = key_columns.iter().map(String::as_str).collect();
let filter: Vec<&str> = options.columns.iter().map(String::as_str).collect();
let (rows, columns) = self
.runtime
.block_on(self.inner.lookup_rows_with_columns(
path,
&borrowed,
&wire_keys,
crate::client::LookupOptions {
timestamp: options.timestamp,
column_filter: filter,
},
))
.map_err(|error| map_error("lookup_rows", error))?;
from_wire_rows(rows, &columns)
}
fn select_rows(&self, query: &str, options: &SelectOptions) -> ApiResult<Vec<Row>> {
let (rows, columns) = self
.runtime
.block_on(
self.inner
.select_rows_with_columns(query, rpc_select_options(options)),
)
.map_err(|error| map_error("select_rows", error))?;
rows.into_iter()
.flatten()
.map(|row| row_from_wire(&row, &columns))
.collect()
}
fn insert_rows(&self, path: &str, rows: &[Row]) -> ApiResult<()> {
let transaction = self.start_transaction()?;
transaction.insert_rows(path, rows)?;
transaction.commit()
}
fn delete_rows(&self, path: &str, keys: &[Row]) -> ApiResult<()> {
let transaction = self.start_transaction()?;
transaction.delete_rows(path, keys)?;
transaction.commit()
}
fn start_transaction(&self) -> ApiResult<Box<dyn TableTransaction + '_>> {
let transaction = self
.runtime
.block_on(
self.inner
.start_transaction(TransactionType::Tablet, StartTransactionOptions::default()),
)
.map_err(|error| map_error("start_transaction", error))?;
Ok(Box::new(BlockingTransaction {
runtime: Arc::clone(&self.runtime),
transaction,
}))
}
}
struct BlockingTransaction<'a> {
runtime: Arc<Runtime>,
transaction: Transaction<'a>,
}
impl TableTransaction for BlockingTransaction<'_> {
fn id(&self) -> String {
self.transaction.id().to_string()
}
fn lookup_rows(
&self,
path: &str,
keys: &[Row],
options: &LookupOptions,
) -> ApiResult<Vec<MaybeRow>> {
let key_columns = column_names(keys);
let wire_keys = to_wire_rows(keys, &key_columns)?;
let borrowed: Vec<&str> = key_columns.iter().map(String::as_str).collect();
let filter: Vec<&str> = options.columns.iter().map(String::as_str).collect();
let (rows, columns) = self
.runtime
.block_on(self.transaction.lookup_rows_with_columns(
path,
&borrowed,
&wire_keys,
crate::client::LookupOptions {
timestamp: None,
column_filter: filter,
},
))
.map_err(|error| map_error("lookup_rows", error))?;
from_wire_rows(rows, &columns)
}
fn select_rows(&self, query: &str, options: &SelectOptions) -> ApiResult<Vec<Row>> {
let (rows, columns) = self
.runtime
.block_on(
self.transaction
.select_rows_with_columns(query, rpc_select_options(options)),
)
.map_err(|error| map_error("select_rows", error))?;
rows.into_iter()
.flatten()
.map(|row| row_from_wire(&row, &columns))
.collect()
}
fn insert_rows(&self, path: &str, rows: &[Row]) -> ApiResult<()> {
let columns = column_names(rows);
let wire_rows = to_wire_rows(rows, &columns)?;
let borrowed: Vec<&str> = columns.iter().map(String::as_str).collect();
self.runtime
.block_on(self.transaction.insert_rows(path, &borrowed, &wire_rows))
.map_err(|error| map_error("insert_rows", error))
}
fn delete_rows(&self, path: &str, keys: &[Row]) -> ApiResult<()> {
let columns = column_names(keys);
let wire_keys = to_wire_rows(keys, &columns)?;
let borrowed: Vec<&str> = columns.iter().map(String::as_str).collect();
self.runtime
.block_on(self.transaction.delete_rows(path, &borrowed, &wire_keys))
.map_err(|error| map_error("delete_rows", error))
}
fn ping(&self) -> ApiResult<()> {
self.runtime
.block_on(self.transaction.ping())
.map_err(|error| map_error("ping_transaction", error))
}
fn commit(self: Box<Self>) -> ApiResult<()> {
let this = *self;
this.runtime
.block_on(this.transaction.commit())
.map_err(|error| map_error("commit_transaction", error))
}
fn abort(self: Box<Self>) -> ApiResult<()> {
let this = *self;
this.runtime
.block_on(this.transaction.abort())
.map_err(|error| map_error("abort_transaction", error))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn select_limit_is_an_rpc_field_and_leaves_the_query_alone() {
let query = "* from [//tmp/t] limit 100;";
let options = SelectOptions {
timestamp: Some(99),
limit: Some(10),
};
let rpc = rpc_select_options(&options);
assert_eq!(rpc.timestamp, Some(99));
assert_eq!(rpc.output_row_limit, Some(10));
assert_eq!(query, "* from [//tmp/t] limit 100;");
}
}