pancake_db_client/
row_helpers.rs

1use std::time::SystemTime;
2
3use pancake_db_idl::dml::field_value::Value;
4use pancake_db_idl::dml::RepeatedFieldValue;
5use prost_types::Timestamp;
6
7/// Re-export for the purpose of [`make_row`].
8pub use pancake_db_idl::dml::{FieldValue, Row};
9
10/// Trait used by [`make_row`] to convert native types to Pancake IDL types.
11pub trait FieldValueConverter {
12  fn to_value(self) -> Option<Value>;
13}
14
15impl FieldValueConverter for f32 {
16  fn to_value(self) -> Option<Value> {
17    Some(Value::Float32Val(self))
18  }
19}
20
21impl FieldValueConverter for f64 {
22  fn to_value(self) -> Option<Value> {
23    Some(Value::Float64Val(self))
24  }
25}
26
27impl FieldValueConverter for i64 {
28  fn to_value(self) -> Option<Value> {
29    Some(Value::Int64Val(self))
30  }
31}
32
33impl FieldValueConverter for SystemTime {
34  fn to_value(self) -> Option<Value> {
35    Some(Value::TimestampVal(Timestamp::from(self)))
36  }
37}
38
39impl FieldValueConverter for bool {
40  fn to_value(self) -> Option<Value> {
41    Some(Value::BoolVal(self))
42  }
43}
44
45impl FieldValueConverter for String {
46  fn to_value(self) -> Option<Value> {
47    Some(Value::StringVal(self))
48  }
49}
50
51impl FieldValueConverter for Vec<u8> {
52  fn to_value(self) -> Option<Value> {
53    Some(Value::BytesVal(self))
54  }
55}
56
57impl<T: FieldValueConverter> FieldValueConverter for Option<T> {
58  fn to_value(self) -> Option<Value> {
59    self.and_then(|inner| inner.to_value())
60  }
61}
62
63impl<T: FieldValueConverter> FieldValueConverter for Vec<T> {
64  fn to_value(self) -> Option<Value> {
65    let mut vals = Vec::with_capacity(self.len());
66    for inner in self {
67      vals.push(FieldValue {
68        value: inner.to_value(),
69      })
70    }
71    Some(Value::ListVal(RepeatedFieldValue {
72      vals,
73    }))
74  }
75}
76
77/// Helper macro to support [`make_row`].
78#[macro_export]
79macro_rules! make_row_insert {
80  {$row: expr;} => {};
81  {$row: expr; $key:expr => $val:expr $(,$keys:expr => $vals:expr)* $(,)?} => {
82    let fv = $crate::row_helpers::FieldValue {
83      value: $crate::row_helpers::FieldValueConverter::to_value($val),
84    };
85    $row.insert($key.to_string(), fv);
86    $crate::make_row_insert! { $row; $($keys => $vals),* }
87  };
88}
89
90/// Outputs a row, given native Rust key => value pairings.
91///
92/// Since instantiating protobuf-generated types is very verbose,
93/// this macro exists to make rows with ease:
94///
95/// ```
96/// use pancake_db_client::make_row;
97/// use std::time::SystemTime;
98///
99/// let my_row = make_row! {
100///   "string_col" => "some string".to_string(),
101///   "timestamp_col" => SystemTime::now(),
102///   "int_col" => Some(77),
103///   "bool_col" => Option::<bool>::None,
104///   "bytes_col" => vec![97_u8, 98_u8, 99_u8],
105///   "bool_list_col" => vec![true, false],
106/// };
107/// ```
108///
109/// Keys can be any type supporting `.to_string()`.
110/// Values can be any
111/// Rust type that corresponds to a Pancake type, or `Option`s or nested `Vec`s
112/// thereof.
113#[macro_export]
114macro_rules! make_row {
115  {} => {
116    $crate::row_helpers::Row::default()
117  };
118  {$($keys:expr => $vals:expr),+ $(,)?} => {
119    {
120      let mut fields = std::collections::HashMap::<String, $crate::row_helpers::FieldValue>::new();
121      $crate::make_row_insert! { fields; $($keys => $vals),+ }
122      $crate::row_helpers::Row { fields }
123    }
124  };
125}
126
127#[cfg(test)]
128mod tests {
129  use std::time::SystemTime;
130
131  use pancake_db_idl::dml::{FieldValue, Row};
132  use pancake_db_idl::dml::field_value::Value;
133  use prost_types::Timestamp;
134
135  use crate::make_row;
136
137  #[test]
138  fn test_row_macro() {
139    let timestamp = SystemTime::now();
140    let row0 = make_row! {};
141    let row1 = make_row! { "f32" => 3.3_f32 };
142    let row2 = make_row! {
143      "f32" => 3.3_f32,
144      "i64" => 4_i64,
145      "bool" => false,
146      "timestamp" => timestamp.clone(),
147      "present" => Some("asdf".to_string()),
148      "absent" => Option::<String>::None,
149      "bytes" => vec![0_u8, 1_u8],
150      "list" => vec![1_i64, 2_i64],
151    };
152
153    assert!(row0.fields.is_empty());
154
155    assert_eq!(row1.fields.len(), 1);
156
157    assert_eq!(row2.fields.len(), 8);
158    fn assert_val_eq(row: &Row, key: &str, value: Option<Value>) {
159      assert_eq!(row.fields[key].clone(), FieldValue { value });
160    }
161    assert_val_eq(&row2, "f32", Some(Value::Float32Val(3.3)));
162    assert_val_eq(&row2, "i64", Some(Value::Int64Val(4)));
163    assert_val_eq(&row2, "bool", Some(Value::BoolVal(false)));
164    assert_val_eq(&row2, "timestamp", Some(Value::TimestampVal(Timestamp::from(timestamp.clone()))));
165    assert_val_eq(&row2, "present", Some(Value::StringVal("asdf".to_string())));
166    assert_val_eq(&row2, "absent", None);
167    assert_val_eq(&row2, "bytes", Some(Value::BytesVal(vec![0, 1])));
168    assert!(matches!(&row2.fields["list"].value, Some(Value::ListVal(_))));
169  }
170}
171
172#[cfg(test)]
173mod tests_no_imports {
174  use crate::make_row;
175
176  #[test]
177  fn test_row_macro() {
178    println!("{:?}", make_row! {});
179    println!("{:?}", make_row! { "a" => 3.3_f64 });
180  }
181}