LCOV - differential code coverage report
Current view: top level - pageserver/src/tenant/remote_timeline_client - index.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 97.6 % 251 245 6 245
Current Date: 2023-10-19 02:04:12 Functions: 62.1 % 66 41 25 41
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  Line data    Source code
       1                 : //! In-memory index to track the tenant files on the remote storage.
       2                 : //! Able to restore itself from the storage index parts, that are located in every timeline's remote directory and contain all data about
       3                 : //! remote timeline layers and its metadata.
       4                 : 
       5                 : use std::collections::HashMap;
       6                 : 
       7                 : use chrono::NaiveDateTime;
       8                 : use serde::{Deserialize, Serialize};
       9                 : use serde_with::{serde_as, DisplayFromStr};
      10                 : use utils::bin_ser::SerializeError;
      11                 : 
      12                 : use crate::tenant::metadata::TimelineMetadata;
      13                 : use crate::tenant::storage_layer::LayerFileName;
      14                 : use crate::tenant::upload_queue::UploadQueueInitialized;
      15                 : use crate::tenant::Generation;
      16                 : 
      17                 : use utils::lsn::Lsn;
      18                 : 
      19                 : /// Metadata gathered for each of the layer files.
      20                 : ///
      21                 : /// Fields have to be `Option`s because remote [`IndexPart`]'s can be from different version, which
      22                 : /// might have less or more metadata depending if upgrading or rolling back an upgrade.
      23 CBC      464136 : #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
      24                 : //#[cfg_attr(test, derive(Default))]
      25                 : pub struct LayerFileMetadata {
      26                 :     file_size: u64,
      27                 : 
      28                 :     pub(crate) generation: Generation,
      29                 : }
      30                 : 
      31                 : impl From<&'_ IndexLayerMetadata> for LayerFileMetadata {
      32           16856 :     fn from(other: &IndexLayerMetadata) -> Self {
      33           16856 :         LayerFileMetadata {
      34           16856 :             file_size: other.file_size,
      35           16856 :             generation: other.generation,
      36           16856 :         }
      37           16856 :     }
      38                 : }
      39                 : 
      40                 : impl LayerFileMetadata {
      41           33109 :     pub fn new(file_size: u64, generation: Generation) -> Self {
      42           33109 :         LayerFileMetadata {
      43           33109 :             file_size,
      44           33109 :             generation,
      45           33109 :         }
      46           33109 :     }
      47                 : 
      48           78719 :     pub fn file_size(&self) -> u64 {
      49           78719 :         self.file_size
      50           78719 :     }
      51                 : }
      52                 : 
      53                 : // TODO seems like another part of the remote storage file format
      54                 : // compatibility issue, see https://github.com/neondatabase/neon/issues/3072
      55                 : /// In-memory representation of an `index_part.json` file
      56                 : ///
      57                 : /// Contains the data about all files in the timeline, present remotely and its metadata.
      58                 : ///
      59                 : /// This type needs to be backwards and forwards compatible. When changing the fields,
      60                 : /// remember to add a test case for the changed version.
      61                 : #[serde_as]
      62            6972 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
      63                 : pub struct IndexPart {
      64                 :     /// Debugging aid describing the version of this type.
      65                 :     #[serde(default)]
      66                 :     version: usize,
      67                 : 
      68                 :     #[serde(default)]
      69                 :     #[serde(skip_serializing_if = "Option::is_none")]
      70                 :     pub deleted_at: Option<NaiveDateTime>,
      71                 : 
      72                 :     /// Per layer file name metadata, which can be present for a present or missing layer file.
      73                 :     ///
      74                 :     /// Older versions of `IndexPart` will not have this property or have only a part of metadata
      75                 :     /// that latest version stores.
      76                 :     pub layer_metadata: HashMap<LayerFileName, IndexLayerMetadata>,
      77                 : 
      78                 :     // 'disk_consistent_lsn' is a copy of the 'disk_consistent_lsn' in the metadata.
      79                 :     // It's duplicated for convenience when reading the serialized structure, but is
      80                 :     // private because internally we would read from metadata instead.
      81                 :     #[serde_as(as = "DisplayFromStr")]
      82                 :     disk_consistent_lsn: Lsn,
      83                 : 
      84                 :     #[serde(rename = "metadata_bytes")]
      85                 :     pub metadata: TimelineMetadata,
      86                 : }
      87                 : 
      88                 : impl IndexPart {
      89                 :     /// When adding or modifying any parts of `IndexPart`, increment the version so that it can be
      90                 :     /// used to understand later versions.
      91                 :     ///
      92                 :     /// Version is currently informative only.
      93                 :     /// Version history
      94                 :     /// - 2: added `deleted_at`
      95                 :     /// - 3: no longer deserialize `timeline_layers` (serialized format is the same, but timeline_layers
      96                 :     ///      is always generated from the keys of `layer_metadata`)
      97                 :     /// - 4: timeline_layers is fully removed.
      98                 :     const LATEST_VERSION: usize = 4;
      99                 : 
     100                 :     // Versions we may see when reading from a bucket.
     101                 :     pub const KNOWN_VERSIONS: &[usize] = &[1, 2, 3, 4];
     102                 : 
     103                 :     pub const FILE_NAME: &'static str = "index_part.json";
     104                 : 
     105            6260 :     pub fn new(
     106            6260 :         layers_and_metadata: HashMap<LayerFileName, LayerFileMetadata>,
     107            6260 :         disk_consistent_lsn: Lsn,
     108            6260 :         metadata: TimelineMetadata,
     109            6260 :     ) -> Self {
     110            6260 :         // Transform LayerFileMetadata into IndexLayerMetadata
     111            6260 :         let layer_metadata = layers_and_metadata
     112            6260 :             .into_iter()
     113          416558 :             .map(|(k, v)| (k, IndexLayerMetadata::from(v)))
     114            6260 :             .collect();
     115            6260 : 
     116            6260 :         Self {
     117            6260 :             version: Self::LATEST_VERSION,
     118            6260 :             layer_metadata,
     119            6260 :             disk_consistent_lsn,
     120            6260 :             metadata,
     121            6260 :             deleted_at: None,
     122            6260 :         }
     123            6260 :     }
     124                 : 
     125 UBC           0 :     pub fn get_version(&self) -> usize {
     126               0 :         self.version
     127               0 :     }
     128                 : 
     129                 :     /// If you want this under normal operations, read it from self.metadata:
     130                 :     /// this method is just for the scrubber to use when validating an index.
     131               0 :     pub fn get_disk_consistent_lsn(&self) -> Lsn {
     132               0 :         self.disk_consistent_lsn
     133               0 :     }
     134                 : }
     135                 : 
     136                 : impl TryFrom<&UploadQueueInitialized> for IndexPart {
     137                 :     type Error = SerializeError;
     138                 : 
     139 CBC         195 :     fn try_from(upload_queue: &UploadQueueInitialized) -> Result<Self, Self::Error> {
     140             195 :         let disk_consistent_lsn = upload_queue.latest_metadata.disk_consistent_lsn();
     141             195 :         let metadata = upload_queue.latest_metadata.clone();
     142             195 : 
     143             195 :         Ok(Self::new(
     144             195 :             upload_queue.latest_files.clone(),
     145             195 :             disk_consistent_lsn,
     146             195 :             metadata,
     147             195 :         ))
     148             195 :     }
     149                 : }
     150                 : 
     151                 : /// Serialized form of [`LayerFileMetadata`].
     152          422277 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
     153                 : pub struct IndexLayerMetadata {
     154                 :     pub file_size: u64,
     155                 : 
     156                 :     #[serde(default = "Generation::none")]
     157                 :     #[serde(skip_serializing_if = "Generation::is_none")]
     158                 :     pub(super) generation: Generation,
     159                 : }
     160                 : 
     161                 : impl From<LayerFileMetadata> for IndexLayerMetadata {
     162          416558 :     fn from(other: LayerFileMetadata) -> Self {
     163          416558 :         IndexLayerMetadata {
     164          416558 :             file_size: other.file_size,
     165          416558 :             generation: other.generation,
     166          416558 :         }
     167          416558 :     }
     168                 : }
     169                 : 
     170                 : #[cfg(test)]
     171                 : mod tests {
     172                 :     use super::*;
     173                 : 
     174               1 :     #[test]
     175               1 :     fn v1_indexpart_is_parsed() {
     176               1 :         let example = r#"{
     177               1 :             "version":1,
     178               1 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     179               1 :             "layer_metadata":{
     180               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     181               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     182               1 :             },
     183               1 :             "disk_consistent_lsn":"0/16960E8",
     184               1 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     185               1 :         }"#;
     186               1 : 
     187               1 :         let expected = IndexPart {
     188               1 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     189               1 :             version: 1,
     190               1 :             layer_metadata: HashMap::from([
     191               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     192               1 :                     file_size: 25600000,
     193               1 :                     generation: Generation::none()
     194               1 :                 }),
     195               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     196               1 :                     // serde_json should always parse this but this might be a double with jq for
     197               1 :                     // example.
     198               1 :                     file_size: 9007199254741001,
     199               1 :                     generation: Generation::none()
     200               1 :                 })
     201               1 :             ]),
     202               1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     203               1 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     204               1 :             deleted_at: None,
     205               1 :         };
     206               1 : 
     207               1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     208               1 :         assert_eq!(part, expected);
     209               1 :     }
     210                 : 
     211               1 :     #[test]
     212               1 :     fn v1_indexpart_is_parsed_with_optional_missing_layers() {
     213               1 :         let example = r#"{
     214               1 :             "version":1,
     215               1 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     216               1 :             "missing_layers":["This shouldn't fail deserialization"],
     217               1 :             "layer_metadata":{
     218               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     219               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     220               1 :             },
     221               1 :             "disk_consistent_lsn":"0/16960E8",
     222               1 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     223               1 :         }"#;
     224               1 : 
     225               1 :         let expected = IndexPart {
     226               1 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     227               1 :             version: 1,
     228               1 :             layer_metadata: HashMap::from([
     229               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     230               1 :                     file_size: 25600000,
     231               1 :                     generation: Generation::none()
     232               1 :                 }),
     233               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     234               1 :                     // serde_json should always parse this but this might be a double with jq for
     235               1 :                     // example.
     236               1 :                     file_size: 9007199254741001,
     237               1 :                     generation: Generation::none()
     238               1 :                 })
     239               1 :             ]),
     240               1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     241               1 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     242               1 :             deleted_at: None,
     243               1 :         };
     244               1 : 
     245               1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     246               1 :         assert_eq!(part, expected);
     247               1 :     }
     248                 : 
     249               1 :     #[test]
     250               1 :     fn v2_indexpart_is_parsed_with_deleted_at() {
     251               1 :         let example = r#"{
     252               1 :             "version":2,
     253               1 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     254               1 :             "missing_layers":["This shouldn't fail deserialization"],
     255               1 :             "layer_metadata":{
     256               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     257               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     258               1 :             },
     259               1 :             "disk_consistent_lsn":"0/16960E8",
     260               1 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     261               1 :             "deleted_at": "2023-07-31T09:00:00.123"
     262               1 :         }"#;
     263               1 : 
     264               1 :         let expected = IndexPart {
     265               1 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     266               1 :             version: 2,
     267               1 :             layer_metadata: HashMap::from([
     268               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     269               1 :                     file_size: 25600000,
     270               1 :                     generation: Generation::none()
     271               1 :                 }),
     272               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     273               1 :                     // serde_json should always parse this but this might be a double with jq for
     274               1 :                     // example.
     275               1 :                     file_size: 9007199254741001,
     276               1 :                     generation: Generation::none()
     277               1 :                 })
     278               1 :             ]),
     279               1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     280               1 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     281               1 :             deleted_at: Some(chrono::NaiveDateTime::parse_from_str(
     282               1 :                 "2023-07-31T09:00:00.123000000", "%Y-%m-%dT%H:%M:%S.%f").unwrap())
     283               1 :         };
     284               1 : 
     285               1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     286               1 :         assert_eq!(part, expected);
     287               1 :     }
     288                 : 
     289               1 :     #[test]
     290               1 :     fn empty_layers_are_parsed() {
     291               1 :         let empty_layers_json = r#"{
     292               1 :             "version":1,
     293               1 :             "timeline_layers":[],
     294               1 :             "layer_metadata":{},
     295               1 :             "disk_consistent_lsn":"0/2532648",
     296               1 :             "metadata_bytes":[136,151,49,208,0,70,0,4,0,0,0,0,2,83,38,72,1,0,0,0,0,2,83,38,32,1,87,198,240,135,97,119,45,125,38,29,155,161,140,141,255,210,0,0,0,0,2,83,38,72,0,0,0,0,1,73,240,192,0,0,0,0,1,73,240,192,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     297               1 :         }"#;
     298               1 : 
     299               1 :         let expected = IndexPart {
     300               1 :             version: 1,
     301               1 :             layer_metadata: HashMap::new(),
     302               1 :             disk_consistent_lsn: "0/2532648".parse::<Lsn>().unwrap(),
     303               1 :             metadata: TimelineMetadata::from_bytes(&[
     304               1 :                 136, 151, 49, 208, 0, 70, 0, 4, 0, 0, 0, 0, 2, 83, 38, 72, 1, 0, 0, 0, 0, 2, 83,
     305               1 :                 38, 32, 1, 87, 198, 240, 135, 97, 119, 45, 125, 38, 29, 155, 161, 140, 141, 255,
     306               1 :                 210, 0, 0, 0, 0, 2, 83, 38, 72, 0, 0, 0, 0, 1, 73, 240, 192, 0, 0, 0, 0, 1, 73,
     307               1 :                 240, 192, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     308               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     309               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     310               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     311               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     312               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     313               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     314               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     315               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     316               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     317               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     318               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     319               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     320               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     321               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     322               1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     323               1 :                 0, 0,
     324               1 :             ])
     325               1 :             .unwrap(),
     326               1 :             deleted_at: None,
     327               1 :         };
     328               1 : 
     329               1 :         let empty_layers_parsed = serde_json::from_str::<IndexPart>(empty_layers_json).unwrap();
     330               1 : 
     331               1 :         assert_eq!(empty_layers_parsed, expected);
     332               1 :     }
     333                 : 
     334               1 :     #[test]
     335               1 :     fn v4_indexpart_is_parsed() {
     336               1 :         let example = r#"{
     337               1 :             "version":4,
     338               1 :             "layer_metadata":{
     339               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     340               1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     341               1 :             },
     342               1 :             "disk_consistent_lsn":"0/16960E8",
     343               1 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     344               1 :             "deleted_at": "2023-07-31T09:00:00.123"
     345               1 :         }"#;
     346               1 : 
     347               1 :         let expected = IndexPart {
     348               1 :             version: 4,
     349               1 :             layer_metadata: HashMap::from([
     350               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     351               1 :                     file_size: 25600000,
     352               1 :                     generation: Generation::none()
     353               1 :                 }),
     354               1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     355               1 :                     // serde_json should always parse this but this might be a double with jq for
     356               1 :                     // example.
     357               1 :                     file_size: 9007199254741001,
     358               1 :                     generation: Generation::none()
     359               1 :                 })
     360               1 :             ]),
     361               1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     362               1 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     363               1 :             deleted_at: Some(chrono::NaiveDateTime::parse_from_str(
     364               1 :                 "2023-07-31T09:00:00.123000000", "%Y-%m-%dT%H:%M:%S.%f").unwrap())
     365               1 :         };
     366               1 : 
     367               1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     368               1 :         assert_eq!(part, expected);
     369               1 :     }
     370                 : }
        

Generated by: LCOV version 2.1-beta