1use std::sync::Arc;
18
19use tokio::runtime::Runtime;
20use ytsaurus_api::{
21 Error as ApiError, LookupOptions, MaybeRow, Result as ApiResult, Row, SelectOptions,
22 TableClient, TableTransaction, Transport,
23};
24
25use crate::client::{Client as AsyncClient, StartTransactionOptions, Transaction, TransactionType};
26use crate::wire;
27
28mod convert;
29
30pub use convert::{row_from_wire, row_to_wire};
31
32pub struct Client {
39 runtime: Arc<Runtime>,
40 inner: Arc<AsyncClient>,
41}
42
43impl Client {
44 pub fn connect(address: &str) -> ApiResult<Self> {
46 Self::builder(address).connect()
47 }
48
49 pub fn builder(address: &str) -> ClientBuilder {
51 ClientBuilder {
52 address: address.to_owned(),
53 token: None,
54 timeout: None,
55 }
56 }
57}
58
59pub struct ClientBuilder {
61 address: String,
62 token: Option<String>,
63 timeout: Option<std::time::Duration>,
64}
65
66impl ClientBuilder {
67 pub fn token(mut self, token: impl Into<String>) -> Self {
69 self.token = Some(token.into());
70 self
71 }
72
73 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
75 self.timeout = Some(timeout);
76 self
77 }
78
79 pub fn connect(self) -> ApiResult<Client> {
80 let runtime = tokio::runtime::Builder::new_current_thread()
85 .enable_all()
86 .build()
87 .map_err(|error| ApiError::transport_from("starting a runtime", error))?;
88
89 let mut builder = AsyncClient::builder(&self.address);
90 if let Some(token) = self.token {
91 builder = builder.token(token);
92 }
93 if let Some(timeout) = self.timeout {
94 builder = builder.timeout(timeout);
95 }
96
97 let inner = runtime
98 .block_on(builder.connect())
99 .map_err(|error| ApiError::transport_from("connecting", error))?;
100
101 Ok(Client {
102 runtime: Arc::new(runtime),
103 inner: Arc::new(inner),
104 })
105 }
106}
107
108fn map_error(operation: &str, error: crate::Error) -> ApiError {
110 match &error {
111 crate::Error::Response {
112 error: reported, ..
113 } => {
114 let code = Some(reported.code);
115 ApiError::cluster_from(operation, code, error)
116 }
117 crate::Error::Timeout { .. } => ApiError::Timeout {
118 operation: operation.to_owned(),
119 },
120 _ => ApiError::transport_from(operation, error),
121 }
122}
123
124fn column_names(rows: &[Row]) -> Vec<String> {
130 let mut names: Vec<String> = Vec::new();
131 for row in rows {
132 for name in row.names() {
133 if !names.iter().any(|known| known == name) {
134 names.push(name.to_owned());
135 }
136 }
137 }
138 names
139}
140
141fn to_wire_rows(rows: &[Row], columns: &[String]) -> ApiResult<Vec<wire::Row>> {
142 rows.iter().map(|row| row_to_wire(row, columns)).collect()
143}
144
145fn from_wire_rows(rows: Vec<MaybeRowWire>, columns: &[String]) -> ApiResult<Vec<MaybeRow>> {
146 rows.into_iter()
147 .map(|row| match row {
148 Some(row) => row_from_wire(&row, columns).map(Some),
149 None => Ok(None),
150 })
151 .collect()
152}
153
154type MaybeRowWire = Option<wire::Row>;
155
156fn rpc_select_options(options: &SelectOptions) -> crate::client::SelectOptions {
159 crate::client::SelectOptions {
160 timestamp: options.timestamp,
161 output_row_limit: options.limit,
162 }
163}
164
165impl TableClient for Client {
166 fn transport(&self) -> Transport {
167 Transport::Rpc
168 }
169
170 fn lookup_rows(
171 &self,
172 path: &str,
173 keys: &[Row],
174 options: &LookupOptions,
175 ) -> ApiResult<Vec<MaybeRow>> {
176 let key_columns = column_names(keys);
177 let wire_keys = to_wire_rows(keys, &key_columns)?;
178 let borrowed: Vec<&str> = key_columns.iter().map(String::as_str).collect();
179 let filter: Vec<&str> = options.columns.iter().map(String::as_str).collect();
180
181 let (rows, columns) = self
182 .runtime
183 .block_on(self.inner.lookup_rows_with_columns(
184 path,
185 &borrowed,
186 &wire_keys,
187 crate::client::LookupOptions {
188 timestamp: options.timestamp,
189 column_filter: filter,
190 },
191 ))
192 .map_err(|error| map_error("lookup_rows", error))?;
193 from_wire_rows(rows, &columns)
194 }
195
196 fn select_rows(&self, query: &str, options: &SelectOptions) -> ApiResult<Vec<Row>> {
197 let (rows, columns) = self
198 .runtime
199 .block_on(
200 self.inner
201 .select_rows_with_columns(query, rpc_select_options(options)),
202 )
203 .map_err(|error| map_error("select_rows", error))?;
204
205 rows.into_iter()
206 .flatten()
207 .map(|row| row_from_wire(&row, &columns))
208 .collect()
209 }
210
211 fn insert_rows(&self, path: &str, rows: &[Row]) -> ApiResult<()> {
212 let transaction = self.start_transaction()?;
216 transaction.insert_rows(path, rows)?;
217 transaction.commit()
218 }
219
220 fn delete_rows(&self, path: &str, keys: &[Row]) -> ApiResult<()> {
221 let transaction = self.start_transaction()?;
222 transaction.delete_rows(path, keys)?;
223 transaction.commit()
224 }
225
226 fn start_transaction(&self) -> ApiResult<Box<dyn TableTransaction + '_>> {
227 let transaction = self
228 .runtime
229 .block_on(
230 self.inner
231 .start_transaction(TransactionType::Tablet, StartTransactionOptions::default()),
232 )
233 .map_err(|error| map_error("start_transaction", error))?;
234
235 Ok(Box::new(BlockingTransaction {
236 runtime: Arc::clone(&self.runtime),
237 transaction,
238 }))
239 }
240}
241
242struct BlockingTransaction<'a> {
244 runtime: Arc<Runtime>,
245 transaction: Transaction<'a>,
246}
247
248impl TableTransaction for BlockingTransaction<'_> {
249 fn id(&self) -> String {
250 self.transaction.id().to_string()
251 }
252
253 fn lookup_rows(
254 &self,
255 path: &str,
256 keys: &[Row],
257 options: &LookupOptions,
258 ) -> ApiResult<Vec<MaybeRow>> {
259 let key_columns = column_names(keys);
260 let wire_keys = to_wire_rows(keys, &key_columns)?;
261 let borrowed: Vec<&str> = key_columns.iter().map(String::as_str).collect();
262 let filter: Vec<&str> = options.columns.iter().map(String::as_str).collect();
263
264 let (rows, columns) = self
265 .runtime
266 .block_on(self.transaction.lookup_rows_with_columns(
267 path,
268 &borrowed,
269 &wire_keys,
270 crate::client::LookupOptions {
271 timestamp: None,
272 column_filter: filter,
273 },
274 ))
275 .map_err(|error| map_error("lookup_rows", error))?;
276 from_wire_rows(rows, &columns)
277 }
278
279 fn select_rows(&self, query: &str, options: &SelectOptions) -> ApiResult<Vec<Row>> {
280 let (rows, columns) = self
281 .runtime
282 .block_on(
283 self.transaction
284 .select_rows_with_columns(query, rpc_select_options(options)),
285 )
286 .map_err(|error| map_error("select_rows", error))?;
287
288 rows.into_iter()
289 .flatten()
290 .map(|row| row_from_wire(&row, &columns))
291 .collect()
292 }
293
294 fn insert_rows(&self, path: &str, rows: &[Row]) -> ApiResult<()> {
295 let columns = column_names(rows);
296 let wire_rows = to_wire_rows(rows, &columns)?;
297 let borrowed: Vec<&str> = columns.iter().map(String::as_str).collect();
298 self.runtime
299 .block_on(self.transaction.insert_rows(path, &borrowed, &wire_rows))
300 .map_err(|error| map_error("insert_rows", error))
301 }
302
303 fn delete_rows(&self, path: &str, keys: &[Row]) -> ApiResult<()> {
304 let columns = column_names(keys);
305 let wire_keys = to_wire_rows(keys, &columns)?;
306 let borrowed: Vec<&str> = columns.iter().map(String::as_str).collect();
307 self.runtime
308 .block_on(self.transaction.delete_rows(path, &borrowed, &wire_keys))
309 .map_err(|error| map_error("delete_rows", error))
310 }
311
312 fn ping(&self) -> ApiResult<()> {
313 self.runtime
314 .block_on(self.transaction.ping())
315 .map_err(|error| map_error("ping_transaction", error))
316 }
317
318 fn commit(self: Box<Self>) -> ApiResult<()> {
319 let this = *self;
320 this.runtime
321 .block_on(this.transaction.commit())
322 .map_err(|error| map_error("commit_transaction", error))
323 }
324
325 fn abort(self: Box<Self>) -> ApiResult<()> {
326 let this = *self;
327 this.runtime
328 .block_on(this.transaction.abort())
329 .map_err(|error| map_error("abort_transaction", error))
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn select_limit_is_an_rpc_field_and_leaves_the_query_alone() {
339 let query = "* from [//tmp/t] limit 100;";
340 let options = SelectOptions {
341 timestamp: Some(99),
342 limit: Some(10),
343 };
344
345 let rpc = rpc_select_options(&options);
346 assert_eq!(rpc.timestamp, Some(99));
347 assert_eq!(rpc.output_row_limit, Some(10));
348 assert_eq!(query, "* from [//tmp/t] limit 100;");
349 }
350}