Skip to content

Commit c72b511

Browse files
committed
Define new types for metadata maps and keys
1 parent e631fa9 commit c72b511

File tree

1 file changed

+89
-2
lines changed

1 file changed

+89
-2
lines changed

src/persist-client/src/internal/encoding.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use bytes::{Buf, Bytes};
1919
use differential_dataflow::lattice::Lattice;
2020
use differential_dataflow::trace::Description;
2121
use mz_ore::cast::CastInto;
22-
use mz_ore::{assert_none, halt};
22+
use mz_ore::{assert_none, halt, soft_panic_or_log};
2323
use mz_persist::indexed::encoding::{BatchColumnarFormat, BlobTraceBatchPart, BlobTraceUpdates};
2424
use mz_persist::location::{SeqNo, VersionedData};
2525
use mz_persist::metrics::ColumnarMetrics;
@@ -32,7 +32,7 @@ use proptest::strategy::Strategy;
3232
use prost::Message;
3333
use semver::Version;
3434
use serde::ser::SerializeStruct;
35-
use serde::{Deserialize, Serialize};
35+
use serde::{Deserialize, Serialize, Serializer};
3636
use timely::progress::{Antichain, Timestamp};
3737
use uuid::Uuid;
3838

@@ -192,6 +192,83 @@ impl<T: Message + Default> RustType<Bytes> for LazyProto<T> {
192192
}
193193
}
194194

195+
/// Our Proto implementation, Prost, cannot handle unrecognized fields. This means that unexpected
196+
/// data will be dropped at deserialization time, which means that we can't reliably roundtrip data
197+
/// from future versions of the code, which causes trouble during upgrades and at other times.
198+
///
199+
/// This type works around the issue by defining an unstructured metadata map. Keys are expected to
200+
/// be well-known strings defined in the code; values are bytes, expected to be encoded protobuf.
201+
/// (The association between the two is lightly enforced with the affiliated [MetadataKey] type.)
202+
/// It's safe to add new metadata keys in new versions, since even unrecognized keys can be losslessly
203+
/// roundtripped. However, if the metadata is not safe for the old version to ignore -- perhaps it
204+
/// needs to be kept in sync with some other part of the struct -- you will need to use a more
205+
/// heavyweight migration for it.
206+
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
207+
pub(crate) struct MetadataMap(BTreeMap<String, Bytes>);
208+
209+
/// Associating a field name and an associated Proto message type, for lookup in a metadata map.
210+
///
211+
/// It is an error to reuse key names, or to change the type associated with a particular name.
212+
/// It is polite to choose short names, since they get serialized alongside every struct.
213+
#[allow(unused)]
214+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
215+
pub(crate) struct MetadataKey<A> {
216+
name: &'static str,
217+
type_: PhantomData<A>,
218+
}
219+
220+
impl<A> MetadataKey<A> {
221+
#[allow(unused)]
222+
pub(crate) const fn new(name: &'static str) -> Self {
223+
MetadataKey {
224+
name,
225+
type_: PhantomData,
226+
}
227+
}
228+
}
229+
230+
impl serde::Serialize for MetadataMap {
231+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232+
where
233+
S: Serializer,
234+
{
235+
serializer.collect_map(self.0.iter())
236+
}
237+
}
238+
239+
impl MetadataMap {
240+
/// Serialize and insert a new key into the map, replacing any existing value for the key.
241+
#[allow(unused)]
242+
pub fn set<V: prost::Message>(&mut self, key: MetadataKey<V>, value: V) {
243+
self.0
244+
.insert(String::from(key.name), Bytes::from(value.encode_to_vec()));
245+
}
246+
247+
/// Deserialize a key from the map, if it is present.
248+
#[allow(unused)]
249+
pub fn get<V: prost::Message + Default>(&self, key: MetadataKey<V>) -> Option<V> {
250+
match V::decode(self.0.get(key.name)?.as_ref()) {
251+
Ok(decoded) => Some(decoded),
252+
Err(err) => {
253+
// This should be impossible unless one of the MetadataKey invariants are broken.
254+
soft_panic_or_log!(
255+
"error when decoding {key}; was it redefined? {err}",
256+
key = key.name
257+
);
258+
None
259+
}
260+
}
261+
}
262+
}
263+
impl RustType<BTreeMap<String, Bytes>> for MetadataMap {
264+
fn into_proto(&self) -> BTreeMap<String, Bytes> {
265+
self.0.clone()
266+
}
267+
fn from_proto(proto: BTreeMap<String, Bytes>) -> Result<Self, TryFromProtoError> {
268+
Ok(MetadataMap(proto))
269+
}
270+
}
271+
195272
pub(crate) fn parse_id(id_prefix: &str, id_type: &str, encoded: &str) -> Result<[u8; 16], String> {
196273
let uuid_encoded = match encoded.strip_prefix(id_prefix) {
197274
Some(x) => x,
@@ -1839,6 +1916,16 @@ mod tests {
18391916

18401917
use super::*;
18411918

1919+
#[mz_ore::test]
1920+
fn metadata_map() {
1921+
const FUN_COUNT: MetadataKey<u64> = MetadataKey::new("fun");
1922+
1923+
let mut map = MetadataMap::default();
1924+
map.set(FUN_COUNT, 100);
1925+
let map = MetadataMap::from_proto(map.into_proto()).unwrap();
1926+
assert_eq!(map.get(FUN_COUNT), Some(100));
1927+
}
1928+
18421929
#[mz_ore::test]
18431930
fn applier_version_state() {
18441931
let v1 = semver::Version::new(1, 0, 0);

0 commit comments

Comments
 (0)