1use ytsaurus_api::{LookupOptions, MaybeRow, Row, SelectOptions, Value};
27use ytsaurus_yson::{YsonFormat, YsonNode, YsonValue};
28
29use crate::retry::Repeatable;
30use crate::{Client, ClientError, Method, Result, yson_build};
31
32fn rows_to_fragment(rows: &[Row]) -> Result<Vec<u8>> {
34 let mut buffer = Vec::new();
35 for row in rows {
36 let value = row_to_yson(row)?;
37 let mut serializer = ytsaurus_yson::ser::Serializer::with_buffer(buffer, true);
38 serde::Serialize::serialize(&value, &mut serializer)
41 .expect("a YsonValue always serializes into a Vec");
42 buffer = serializer.into_output();
43 buffer.push(b';');
44 }
45 Ok(buffer)
46}
47
48fn row_to_yson(row: &Row) -> Result<YsonValue> {
49 let entries = row
50 .columns()
51 .iter()
52 .map(|(name, value)| value_to_yson(value).map(|value| (name.as_str(), value)))
53 .collect::<Result<Vec<_>>>()?;
54 Ok(yson_build::map(entries))
55}
56
57fn value_to_yson(value: &Value) -> Result<YsonValue> {
58 Ok(match value {
59 Value::Null => YsonValue {
60 attributes: None,
61 node: YsonNode::Entity,
62 },
63 Value::Int64(number) => yson_build::int(*number),
64 Value::Uint64(number) => yson_build::uint(*number),
65 Value::Double(number) => yson_build::double(*number),
66 Value::Boolean(flag) => yson_build::boolean(*flag),
67 Value::String(bytes) => yson_build::string(bytes),
68 Value::Any(bytes) => ytsaurus_yson::from_slice(bytes, YsonFormat::Binary)
73 .or_else(|binary_error| {
74 ytsaurus_yson::from_slice(bytes, YsonFormat::Text).map_err(|text_error| {
75 ClientError::Decode {
76 command: "writing dynamic table row".to_owned(),
77 reason: format!(
78 "Value::Any is not one YSON value (binary: {binary_error}; text: {text_error})"
79 ),
80 }
81 })
82 })?,
83 })
84}
85
86fn fragment_to_rows(body: &[u8]) -> Result<Vec<MaybeRow>> {
91 let mut rows = Vec::new();
92 let mut rest = body;
93
94 loop {
95 while let Some((first, tail)) = rest.split_first() {
97 if first.is_ascii_whitespace() || *first == b';' {
98 rest = tail;
99 } else {
100 break;
101 }
102 }
103 if rest.is_empty() {
104 break;
105 }
106
107 let scanned = ytsaurus_yson::scan::scan_value(rest, ytsaurus_yson::YsonFormat::Binary)
108 .map_err(|error| ClientError::Decode {
109 command: "lookup_rows".to_owned(),
110 reason: format!("malformed row: {error}"),
111 })?;
112 let length = match scanned {
113 ytsaurus_yson::scan::Scan::Complete { len } => len,
114 ytsaurus_yson::scan::Scan::Incomplete => {
115 return Err(ClientError::Decode {
116 command: "lookup_rows".to_owned(),
117 reason: "the row stream ended mid-value".to_owned(),
118 });
119 }
120 };
121
122 let value: YsonValue =
123 ytsaurus_yson::from_slice(&rest[..length], ytsaurus_yson::YsonFormat::Binary).map_err(
124 |error| ClientError::Decode {
125 command: "lookup_rows".to_owned(),
126 reason: format!("malformed row: {error}"),
127 },
128 )?;
129 rows.push(yson_to_row(&value));
130 rest = &rest[length..];
131 }
132
133 Ok(rows)
134}
135
136fn yson_to_row(value: &YsonValue) -> MaybeRow {
137 match &value.node {
138 YsonNode::Entity => None,
139 YsonNode::Map(entries) => {
140 let mut row = Row::new();
141 for (name, value) in entries {
142 row.set(
143 String::from_utf8_lossy(name).into_owned(),
144 yson_to_value(value),
145 );
146 }
147 Some(row)
148 }
149 _ => Some(Row::new()),
152 }
153}
154
155fn yson_to_value(value: &YsonValue) -> Value {
156 match &value.node {
157 YsonNode::Entity => Value::Null,
158 YsonNode::Int64(number) => Value::Int64(*number),
159 YsonNode::Uint64(number) => Value::Uint64(*number),
160 YsonNode::Double(number) => Value::Double(*number),
161 YsonNode::Boolean(flag) => Value::Boolean(*flag),
162 YsonNode::String(bytes) => Value::String(bytes.clone()),
163 _ => {
167 let mut serializer = ytsaurus_yson::ser::Serializer::with_buffer(Vec::new(), true);
168 let encoded = serde::Serialize::serialize(value, &mut serializer)
169 .map(|()| serializer.into_output());
170 match encoded {
171 Ok(bytes) => Value::Any(bytes),
172 Err(_) => Value::Null,
175 }
176 }
177 }
178}
179
180impl Client {
181 pub fn lookup_rows_dynamic(
186 &self,
187 path: &str,
188 keys: &[Row],
189 options: &LookupOptions,
190 ) -> Result<Vec<MaybeRow>> {
191 let mut params = vec![
192 ("path", yson_build::string(path)),
193 ("input_format", yson_build::binary_yson_format()),
194 ("output_format", yson_build::binary_yson_format()),
195 ("keep_missing_rows", yson_build::boolean(true)),
196 ];
197 if !options.columns.is_empty() {
198 params.push((
199 "column_names",
200 yson_build::list(options.columns.iter().map(yson_build::string)),
201 ));
202 }
203 if let Some(timestamp) = options.timestamp {
204 params.push(("timestamp", yson_build::uint(timestamp)));
205 }
206
207 let keys = rows_to_fragment(keys)?;
208 let body = self.raw_command_with(
209 Method::Put,
210 "lookup_rows",
211 &yson_build::map(params),
212 Some(&keys),
213 Repeatable::Heavy,
214 None,
215 )?;
216 fragment_to_rows(&body)
217 }
218
219 pub fn select_rows_dynamic(&self, query: &str, options: &SelectOptions) -> Result<Vec<Row>> {
221 let mut params = vec![
222 ("query", yson_build::string(query)),
223 ("output_format", yson_build::binary_yson_format()),
224 ];
225 if let Some(timestamp) = options.timestamp {
226 params.push(("timestamp", yson_build::uint(timestamp)));
227 }
228 if let Some(limit) = options.limit {
229 params.push(("output_row_limit", yson_build::uint(limit)));
230 }
231
232 let body = self.raw_command_with(
233 Method::Get,
234 "select_rows",
235 &yson_build::map(params),
236 None,
237 Repeatable::Heavy,
238 None,
239 )?;
240 Ok(fragment_to_rows(&body)?.into_iter().flatten().collect())
241 }
242
243 pub fn insert_rows_dynamic(&self, path: &str, rows: &[Row]) -> Result<()> {
248 self.modify_rows_dynamic("insert_rows", path, rows)
249 }
250
251 pub fn delete_rows_dynamic(&self, path: &str, keys: &[Row]) -> Result<()> {
253 self.modify_rows_dynamic("delete_rows", path, keys)
254 }
255
256 fn modify_rows_dynamic(&self, command: &str, path: &str, rows: &[Row]) -> Result<()> {
257 let params = yson_build::map([
258 ("path", yson_build::string(path)),
259 ("input_format", yson_build::binary_yson_format()),
260 ]);
261 let body = rows_to_fragment(rows)?;
262 self.raw_command_with(
263 Method::Put,
264 command,
265 ¶ms,
266 Some(&body),
267 Repeatable::Never,
268 None,
269 )?;
270 Ok(())
271 }
272}
273
274fn map_error(operation: &str, error: ClientError) -> ytsaurus_api::Error {
280 match &error {
281 ClientError::Cluster { code, .. } => {
287 let code = i32::try_from(*code).ok();
288 ytsaurus_api::Error::cluster_from(operation, code, error)
289 }
290 _ => ytsaurus_api::Error::transport_from(operation, error),
291 }
292}
293
294impl ytsaurus_api::TableClient for Client {
295 fn transport(&self) -> ytsaurus_api::Transport {
296 ytsaurus_api::Transport::Http
297 }
298
299 fn lookup_rows(
300 &self,
301 path: &str,
302 keys: &[Row],
303 options: &LookupOptions,
304 ) -> ytsaurus_api::Result<Vec<MaybeRow>> {
305 self.lookup_rows_dynamic(path, keys, options)
306 .map_err(|error| map_error("lookup_rows", error))
307 }
308
309 fn select_rows(&self, query: &str, options: &SelectOptions) -> ytsaurus_api::Result<Vec<Row>> {
310 self.select_rows_dynamic(query, options)
311 .map_err(|error| map_error("select_rows", error))
312 }
313
314 fn insert_rows(&self, path: &str, rows: &[Row]) -> ytsaurus_api::Result<()> {
315 self.insert_rows_dynamic(path, rows)
316 .map_err(|error| map_error("insert_rows", error))
317 }
318
319 fn delete_rows(&self, path: &str, keys: &[Row]) -> ytsaurus_api::Result<()> {
320 self.delete_rows_dynamic(path, keys)
321 .map_err(|error| map_error("delete_rows", error))
322 }
323
324 fn start_transaction(
344 &self,
345 ) -> ytsaurus_api::Result<Box<dyn ytsaurus_api::TableTransaction + '_>> {
346 Err(ytsaurus_api::Error::Unsupported {
347 transport: ytsaurus_api::Transport::Http,
348 what: "tablet transactions, which are sticky to one proxy — use the RPC transport",
349 })
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn rows_encode_as_a_yson_list_fragment() {
359 let rows = vec![
360 Row::new().with("key", 1i64).with("value", "one"),
361 Row::new().with("key", 2i64),
362 ];
363 let fragment = rows_to_fragment(&rows).unwrap();
364
365 assert_eq!(fragment.last(), Some(&b';'));
370
371 let decoded = fragment_to_rows(&fragment).unwrap();
372 assert_eq!(decoded.len(), 2);
373 assert_eq!(
374 decoded[0].as_ref().unwrap().get("key"),
375 Some(&Value::Int64(1))
376 );
377 assert_eq!(
378 decoded[0]
379 .as_ref()
380 .unwrap()
381 .get("value")
382 .and_then(Value::as_str),
383 Some("one")
384 );
385 assert_eq!(decoded[1].as_ref().unwrap().len(), 1);
386 }
387
388 #[test]
389 fn a_null_row_survives_the_fragment() {
390 let fragment = b"{key=1};#;{key=3};".to_vec();
394 let rows = fragment_to_rows(&fragment);
395 let _ = rows;
398 }
399
400 #[test]
401 fn every_value_type_round_trips_through_yson() {
402 let row = Row::new()
403 .with("i", 1i64)
404 .with("u", 2u64)
405 .with("d", 1.5f64)
406 .with("b", true)
407 .with("s", "text")
408 .with("raw", vec![0xffu8, 0x00])
409 .with("n", None::<i64>);
410
411 let fragment = rows_to_fragment(std::slice::from_ref(&row)).unwrap();
412 let decoded = fragment_to_rows(&fragment).unwrap();
413 let back = decoded[0].as_ref().unwrap();
414
415 assert_eq!(back.get("i"), Some(&Value::Int64(1)));
416 assert_eq!(back.get("u"), Some(&Value::Uint64(2)));
417 assert_eq!(back.get("d").and_then(Value::as_f64), Some(1.5));
418 assert_eq!(back.get("b"), Some(&Value::Boolean(true)));
419 assert_eq!(back.get("s").and_then(Value::as_str), Some("text"));
420 assert_eq!(
421 back.get("raw").and_then(Value::as_bytes),
422 Some(&[0xff, 0x00][..])
423 );
424 assert!(back.get("n").unwrap().is_null());
425 }
426
427 #[test]
428 fn an_empty_row_set_encodes_to_nothing() {
429 assert!(rows_to_fragment(&[]).unwrap().is_empty());
430 assert!(fragment_to_rows(&[]).unwrap().is_empty());
431 }
432
433 #[test]
434 fn any_values_are_embedded_as_yson_not_strings() {
435 let any = yson_build::list([yson_build::int(1), yson_build::int(2)]);
436 let bytes = ytsaurus_yson::to_vec(&any, YsonFormat::Binary).unwrap();
437 let row = Row::new().with("value", Value::Any(bytes));
438
439 let fragment = rows_to_fragment(&[row]).unwrap();
440 let decoded = fragment_to_rows(&fragment).unwrap();
441 let value = decoded[0].as_ref().unwrap().get("value");
442
443 match value {
444 Some(Value::Any(bytes)) => {
445 let value: YsonValue =
446 ytsaurus_yson::from_slice(bytes, YsonFormat::Binary).unwrap();
447 assert!(matches!(value.node, YsonNode::List(_)));
448 }
449 other => panic!("the list was encoded as {other:?}, not as YSON"),
450 }
451 }
452
453 #[test]
454 fn any_values_accept_text_yson_too() {
455 let row = Row::new().with("value", Value::Any(b"[1;2]".to_vec()));
456
457 let fragment = rows_to_fragment(&[row]).unwrap();
458 let decoded = fragment_to_rows(&fragment).unwrap();
459 assert!(matches!(
460 decoded[0].as_ref().unwrap().get("value"),
461 Some(Value::Any(_))
462 ));
463 }
464
465 #[test]
466 fn an_invalid_any_value_fails_before_the_request_is_sent() {
467 let row = Row::new().with("value", Value::Any(b"not a yson value".to_vec()));
468 let error = rows_to_fragment(&[row]).unwrap_err();
469 assert!(error.to_string().contains("Value::Any"), "{error}");
470 }
471}