@@ -19,7 +19,7 @@ use bytes::{Buf, Bytes};
1919use differential_dataflow:: lattice:: Lattice ;
2020use differential_dataflow:: trace:: Description ;
2121use mz_ore:: cast:: CastInto ;
22- use mz_ore:: { assert_none, halt} ;
22+ use mz_ore:: { assert_none, halt, soft_panic_or_log } ;
2323use mz_persist:: indexed:: encoding:: { BatchColumnarFormat , BlobTraceBatchPart , BlobTraceUpdates } ;
2424use mz_persist:: location:: { SeqNo , VersionedData } ;
2525use mz_persist:: metrics:: ColumnarMetrics ;
@@ -32,7 +32,7 @@ use proptest::strategy::Strategy;
3232use prost:: Message ;
3333use semver:: Version ;
3434use serde:: ser:: SerializeStruct ;
35- use serde:: { Deserialize , Serialize } ;
35+ use serde:: { Deserialize , Serialize , Serializer } ;
3636use timely:: progress:: { Antichain , Timestamp } ;
3737use 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+
195272pub ( 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